diff --git a/examples/dynamic-checkout/index.html b/examples/dynamic-checkout/index.html
index 24f65ab0..3f5245df 100644
--- a/examples/dynamic-checkout/index.html
+++ b/examples/dynamic-checkout/index.html
@@ -11,7 +11,7 @@
document.addEventListener("DOMContentLoaded", function () {
// You need to replace these values with your own
const projectId = "test-proj_qEi1u5BwoYcZb6mOMKDWIm4mpqKCq6bN"
- const invoiceId = "iv_1buJtZFcpQe3sdo3TdtnDWp8jTHTEk88"
+ const invoiceId = "iv_30JJHCq6bN6TX1rV260OFo1oARdK0eQu"
const clientSecret = ""
const client = new ProcessOut.ProcessOut(projectId)
diff --git a/package.json b/package.json
index f7df18ad..ec8de60e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "processout.js",
- "version": "1.2.6",
+ "version": "1.2.7",
"description": "ProcessOut.js is a JavaScript library for ProcessOut's payment processing API.",
"scripts": {
"build:processout": "tsc -p src/processout && uglifyjs --compress --keep-fnames --ie8 dist/processout.js -o dist/processout.js",
diff --git a/src/nativeapm/config/theme.ts b/src/nativeapm/config/theme.ts
index 86c95968..3a41bcbd 100644
--- a/src/nativeapm/config/theme.ts
+++ b/src/nativeapm/config/theme.ts
@@ -132,10 +132,82 @@ const defaultTheme: NativeApmThemeConfigType = {
outline: "none",
background: "transparent",
padding: "8px 6px",
- width: "130px",
+ width: "80px",
fontSize: "0.9rem",
borderRight: "6px solid transparent",
height: "3rem",
+ opacity: "0",
+ position: "absolute",
+ top: "0",
+ left: "0",
+ zIndex: "10",
+ },
+ selectWrapper: {
+ position: "relative",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ cursor: "pointer",
+ minWidth: "60px",
+ minHeight: "40px",
+ width: "70px",
+ height: "40px",
+ boxSizing: "border-box",
+ },
+ flagDisplay: {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ pointerEvents: "none",
+ width: "100%",
+ height: "100%",
+ paddingRight: "3px",
+ },
+ flagImageVisible: {
+ width: "24px",
+ height: "16px",
+ objectFit: "cover",
+ display: "inline-block",
+ margin: "auto",
+ borderRadius: "4px",
+ },
+ flagImageHidden: {
+ width: "24px",
+ height: "16px",
+ objectFit: "cover",
+ display: "none",
+ margin: "auto",
+ },
+ flagDefaultTextVisible: {
+ textAlign: "center",
+ display: "inline-block",
+ alignItems: "center",
+ justifyContent: "center",
+ width: "20px",
+ height: "15px",
+ backgroundColor: "#ccc",
+ borderRadius: "4px",
+ content: '""',
+ },
+ flagDefaultTextHidden: {
+ textAlign: "center",
+ display: "none",
+ alignItems: "center",
+ justifyContent: "center",
+ width: "100%",
+ height: "100%",
+ },
+ selectChevron: {
+ position: "absolute",
+ right: "9px",
+ top: "50%",
+ transform: "translateY(-50%)",
+ width: "0",
+ height: "0",
+ borderLeft: "4px solid transparent",
+ borderRight: "4px solid transparent",
+ borderTop: "4px solid #666",
+ pointerEvents: "none",
},
phoneInputsContainer: {
display: "flex",
diff --git a/src/nativeapm/elements/inputs/input.ts b/src/nativeapm/elements/inputs/input.ts
index 4844ca33..5ceec1ea 100644
--- a/src/nativeapm/elements/inputs/input.ts
+++ b/src/nativeapm/elements/inputs/input.ts
@@ -35,15 +35,18 @@ module ProcessOut {
* @type {INativeApmInput}
*/
inputInstance: INativeApmInput
+ processOut: ProcessOut
/**
* Native APM Input constructor
*/
constructor(
+ processOut: ProcessOut,
inputData: NativeApmInputData,
theme: NativeApmThemeConfigType,
prefilledValue?: string,
) {
+ this.processOut = processOut
this.inputInstance = this.createInputElement(inputData, theme, prefilledValue)
}
@@ -87,7 +90,12 @@ module ProcessOut {
return new NativeApmTextInput(inputData, theme, prefilledValue)
}
case "phone":
- return new NativeApmPhoneWithCountryInput(inputData, theme, prefilledValue)
+ return new NativeApmPhoneWithCountryInput(
+ this.processOut,
+ inputData,
+ theme,
+ prefilledValue,
+ )
case "single_select":
case "single-select":
diff --git a/src/nativeapm/elements/inputs/phone-with-country.ts b/src/nativeapm/elements/inputs/phone-with-country.ts
index c6ec63d0..852f6f9c 100644
--- a/src/nativeapm/elements/inputs/phone-with-country.ts
+++ b/src/nativeapm/elements/inputs/phone-with-country.ts
@@ -1,487 +1,681 @@
///
-/**
- * ProcessOut module/namespace
- */
module ProcessOut {
- /**
- * ProcessOut Native APM class for handling phone input with country code selection
- */
export class NativeApmPhoneWithCountryInput implements INativeApmInput {
- /**
- * Native APM country code select element
- * @type {HTMLSelectElement}
- */
- countrySelectElement: HTMLSelectElement
-
- /**
- * Native APM phone prefix input element (auto-populated from country selection)
- * @type {HTMLInputElement}
- */
- phonePrefixInputElement: HTMLInputElement
-
- /**
- * Native APM phone number input element
- * @type {HTMLInputElement}
- */
- phoneNumberInputElement: HTMLInputElement
-
- /**
- * Native APM input data
- * @type {NativeApmInputData}
- */
- inputData: NativeApmInputData
-
- /**
- * Native APM error message element
- * @type {HTMLElement}
- */
- errorMessageElement: HTMLElement
-
- /**
- * Native APM input label element
- * @type {HTMLElement}
- */
- inputLabel: HTMLElement
-
- /**
- * Native APM phone input container element
- * @type {HTMLElement}
- */
- phoneInputContainer: HTMLElement
-
- /**
- * Theme of the Native APM form
- * @type {NativeApmThemeConfigType}
- */
- theme: NativeApmThemeConfigType
-
- /**
- * Phone number regex for validation
- */
- phoneNumberRegex = /^[0-9]{7,15}$/
-
- /**
- * Native APM Input constructor
- */
+ private processOut: ProcessOut
+ private readonly countrySelectElement: HTMLSelectElement
+ private readonly phoneNumberInputElement: HTMLInputElement
+ private readonly errorMessageElement: HTMLElement
+ private readonly inputLabel: HTMLElement
+ private readonly theme: NativeApmThemeConfigType
+ private readonly flagDisplayElement: HTMLElement
+ private readonly selectWrapper: HTMLElement
+ private readonly chevronElement: HTMLElement
+ private phoneInputContainer: HTMLElement
+
+ readonly inputData: NativeApmInputData
+
+ private static readonly PHONE_NUMBER_REGEX = /^[0-9]{7,15}$/
+ private static readonly DEFAULT_MAX_LENGTH = 15
+
constructor(
+ processOut: ProcessOut,
inputData: NativeApmInputData,
theme: NativeApmThemeConfigType,
prefilledValue?: string,
) {
+ this.processOut = processOut
this.theme = theme
this.inputData = inputData
this.countrySelectElement = this.createCountrySelectElement()
- this.phonePrefixInputElement = this.createPhonePrefixInputElement()
this.phoneNumberInputElement = this.createPhoneNumberInputElement(prefilledValue)
this.inputLabel = this.createInputLabel()
this.errorMessageElement = this.createErrorMessageElement()
+ this.flagDisplayElement = this.createFlagDisplayElement()
+ this.selectWrapper = this.createSelectWrapper()
+ this.chevronElement = this.createChevronElement()
}
- /**
- * This function returns the input element
- */
public getInputElement() {
- const inputWrapper = document.createElement("div")
- inputWrapper.setAttribute("class", "native-apm-phone-with-country-input-wrapper")
+ this.phoneInputContainer = this.createPhoneInputContainer()
+ const inputWrapper = this.createInputWrapper()
+ const countrySelectContainer = this.createCountrySelectContainer()
+
+ this.applyStyles(inputWrapper, countrySelectContainer)
+ this.setAccessibilityAttributes()
+ this.setupFocusEventListeners()
+ this.setupKeyboardNavigation()
+ this.setupEventListeners()
+ this.assembleElements(inputWrapper, countrySelectContainer)
+ this.initializeFlagDisplay()
- const phoneInputContainer = document.createElement("div")
- phoneInputContainer.setAttribute("class", "native-apm-phone-input-container")
- this.phoneInputContainer = phoneInputContainer
+ return inputWrapper
+ }
- const countrySelectContainer = document.createElement("div")
- countrySelectContainer.setAttribute("class", "native-apm-country-select-container")
+ private initializeFlagDisplay() {
+ const phoneValue = this.phoneNumberInputElement.value
- const phoneInputsContainer = document.createElement("div")
- phoneInputsContainer.setAttribute("class", "native-apm-phone-inputs-container")
+ if (phoneValue) {
+ const detectedCountry = this.detectCountry(phoneValue)
- StylesUtils.styleElement(inputWrapper, this.theme.form.inputs.wrapper)
+ this.updateCountrySelect(detectedCountry)
+ } else {
+ this.updateFlagDisplay(null)
+ }
+ }
- StylesUtils.styleElement(
- this.countrySelectElement,
- this.theme.form.inputs.phoneWithCountry.countrySelect,
- )
+ private createInputWrapper() {
+ const wrapper = document.createElement("div")
+
+ wrapper.className = "native-apm-phone-with-country-input-wrapper"
+
+ return wrapper
+ }
+
+ private createPhoneInputContainer() {
+ const container = document.createElement("div")
+
+ container.className = "native-apm-phone-input-container"
+
+ return container
+ }
+
+ private createCountrySelectContainer() {
+ const container = document.createElement("div")
+
+ container.className = "native-apm-country-select-container"
+
+ return container
+ }
+
+ private applyStyles(inputWrapper: HTMLElement, countrySelectContainer: HTMLElement) {
+ const inputsStyles = this.theme.form.inputs
+
+ StylesUtils.styleElement(inputWrapper, inputsStyles.wrapper)
StylesUtils.styleElement(
- this.phonePrefixInputElement,
- this.theme.form.inputs.phoneWithCountry.phonePrefixInput,
+ this.countrySelectElement,
+ inputsStyles.phoneWithCountry.countrySelect,
)
StylesUtils.styleElement(
this.phoneNumberInputElement,
- this.theme.form.inputs.phoneWithCountry.phoneNumberInput,
+ inputsStyles.phoneWithCountry.phoneNumberInput,
)
- StylesUtils.styleElement(
- phoneInputContainer,
- this.theme.form.inputs.phoneWithCountry.container,
- )
+ StylesUtils.styleElement(this.phoneInputContainer, inputsStyles.phoneWithCountry.container)
StylesUtils.styleElement(
countrySelectContainer,
- this.theme.form.inputs.phoneWithCountry.countrySelectContainer,
+ inputsStyles.phoneWithCountry.countrySelectContainer,
)
- StylesUtils.styleElement(
- phoneInputsContainer,
- this.theme.form.inputs.phoneWithCountry.phoneInputsContainer,
- )
+ StylesUtils.styleElement(this.selectWrapper, inputsStyles.phoneWithCountry.selectWrapper)
- // Initially hide the prefix input and set phone input to full width
- this.phonePrefixInputElement.style.display = "none"
- this.updatePhoneInputLayout()
+ StylesUtils.styleElement(this.flagDisplayElement, inputsStyles.phoneWithCountry.flagDisplay)
- // Set up accessibility features
- this.setupAccessibility()
+ const flagImg = this.flagDisplayElement.querySelector(".native-apm-flag-image") as HTMLElement
- this.countrySelectElement.addEventListener("change", () => this.handleCountrySelection())
+ const flagText = this.flagDisplayElement.querySelector(
+ ".native-apm-flag-default-text",
+ ) as HTMLElement
- countrySelectContainer.appendChild(this.countrySelectElement)
- phoneInputsContainer.appendChild(this.phonePrefixInputElement)
- phoneInputsContainer.appendChild(this.phoneNumberInputElement)
+ if (flagImg) {
+ StylesUtils.styleElement(flagImg, inputsStyles.phoneWithCountry.flagImageHidden)
+ }
- phoneInputContainer.appendChild(countrySelectContainer)
- phoneInputContainer.appendChild(phoneInputsContainer)
+ if (flagText) {
+ StylesUtils.styleElement(flagText, inputsStyles.phoneWithCountry.flagDefaultTextVisible)
+ }
- inputWrapper.appendChild(this.inputLabel)
- inputWrapper.appendChild(phoneInputContainer)
- inputWrapper.appendChild(this.errorMessageElement)
+ StylesUtils.styleElement(this.chevronElement, inputsStyles.phoneWithCountry.selectChevron)
+ }
- return inputWrapper
+ private setupEventListeners() {
+ this.phoneNumberInputElement.addEventListener("input", () => {
+ this.handlePhoneNumberInput()
+ })
+
+ this.countrySelectElement.addEventListener("change", () => {
+ this.handleCountryChange()
+ })
}
- /**
- * This function handles country selection and populates phone prefix
- */
- private handleCountrySelection() {
+ private handleCountryChange() {
const selectedCountryCode = this.countrySelectElement.value
if (selectedCountryCode) {
- this.phonePrefixInputElement.value = `(${selectedCountryCode})`
- this.phonePrefixInputElement.style.display = "block"
-
- this.updatePhoneInputLayout()
- this.updatePrefixInputWidth()
-
- // Focus on phone number input after country selection
- this.phoneNumberInputElement.focus()
+ this.applySelectedCountry(selectedCountryCode)
} else {
- this.phonePrefixInputElement.value = ""
- this.phonePrefixInputElement.style.display = "none"
- this.updatePhoneInputLayout()
+ this.clearCountrySelection()
}
this.resetErrorMessage()
+ this.phoneNumberInputElement.focus()
}
- /**
- * This function updates the phone input layout based on whether prefix is shown
- */
- private updatePhoneInputLayout() {
- const prefixVisible = this.phonePrefixInputElement.style.display !== "none"
+ private applySelectedCountry(countryCode: string) {
+ const countryData = COUNTRY_CODE_TO_PHONE_PREFIX[countryCode]
+ const prefix = countryData && countryData.prefix ? countryData.prefix : ""
- if (prefixVisible) {
- // Show prefix and phone number inputs side by side
- this.phoneNumberInputElement.style.width = "100%"
+ // Apply the prefix of country after user manually selects the country
+ this.phoneNumberInputElement.value = prefix.replace(/[^+\d]/g, "")
+ this.updateFlagDisplay(countryCode)
+ }
- // Hide placeholder when prefix is visible (country is selected)
- this.phoneNumberInputElement.removeAttribute("placeholder")
- } else {
- // Show only phone number input with full width and placeholder
- this.phoneNumberInputElement.style.width = "100%"
- this.phoneNumberInputElement.setAttribute(
- "placeholder",
- TextUtils.getText("phoneNumberPlaceholder") || "Phone number",
- )
+ private clearCountrySelection() {
+ this.phoneNumberInputElement.value = ""
+ this.updateFlagDisplay("")
+ }
+
+ private assembleElements(inputWrapper: HTMLElement, countrySelectContainer: HTMLElement) {
+ this.selectWrapper.appendChild(this.countrySelectElement)
+ this.selectWrapper.appendChild(this.flagDisplayElement)
+ this.selectWrapper.appendChild(this.chevronElement)
+
+ countrySelectContainer.appendChild(this.selectWrapper)
+ this.phoneInputContainer.appendChild(countrySelectContainer)
+ this.phoneInputContainer.appendChild(this.phoneNumberInputElement)
+
+ inputWrapper.appendChild(this.inputLabel)
+ inputWrapper.appendChild(this.phoneInputContainer)
+ inputWrapper.appendChild(this.errorMessageElement)
+ }
+
+ private handlePhoneNumberInput() {
+ // Prepend plus if user starts typing with a number
+ let phoneNumber = this.phoneNumberInputElement.value
+ phoneNumber = this.autoPrependPlusIfNeeded(phoneNumber)
+
+ this.processPhoneNumberInput(phoneNumber)
+ this.resetErrorMessage()
+ }
+
+ private autoPrependPlusIfNeeded(phoneNumber: string) {
+ if (phoneNumber.length === 1 && phoneNumber !== "+") {
+ const updatedNumber = "+" + phoneNumber
+
+ this.phoneNumberInputElement.value = updatedNumber
+
+ return updatedNumber
+ }
+
+ return phoneNumber
+ }
+
+ private processPhoneNumberInput(phoneNumber: string) {
+ const detectedCountry = this.detectCountry(phoneNumber)
+ this.updateCountrySelect(detectedCountry)
+
+ const maxLength = this.getMaxLengthForCountry(detectedCountry)
+ this.limitInputLength(phoneNumber, maxLength)
+ }
+
+ private detectCountry(phoneNumber: string) {
+ if (phoneNumber.indexOf("+") === 0) {
+ const detectedByPrefix = this.detectCountryByPrefix(phoneNumber)
+
+ if (detectedByPrefix) {
+ return detectedByPrefix
+ }
+ }
+
+ return this.detectCountryWithLibphonenumber(phoneNumber)
+ }
+
+ private detectCountryByPrefix(phoneNumber: string) {
+ const prefixMap = this.buildPrefixMap()
+ let bestMatch = { prefix: "", countries: [] as string[] }
+
+ Object.keys(prefixMap).forEach(function (prefix) {
+ if (phoneNumber.indexOf(prefix) === 0 && prefix.length > bestMatch.prefix.length) {
+ bestMatch = { prefix: prefix, countries: prefixMap[prefix] }
+ }
+ })
+
+ return bestMatch.countries.length > 0 ? bestMatch.countries[0] : null
+ }
+
+ private buildPrefixMap(): { [prefix: string]: string[] } {
+ const prefixMap: { [prefix: string]: string[] } = {}
+
+ Object.keys(COUNTRY_CODE_TO_PHONE_PREFIX).forEach(function (countryCode) {
+ const prefix = COUNTRY_CODE_TO_PHONE_PREFIX[countryCode].prefix
+
+ prefixMap[prefix] = prefixMap[prefix] || []
+ prefixMap[prefix].push(countryCode)
+ })
+
+ return prefixMap
+ }
+
+ private detectCountryWithLibphonenumber(phoneNumber: string) {
+ const libphonenumber = window && window.globalThis ? window.globalThis.libphonenumber : null
+
+ if (!libphonenumber || phoneNumber.length <= 1) {
+ return null
+ }
+
+ try {
+ const parsed = libphonenumber.parsePhoneNumber(phoneNumber)
+
+ return parsed?.country || null
+ } catch {
+ return null
}
}
- /**
- * This function updates the prefix input width based on its content
- */
- private updatePrefixInputWidth() {
- const prefixValue = this.phonePrefixInputElement.value
+ private updateCountrySelect(detectedCountry: string | null) {
+ if (!detectedCountry) {
+ this.countrySelectElement.selectedIndex = 0
+ this.updateFlagDisplay("")
- if (prefixValue) {
- // Calculate width based on character count (approximate)
- const charCount = prefixValue.length
- this.phonePrefixInputElement.style.width = `${charCount}ch`
+ return
}
+
+ const options = Array.from(this.countrySelectElement.options)
+ options.forEach((option, index) => {
+ if (option.value === detectedCountry) {
+ this.countrySelectElement.selectedIndex = index
+ this.updateFlagDisplay(detectedCountry)
+ }
+ })
}
- /**
- * This function sets up accessibility features for the phone input
- */
- private setupAccessibility() {
- // Make the container focusable and add ARIA attributes
+ private getMaxLengthForCountry(detectedCountry: string | null) {
+ const libphonenumber = window.globalThis.libphonenumber
+
+ if (!detectedCountry) {
+ return NativeApmPhoneWithCountryInput.DEFAULT_MAX_LENGTH
+ }
+
+ if (!libphonenumber) {
+ return NativeApmPhoneWithCountryInput.DEFAULT_MAX_LENGTH
+ }
+
+ try {
+ const metadata = libphonenumber.getMetadata()
+ const countryMeta = metadata.countries[detectedCountry]
+
+ return countryMeta && countryMeta[2]
+ ? Math.max(...countryMeta[2])
+ : NativeApmPhoneWithCountryInput.DEFAULT_MAX_LENGTH
+ } catch {
+ return NativeApmPhoneWithCountryInput.DEFAULT_MAX_LENGTH
+ }
+ }
+
+ private limitInputLength(phoneNumber: string, maxLength: number) {
+ const sanitized = this.sanitizePhoneNumber(phoneNumber)
+ const limited = this.applyLengthLimit(sanitized, maxLength)
+
+ if (limited !== phoneNumber) {
+ this.phoneNumberInputElement.value = limited
+ }
+ }
+
+ private sanitizePhoneNumber(phoneNumber: string) {
+ return phoneNumber[0] === "+"
+ ? "+" + phoneNumber.slice(1).replace(/[^\d-]/g, "")
+ : phoneNumber.replace(/[^\d-]/g, "")
+ }
+
+ private applyLengthLimit(phoneNumber: string, maxLength: number) {
+ if (phoneNumber[0] !== "+") {
+ return phoneNumber
+ }
+
+ const prefixMatch = phoneNumber.match(/^\+\d+/)
+ const prefixLength = prefixMatch ? prefixMatch[0].length : 1
+
+ const prefix = phoneNumber.slice(0, prefixLength)
+ const number = phoneNumber.slice(prefixLength, prefixLength + maxLength)
+
+ return prefix + number
+ }
+
+ private setAccessibilityAttributes() {
+ const { key } = this.inputData
+
this.phoneInputContainer.setAttribute("tabindex", "0")
this.phoneInputContainer.setAttribute("role", "group")
- this.phoneInputContainer.setAttribute("aria-labelledby", `${this.inputData.key}_label`)
- this.phoneInputContainer.setAttribute("aria-describedby", `${this.inputData.key}_error`)
-
- // Add focus event listeners to all input elements
- this.countrySelectElement.addEventListener("focus", () => this.handleInputFocus())
- this.phonePrefixInputElement.addEventListener("focus", () => this.handleInputFocus())
- this.phoneNumberInputElement.addEventListener("focus", () => this.handleInputFocus())
-
- // Add blur event listeners to all input elements
- this.countrySelectElement.addEventListener("blur", () => this.handleInputBlur())
- this.phonePrefixInputElement.addEventListener("blur", () => this.handleInputBlur())
- this.phoneNumberInputElement.addEventListener("blur", () => this.handleInputBlur())
-
- // Add keyboard navigation to the container
- this.phoneInputContainer.addEventListener("keydown", event =>
- this.handleContainerKeyDown(event),
- )
+ this.phoneInputContainer.setAttribute("aria-labelledby", key + "_label")
+ this.phoneInputContainer.setAttribute("aria-describedby", key + "_error")
+ }
+
+ private setupFocusEventListeners() {
+ const elements = [this.countrySelectElement, this.phoneNumberInputElement]
+
+ elements.forEach(element => {
+ element.addEventListener("focus", () => {
+ this.handleInputFocus()
+ })
+
+ element.addEventListener("blur", () => {
+ this.handleInputBlur()
+ })
+ })
+ }
+
+ private setupKeyboardNavigation() {
+ this.phoneInputContainer.addEventListener("keydown", event => {
+ this.handleContainerKeyDown(event)
+ })
}
- /**
- * This function handles focus events on any input within the phone input group
- */
private handleInputFocus() {
- // Add focused styles to the container
StylesUtils.styleElement(
this.phoneInputContainer,
this.theme.form.inputs.phoneWithCountry.containerFocused,
)
- // Update ARIA attributes to indicate focus
this.phoneInputContainer.setAttribute("aria-expanded", "true")
}
- /**
- * This function handles blur events on any input within the phone input group
- */
private handleInputBlur() {
- // Check if any child element is still focused
- const activeElement = document.activeElement
-
- const isAnyChildFocused =
- activeElement === this.countrySelectElement ||
- activeElement === this.phonePrefixInputElement ||
- activeElement === this.phoneNumberInputElement ||
- activeElement === this.phoneInputContainer
-
- if (!isAnyChildFocused) {
- // Remove focused styles from the container
+ if (!this.isAnyChildFocused()) {
StylesUtils.styleElement(
this.phoneInputContainer,
this.theme.form.inputs.phoneWithCountry.container,
)
- // Update ARIA attributes
this.phoneInputContainer.setAttribute("aria-expanded", "false")
}
}
- /**
- * This function handles keyboard navigation within the container
- */
+ private isAnyChildFocused() {
+ const activeElement = document.activeElement
+
+ const focusableElements = [
+ this.countrySelectElement,
+ this.phoneNumberInputElement,
+ this.phoneInputContainer,
+ ]
+
+ let focusedElement = null
+
+ focusableElements.forEach(function (element) {
+ if (element === activeElement) {
+ focusedElement = element
+ }
+ })
+
+ return !!focusedElement
+ }
+
private handleContainerKeyDown(event: KeyboardEvent) {
- switch (event.key) {
- case "Enter":
- // Focus the country select when container is activated
- event.preventDefault()
- this.countrySelectElement.focus()
- break
- case "Tab":
- // Allow normal tab navigation
- break
- default:
- // For other keys, focus the phone number input
- if (!this.phoneNumberInputElement.matches(":focus")) {
- this.phoneNumberInputElement.focus()
- }
- break
+ if (event.key === "Enter") {
+ event.preventDefault()
+ this.countrySelectElement.focus()
+ return
+ }
+
+ if (event.key !== "Tab" && !this.phoneNumberInputElement.matches(":focus")) {
+ this.phoneNumberInputElement.focus()
}
}
- /**
- * This function validates the input
- */
public validate() {
- const isRequiredPassed = this.inputData.required
- ? this.phoneNumberInputElement.value.length > 0
- : true
+ const validationResults = {
+ required: this.validateRequired(),
+ phone: this.validatePhoneNumber(),
+ country: this.validateCountrySelection(),
+ }
- const isPhoneValid =
- this.phoneNumberInputElement.value.length > 0
- ? this.phoneNumberRegex.test(this.phoneNumberInputElement.value.replace(/\s/g, ""))
- : true
+ this.handleValidationErrors(validationResults)
- const isCountrySelected = this.countrySelectElement.value.length > 0
- const isPrefixValid = this.phonePrefixInputElement.value.length > 0
+ return validationResults.required && validationResults.phone && validationResults.country
+ }
- if (!(isRequiredPassed && isPhoneValid)) {
+ private handleValidationErrors(results: {
+ required: boolean
+ phone: boolean
+ country: boolean
+ }) {
+ if (!results.required || !results.phone) {
this.setErrorMessage()
}
- if (!isCountrySelected) {
+ if (!results.country) {
this.setErrorMessage(TextUtils.getText("invalidCountryCode") || "Invalid country code")
}
+ }
- return isRequiredPassed && isPhoneValid && isCountrySelected && isPrefixValid
+ private validateRequired() {
+ return !this.inputData.required || this.phoneNumberInputElement.value.length > 0
}
- /**
- * This function returns value of the input
- */
- public getValue() {
- const phonePrefix =
- this.phonePrefixInputElement.value && this.phonePrefixInputElement.value.length > 0
- ? this.phonePrefixInputElement.value.replace(/[()]/g, "")
- : ""
+ private validatePhoneNumber() {
+ const phoneValue = this.phoneNumberInputElement.value
- const phoneNumber =
- this.phoneNumberInputElement.value && this.phoneNumberInputElement.value.length > 0
- ? this.phoneNumberInputElement.value.replace(/\s/g, "")
- : ""
+ if (!phoneValue) {
+ return true
+ }
+
+ return this.validateWithLibphonenumber(phoneValue) ?? this.validateWithRegex(phoneValue)
+ }
- return {
- [this.inputData.key]: `${phonePrefix}${phoneNumber}`,
+ private validateWithLibphonenumber(phoneValue: string) {
+ const libphonenumber = window.globalThis.libphonenumber
+
+ if (!libphonenumber) {
+ return null
+ }
+
+ try {
+ const parsed = libphonenumber.parsePhoneNumber(phoneValue)
+
+ return parsed.isValid()
+ } catch {
+ return false
+ }
+ }
+
+ private validateWithRegex(phoneValue: string) {
+ return NativeApmPhoneWithCountryInput.PHONE_NUMBER_REGEX.test(phoneValue.replace(/\s/g, ""))
+ }
+
+ private validateCountrySelection() {
+ return this.countrySelectElement.value.length > 0
+ }
+
+ public getValue(): { [key: string]: string } {
+ const phoneValue = this.getFormattedPhoneNumber()
+
+ return { [this.inputData.key]: phoneValue }
+ }
+
+ private getFormattedPhoneNumber() {
+ const libphonenumber = window.globalThis.libphonenumber
+ const phoneValue = this.phoneNumberInputElement.value
+
+ if (!libphonenumber || !phoneValue) {
+ return phoneValue
+ }
+
+ try {
+ const parsed = libphonenumber.parsePhoneNumber(phoneValue)
+
+ return parsed.number
+ } catch {
+ return phoneValue
}
}
- /**
- * This function returns the selected country code
- */
public getCountryCode() {
return this.countrySelectElement.value
}
- /**
- * This function returns the phone number
- */
public getPhoneNumber() {
return this.phoneNumberInputElement.value
}
- /**
- * This function creates country select element
- */
private createCountrySelectElement() {
const select = document.createElement("select")
- select.setAttribute("class", "native-apm-country-select")
- select.setAttribute("name", `${this.inputData.key}_country_code`)
- select.setAttribute("id", `${this.inputData.key}_country_code`)
+ this.setCountrySelectAttributes(select)
+ this.addDefaultOption(select)
+ this.addCountryOptions(select)
+
+ StylesUtils.styleElement(select, this.theme.form.inputs.select)
+
+ return select
+ }
+
+ private setCountrySelectAttributes(select: HTMLSelectElement) {
+ const { key } = this.inputData
+
+ select.className = "native-apm-country-select"
+ select.name = key + "_country_code"
+ select.id = key + "_country_code"
select.setAttribute("aria-label", "Select country code")
+ }
+ private addDefaultOption(select: HTMLSelectElement) {
const defaultOption = document.createElement("option")
+
defaultOption.value = ""
- defaultOption.textContent = TextUtils.getText("selectCountryPlaceholder") || "Select country"
- defaultOption.disabled = true
+ defaultOption.disabled = false
defaultOption.selected = true
+
select.appendChild(defaultOption)
+ }
- // Add country options using country names as display text and country codes as values
+ private addCountryOptions(select: HTMLSelectElement) {
Object.keys(COUNTRY_CODE_TO_PHONE_PREFIX).forEach(countryCode => {
const country = COUNTRY_CODE_TO_PHONE_PREFIX[countryCode]
if (country) {
- const option = document.createElement("option")
- option.value = country.prefix
- option.textContent = country.name
+ const option = this.createCountryOption(countryCode, country)
select.appendChild(option)
}
})
+ }
- select.addEventListener("change", this.resetErrorMessage.bind(this))
+ private createCountryOption(countryCode: string, country: { prefix: string; name: string }) {
+ const option = document.createElement("option")
- StylesUtils.styleElement(select, this.theme.form.inputs.select)
+ option.value = countryCode
+ option.textContent = country.name
- return select
+ return option
}
- /**
- * This function creates phone prefix input element
- */
- private createPhonePrefixInputElement() {
+ private createPhoneNumberInputElement(prefilledValue?: string) {
const input = document.createElement("input")
- input.setAttribute("class", "native-apm-phone-prefix-input")
- input.setAttribute("name", `${this.inputData.key}_prefix`)
- input.setAttribute("id", `${this.inputData.key}_prefix`)
- input.setAttribute("type", "tel")
- input.setAttribute("readonly", "readonly")
- input.setAttribute("maxlength", "8")
- input.setAttribute("aria-label", "Phone country code prefix")
- input.setAttribute("aria-readonly", "true")
+ this.setPhoneInputAttributes(input)
+ this.setPhoneInputValue(input, prefilledValue)
+ this.setupPhoneInputEventListeners(input)
StylesUtils.styleElement(input, this.theme.form.inputs.text)
return input
}
- /**
- * This function creates phone number input element
- */
- private createPhoneNumberInputElement(prefilledValue?: string) {
- const input = document.createElement("input")
+ private setPhoneInputAttributes(input: HTMLInputElement) {
+ const { key } = this.inputData
- input.setAttribute("class", "native-apm-phone-number-input")
- input.setAttribute("name", this.inputData.key)
- input.setAttribute("id", this.inputData.key)
- input.setAttribute("type", "tel")
- input.setAttribute("maxlength", "15")
+ input.className = "native-apm-phone-number-input"
+ input.name = key
+ input.id = key
+ input.type = "tel"
+ input.maxLength = 15
input.setAttribute("aria-label", "Phone number")
- input.setAttribute(
- "placeholder",
- TextUtils.getText("phoneNumberPlaceholder") || "Phone number",
- )
+ input.placeholder = TextUtils.getText("phoneNumberPlaceholder") || "Phone number"
+ }
+ private setPhoneInputValue(input: HTMLInputElement, prefilledValue?: string) {
if (prefilledValue) {
input.value = prefilledValue
}
+ }
- input.addEventListener("keypress", this.resetErrorMessage.bind(this))
- input.addEventListener("input", this.resetErrorMessage.bind(this))
-
- // Handle placeholder visibility when input changes
- input.addEventListener("input", () => {
- const prefixVisible = this.phonePrefixInputElement.style.display !== "none"
+ private setupPhoneInputEventListeners(input: HTMLInputElement) {
+ const resetErrorHandler = () => {
+ this.resetErrorMessage()
+ }
- if (prefixVisible) {
- // When prefix is visible (country selected), always hide placeholder
- input.removeAttribute("placeholder")
- }
+ input.addEventListener("keypress", resetErrorHandler)
+ input.addEventListener("input", resetErrorHandler)
+ input.addEventListener("keydown", event => {
+ this.handleKeyDown(event)
+ })
+ input.addEventListener("paste", event => {
+ this.handlePaste(event)
})
+ }
- StylesUtils.styleElement(input, this.theme.form.inputs.text)
+ private handleKeyDown(event: KeyboardEvent) {
+ const allowedKeys = [
+ "Backspace",
+ "Delete",
+ "Tab",
+ "Escape",
+ "Enter",
+ "Home",
+ "End",
+ "ArrowLeft",
+ "ArrowRight",
+ "ArrowUp",
+ "ArrowDown",
+ ]
+
+ const isAllowedKey = allowedKeys.indexOf(event.key) !== -1
+ const isValidInput = event.key === "+" || /^[0-9]$/.test(event.key)
+
+ if (!isAllowedKey && !isValidInput) {
+ event.preventDefault()
+ }
+ }
- return input
+ private handlePaste(event: ClipboardEvent) {
+ event.preventDefault()
+
+ const clipboardData =
+ event.clipboardData && event.clipboardData.getData
+ ? event.clipboardData.getData("text")
+ : ""
+
+ const filteredData = clipboardData.replace(/[^+0-9]/g, "")
+
+ const input = event.target as HTMLInputElement
+ const start = input.selectionStart || 0
+ const end = input.selectionEnd || 0
+ const currentValue = input.value
+
+ const newValue = currentValue.slice(0, start) + filteredData + currentValue.slice(end)
+ input.value = newValue
+
+ const newCursorPosition = start + filteredData.length
+
+ input.setSelectionRange(newCursorPosition, newCursorPosition)
+ input.dispatchEvent(new Event("input", { bubbles: true }))
}
- /**
- * This function creates error message element
- */
private createErrorMessageElement() {
- const errorMessageElement = document.createElement("span")
+ const errorElement = document.createElement("span")
+ const { key } = this.inputData
- errorMessageElement.setAttribute("class", "native-apm-input-error")
- errorMessageElement.setAttribute("id", `${this.inputData.key}_error`)
+ errorElement.className = "native-apm-input-error"
+ errorElement.id = key + "_error"
- StylesUtils.styleElement(errorMessageElement, this.theme.form.errors)
+ StylesUtils.styleElement(errorElement, this.theme.form.errors)
- return errorMessageElement
+ return errorElement
}
- /**
- * This function creates input label element
- */
private createInputLabel() {
const label = document.createElement("label")
+ const { key, display_name, required } = this.inputData
- label.setAttribute("class", "native-apm-input-label")
- label.setAttribute("for", this.inputData.key)
- label.setAttribute("id", `${this.inputData.key}_label`)
-
- label.textContent =
- this.inputData.display_name || TextUtils.getText("phoneNumberLabel") || "Phone Number"
+ label.className = "native-apm-input-label"
+ label.setAttribute("for", key)
+ label.id = key + "_label"
+ label.textContent = display_name || TextUtils.getText("phoneNumberLabel") || "Phone Number"
- if (this.inputData.required) {
- const requiredStar = this.createRequiredStar()
- label.appendChild(requiredStar)
+ if (required) {
+ label.appendChild(this.createRequiredStar())
}
StylesUtils.styleElement(label, this.theme.form.labels)
@@ -489,9 +683,6 @@ module ProcessOut {
return label
}
- /**
- * This function creates required star element for label in required inputs
- */
private createRequiredStar() {
const requiredStar = document.createElement("span")
requiredStar.textContent = "*"
@@ -501,21 +692,99 @@ module ProcessOut {
return requiredStar
}
- /**
- * This function resets error message
- */
private resetErrorMessage() {
- if (this.errorMessageElement.textContent.length) {
+ if (this.errorMessageElement.textContent) {
this.errorMessageElement.textContent = ""
}
}
- /**
- * This function sets error message
- */
public setErrorMessage(message?: string) {
this.errorMessageElement.textContent =
message || TextUtils.getText("invalidPhoneNumber") || "Invalid phone number"
}
+
+ private createFlagDisplayElement() {
+ const flagContainer = document.createElement("div")
+ flagContainer.className = "native-apm-flag-display"
+
+ const flagImg = document.createElement("img")
+ flagImg.className = "native-apm-flag-image"
+ flagImg.alt = "Country flag"
+
+ const defaultText = document.createElement("span")
+ defaultText.className = "native-apm-flag-default-text"
+
+ flagContainer.appendChild(flagImg)
+ flagContainer.appendChild(defaultText)
+
+ return flagContainer
+ }
+
+ private createSelectWrapper() {
+ const wrapper = document.createElement("div")
+ wrapper.className = "native-apm-select-wrapper"
+
+ // Trigger the select dropdown by simulating a click
+ wrapper.addEventListener("click", () => {
+ this.countrySelectElement.focus()
+
+ if (this.countrySelectElement.click) {
+ this.countrySelectElement.click()
+ }
+ })
+
+ return wrapper
+ }
+
+ private createChevronElement() {
+ const chevron = document.createElement("div")
+
+ chevron.className = "native-apm-select-chevron"
+
+ return chevron
+ }
+
+ private updateFlagDisplay(countryCode: string | null) {
+ const flagImg = this.flagDisplayElement.querySelector(
+ ".native-apm-flag-image",
+ ) as HTMLImageElement
+
+ const defaultText = this.flagDisplayElement.querySelector(
+ ".native-apm-flag-default-text",
+ ) as HTMLElement
+
+ if (countryCode) {
+ this.showCountryFlag(flagImg, defaultText, countryCode)
+ } else {
+ this.showDefaultFlag(flagImg, defaultText)
+ }
+ }
+
+ private showCountryFlag(
+ flagImg: HTMLImageElement,
+ defaultText: HTMLElement,
+ countryCode: string,
+ ) {
+ flagImg.src = this.processOut.endpoint(
+ "js",
+ "images/countries/" + countryCode.toLowerCase() + ".png",
+ )
+
+ StylesUtils.styleElement(flagImg, this.theme.form.inputs.phoneWithCountry.flagImageVisible)
+
+ StylesUtils.styleElement(
+ defaultText,
+ this.theme.form.inputs.phoneWithCountry.flagDefaultTextHidden,
+ )
+ }
+
+ private showDefaultFlag(flagImg: HTMLImageElement, defaultText: HTMLElement) {
+ StylesUtils.styleElement(flagImg, this.theme.form.inputs.phoneWithCountry.flagImageHidden)
+
+ StylesUtils.styleElement(
+ defaultText,
+ this.theme.form.inputs.phoneWithCountry.flagDefaultTextVisible,
+ )
+ }
}
}
diff --git a/src/nativeapm/nativeapm.ts b/src/nativeapm/nativeapm.ts
index 8b86862e..4c65ad2a 100644
--- a/src/nativeapm/nativeapm.ts
+++ b/src/nativeapm/nativeapm.ts
@@ -112,6 +112,7 @@ module ProcessOut {
this.theme = new NativeApmThemeConfig()
this.loadMarkdownLibrary()
this.loadQrCodesLibrary()
+ this.loadPhoneNumberInputLibrary()
}
/**
@@ -178,6 +179,7 @@ module ProcessOut {
this.gatewayConfiguration.native_apm.parameters.length
) {
const nativeApmFormView = new NativeApmFormView(
+ this.processOutInstance,
this.gatewayConfiguration.native_apm,
this.proceedPayment.bind(this),
this.theme,
@@ -286,6 +288,7 @@ module ProcessOut {
if (data.native_apm && data.native_apm.state === "CUSTOMER_INPUT") {
const customerInputView = new NativeApmFormView(
+ this.processOutInstance,
{
gateway: this.gatewayConfiguration.native_apm.gateway,
parameters: data.native_apm.parameterDefinitions,
@@ -515,5 +518,13 @@ module ProcessOut {
qrCodeScript.src = this.processOutInstance.endpoint("js", "/js/libraries/qrcode.min.js")
document.head.appendChild(qrCodeScript)
}
+ private loadPhoneNumberInputLibrary() {
+ const phoneInputScript = document.createElement("script")
+ phoneInputScript.src = this.processOutInstance.endpoint(
+ "js",
+ "/js/libraries/libphonenumber-js.min.js",
+ )
+ document.head.appendChild(phoneInputScript)
+ }
}
}
diff --git a/src/nativeapm/views/form.ts b/src/nativeapm/views/form.ts
index b808247b..5f70cba6 100644
--- a/src/nativeapm/views/form.ts
+++ b/src/nativeapm/views/form.ts
@@ -1,13 +1,13 @@
///
type NativeApmFormData = {
- gateway: any;
- parameters: NativeApmInputData[];
+ gateway: any
+ parameters: NativeApmInputData[]
invoice: {
- amount: string;
- currency_code: string;
- };
-};
+ amount: string
+ currency_code: string
+ }
+}
/**
* ProcessOut module/namespace
@@ -21,101 +21,109 @@ module ProcessOut {
* Native APM form element
* @type {HTMLFormElement}
*/
- formElement: HTMLFormElement;
+ formElement: HTMLFormElement
/**
* Native APM submit button element
* @type {NativeApmButton}
*/
- submitButton: NativeApmButton;
+ submitButton: NativeApmButton
/**
* Native APM inputs of the form
* @type {NativeApmInput[]}
*/
- formInputs: NativeApmInput[];
+ formInputs: NativeApmInput[]
/**
* Theme of the Native APM widget
* @type {NativeApmThemeConfigType}
*/
- theme: NativeApmThemeConfigType;
+ theme: NativeApmThemeConfigType
/**
* Handler fuctnion which fires on form submit
* @type {Function}
*/
- onSubmitHandler: Function;
+ onSubmitHandler: Function
/**
* Form data like configuration of inputs etc
* @type {NativeApmFormData}
*/
- formData: NativeApmFormData;
+ formData: NativeApmFormData
/**
* Prefilled data of the user
* @type {any}
*/
- prefilledData: PrefilledData;
+ prefilledData: PrefilledData
+
+ /**
+ * ProcessOut instance
+ * @type {ProcessOut}
+ */
+ processOut: ProcessOut
/**
* Native APM Form constructor
*/
constructor(
+ processOut: ProcessOut,
formData: NativeApmFormData,
onSubmitHandler: Function,
theme: NativeApmThemeConfigType,
- prefilledData: PrefilledData
+ prefilledData: PrefilledData,
) {
if (!formData) {
throw new Exception(
"default",
- "Form data is not defined. You must provide valid form data to create the form view."
- );
+ "Form data is not defined. You must provide valid form data to create the form view.",
+ )
}
- this.theme = theme;
- this.prefilledData = prefilledData;
- this.formData = formData;
- this.onSubmitHandler = onSubmitHandler;
- this.formInputs = this.createFormInputs();
- this.submitButton = this.createSubmitButton();
- this.formElement = this.createFormElement();
+ this.theme = theme
+ this.processOut = processOut
+ this.prefilledData = prefilledData
+ this.formData = formData
+ this.onSubmitHandler = onSubmitHandler
+ this.formInputs = this.createFormInputs()
+ this.submitButton = this.createSubmitButton()
+ this.formElement = this.createFormElement()
}
/**
* This function returns the form view element
*/
public getViewElement() {
- return this.formElement;
+ return this.formElement
}
/**
* This function creates the form element
*/
private createFormElement() {
- const form = document.createElement("form");
- const inputsWrapper = document.createElement("div");
- const paymentProviderImage = this.createPaymentProviderImage();
+ const form = document.createElement("form")
+ const inputsWrapper = document.createElement("div")
+ const paymentProviderImage = this.createPaymentProviderImage()
- form.setAttribute("class", "native-apm-form");
- inputsWrapper.setAttribute("class", "native-apm-form-inputs-wrapper");
+ form.setAttribute("class", "native-apm-form")
+ inputsWrapper.setAttribute("class", "native-apm-form-inputs-wrapper")
- StylesUtils.styleElement(form, this.theme.form);
- this.styleInputsWrapper(inputsWrapper);
+ StylesUtils.styleElement(form, this.theme.form)
+ this.styleInputsWrapper(inputsWrapper)
- this.formInputs.forEach((input) => {
- inputsWrapper.appendChild(input.getInputElement());
- });
+ this.formInputs.forEach(input => {
+ inputsWrapper.appendChild(input.getInputElement())
+ })
- form.appendChild(paymentProviderImage);
- form.appendChild(inputsWrapper);
- form.appendChild(this.submitButton.getButtonElement());
+ form.appendChild(paymentProviderImage)
+ form.appendChild(inputsWrapper)
+ form.appendChild(this.submitButton.getButtonElement())
- form.addEventListener("submit", this.submitForm.bind(this));
+ form.addEventListener("submit", this.submitForm.bind(this))
- return form;
+ return form
}
/**
@@ -123,9 +131,9 @@ module ProcessOut {
*/
private createFormInputs() {
return this.formData.parameters.map((parameter: any) => {
- const prefilledValue = this.prefilledData[parameter.key];
- return new NativeApmInput(parameter, this.theme, prefilledValue);
- });
+ const prefilledValue = this.prefilledData[parameter.key]
+ return new NativeApmInput(this.processOut, parameter, this.theme, prefilledValue)
+ })
}
/**
@@ -136,46 +144,43 @@ module ProcessOut {
${TextUtils.getText("submitButtonText")}
${this.formData.invoice.amount}
${this.formData.invoice.currency_code}
- `;
+ `
- return new NativeApmButton(buttonText, this.theme);
+ return new NativeApmButton(buttonText, this.theme)
}
/**
* This function creates the payment provider image element
*/
private createPaymentProviderImage() {
- const merchantImg = document.createElement("img");
+ const merchantImg = document.createElement("img")
- merchantImg.setAttribute("class", "native-apm-payment-provider-logo");
- merchantImg.setAttribute("src", this.formData.gateway.logo_url);
+ merchantImg.setAttribute("class", "native-apm-payment-provider-logo")
+ merchantImg.setAttribute("src", this.formData.gateway.logo_url)
- StylesUtils.styleElement(merchantImg, this.theme.logo);
+ StylesUtils.styleElement(merchantImg, this.theme.logo)
- return merchantImg;
+ return merchantImg
}
/**
* This function fires when the form is submitted
*/
private submitForm(event: Event) {
- event.preventDefault();
+ event.preventDefault()
- const isFormValid = this.formInputs.every((input) =>
- input.getInputInstance().validate()
- );
+ const isFormValid = this.formInputs.every(input => input.getInputInstance().validate())
if (isFormValid) {
- const values = this.getValuesFromInputs();
+ const values = this.getValuesFromInputs()
- this.submitButton.setLoadingState();
+ this.submitButton.setLoadingState()
this.onSubmitHandler(
values,
() => this.submitButton.resetLoadingState(),
- (invalid_fields: Array) =>
- this.handleApiValidationErrors(invalid_fields)
- );
+ (invalid_fields: Array) => this.handleApiValidationErrors(invalid_fields),
+ )
}
}
@@ -183,23 +188,18 @@ module ProcessOut {
* This function sets error message for invalid inputs after API validation
*/
private handleApiValidationErrors(invalid_fields) {
- return invalid_fields.forEach((invalidField) =>
- this.markInputAsNotValid(invalidField)
- );
+ return invalid_fields.forEach(invalidField => this.markInputAsNotValid(invalidField))
}
/**
* This function fires when the form is submitted
*/
- private markInputAsNotValid(invalidInputData: {
- name: string;
- message: string;
- }) {
- this.formInputs.forEach((input) => {
+ private markInputAsNotValid(invalidInputData: { name: string; message: string }) {
+ this.formInputs.forEach(input => {
if (input.getInputInstance().inputData.key === invalidInputData.name) {
- input.getInputInstance().setErrorMessage();
+ input.getInputInstance().setErrorMessage()
}
- });
+ })
}
/**
@@ -207,8 +207,8 @@ module ProcessOut {
*/
private getValuesFromInputs() {
return this.formInputs.reduce((acc, input) => {
- return { ...acc, ...input.getInputValue() };
- }, {});
+ return { ...acc, ...input.getInputValue() }
+ }, {})
}
/**
@@ -222,9 +222,9 @@ module ProcessOut {
StylesUtils.styleElement(inputsWrapper, {
...this.theme.form.inputsWrapper,
width: "auto",
- });
+ })
} else {
- StylesUtils.styleElement(inputsWrapper, this.theme.form.inputsWrapper);
+ StylesUtils.styleElement(inputsWrapper, this.theme.form.inputsWrapper)
}
}
}