@verbb/formie-browser 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -22
- package/dist/chunks/address-country-Rx6GQTND.js +68 -0
- package/dist/chunks/address-finder-yXU5yMjG.js +30 -0
- package/dist/chunks/address-state-BQ0xGV9n.js +344 -0
- package/dist/chunks/api-CmwLRq_n.js +780 -0
- package/dist/chunks/api-DMK8NSUI.js +407 -0
- package/dist/chunks/api-sPqGbOww.js +125 -0
- package/dist/chunks/async-nPFRNQ06.js +25 -0
- package/dist/chunks/bpoint-DSjqRv63.js +24 -0
- package/dist/chunks/calculations-CeDgB9jt.js +299 -0
- package/dist/chunks/captcha-eu-CWJb64_j.js +31 -0
- package/dist/chunks/checkbox-radio-DQ0H67Tj.js +125 -0
- package/dist/chunks/chunk-3b4jIN3o.js +13 -0
- package/dist/chunks/combobox-D-2b42wn.js +1954 -0
- package/dist/chunks/conditions-DjZOtHfS.js +412 -0
- package/dist/chunks/constants-DVcJAvc5.js +38 -0
- package/dist/chunks/country-from-ip-BwxKaBuJ.js +26 -0
- package/dist/chunks/custom-google-maps-BsaSirEZ.js +194 -0
- package/dist/chunks/custom-link-CJ1-FjEM.js +46 -0
- package/dist/chunks/custom-maps-DYW_pBid.js +161 -0
- package/dist/chunks/date-picker-5CA_-zGb.js +5311 -0
- package/dist/chunks/debug-BV0DvdHx.js +41 -0
- package/dist/chunks/dist-BQ4a9hmy.js +2659 -0
- package/dist/chunks/event-names-BCI2FLD8.js +51 -0
- package/dist/chunks/eway-D3ctTbEk.js +70 -0
- package/dist/chunks/field-references.keys-58ZSTrCW.js +21 -0
- package/dist/chunks/field-references.resolver-CHwn0G0L.js +145 -0
- package/dist/chunks/file-upload-D49m8-DR.js +287 -0
- package/dist/chunks/friendly-captcha-v1-ByXzENYZ.js +31 -0
- package/dist/chunks/friendly-captcha-v2-BPdlmhQu.js +36 -0
- package/dist/chunks/friendly-challenge-8uPatWqk.js +832 -0
- package/dist/chunks/go-cardless-BwASSYSK.js +23 -0
- package/dist/chunks/google-address-OhKilPWE.js +161 -0
- package/dist/chunks/hcaptcha-CdCd9oEi.js +54 -0
- package/dist/chunks/hidden-D7_Ch-QN.js +26 -0
- package/dist/chunks/http-D-JExro7.js +27 -0
- package/dist/chunks/i18n-BY1ds1BL.js +121 -0
- package/dist/chunks/leaflet-src-j-lHNmhR.js +4585 -0
- package/dist/chunks/loqate-Cp2zvwpE.js +76 -0
- package/dist/chunks/mollie-COG-ko2D.js +23 -0
- package/dist/chunks/moneris-CMH8PlRc.js +117 -0
- package/dist/chunks/opayo-D2jsR5vh.js +113 -0
- package/dist/chunks/paddle-DxF88RfC.js +55 -0
- package/dist/chunks/paypal-b5pnwrA2.js +95 -0
- package/dist/chunks/payway-BJYu9DIR.js +55 -0
- package/dist/chunks/phone-country-D3ayr60L.js +2645 -0
- package/dist/chunks/place-kit-BGHklIZ-.js +48 -0
- package/dist/chunks/placekit-autocomplete.esm-DWVabQlq.js +1236 -0
- package/dist/chunks/recaptcha-enterprise-BSwi8IVX.js +65 -0
- package/dist/chunks/recaptcha-shared-B4zAescW.js +15 -0
- package/dist/chunks/recaptcha-v2-checkbox-CnDQmsKa.js +40 -0
- package/dist/chunks/recaptcha-v2-invisible-Cz5uIlp-.js +53 -0
- package/dist/chunks/recaptcha-v3-BtbbP111.js +27 -0
- package/dist/chunks/repeater-B5leVxZU.js +102 -0
- package/dist/chunks/rich-text-D0sb8bmX.js +349 -0
- package/dist/chunks/scripts-6CYSUGKQ.js +24 -0
- package/dist/chunks/sdk-BhHAYgga.js +1397 -0
- package/dist/chunks/shared-Bx9s0i0P.js +100 -0
- package/dist/chunks/signature-xz4QO7CR.js +536 -0
- package/dist/chunks/snaptcha-vz1RnZ15.js +8 -0
- package/dist/chunks/square-BP8RWAKB.js +40 -0
- package/dist/chunks/stripe-MihN63uC.js +184 -0
- package/dist/chunks/styles-BfoIZwJp.js +19 -0
- package/dist/chunks/summary-D3AjxpYN.js +114 -0
- package/dist/chunks/table-C-lkQN6I.js +78 -0
- package/dist/chunks/text-limit-DbgEFHLO.js +125 -0
- package/dist/chunks/theme-classes-Tv7q7ToE.js +44 -0
- package/dist/chunks/turnstile-D_Pi1CKM.js +58 -0
- package/dist/chunks/upload-manager-BpqWSHmU.js +2558 -0
- package/dist/chunks/utils-Vqp8i_RM.js +49401 -0
- package/dist/css/formie-theme.css +2 -0
- package/dist/css/theme/_buttons.css +16 -0
- package/dist/css/theme/_tokens.css +2 -0
- package/dist/css/theme/fields/_address-state.css +94 -0
- package/dist/css/theme/fields/_combobox.css +246 -0
- package/dist/css/theme/fields/_select.css +13 -0
- package/dist/css/theme/fields/_signature.css +19 -0
- package/dist/css/theme/fields/_upload-manager.css +279 -0
- package/dist/index.js +2448 -39
- package/dist/js/core/create-formie-client.d.ts.map +1 -1
- package/dist/js/core/submit-result-ui.d.ts.map +1 -1
- package/dist/js/core/validation-focus.d.ts +4 -0
- package/dist/js/core/validation-focus.d.ts.map +1 -0
- package/dist/js/modules/address/api.d.ts +1 -0
- package/dist/js/modules/address/api.d.ts.map +1 -1
- package/dist/js/modules/address/constants.d.ts +22 -1
- package/dist/js/modules/address/constants.d.ts.map +1 -1
- package/dist/js/modules/address/host.d.ts +2 -2
- package/dist/js/modules/address/host.d.ts.map +1 -1
- package/dist/js/modules/address/loqate.d.ts.map +1 -1
- package/dist/js/modules/fields/address-country.d.ts +3 -0
- package/dist/js/modules/fields/address-country.d.ts.map +1 -0
- package/dist/js/modules/fields/address-state.d.ts +3 -0
- package/dist/js/modules/fields/address-state.d.ts.map +1 -0
- package/dist/js/modules/fields/calculations.d.ts.map +1 -1
- package/dist/js/modules/fields/combobox.d.ts +17 -0
- package/dist/js/modules/fields/combobox.d.ts.map +1 -0
- package/dist/js/modules/fields/custom-google-maps.d.ts +64 -0
- package/dist/js/modules/fields/custom-google-maps.d.ts.map +1 -0
- package/dist/js/modules/fields/custom-link.d.ts +3 -0
- package/dist/js/modules/fields/custom-link.d.ts.map +1 -0
- package/dist/js/modules/fields/custom-maps.d.ts +3 -0
- package/dist/js/modules/fields/custom-maps.d.ts.map +1 -0
- package/dist/js/modules/fields/date-picker.d.ts.map +1 -1
- package/dist/js/modules/fields/index.d.ts.map +1 -1
- package/dist/js/modules/fields/phone-country.d.ts.map +1 -1
- package/dist/js/modules/fields/signature.d.ts.map +1 -1
- package/dist/js/modules/fields/upload-manager.d.ts +3 -0
- package/dist/js/modules/fields/upload-manager.d.ts.map +1 -0
- package/dist/js/submit/pipeline.d.ts.map +1 -1
- package/dist/js/utils/country-from-ip.d.ts +7 -0
- package/dist/js/utils/country-from-ip.d.ts.map +1 -0
- package/dist/js/utils/field-references.d.ts +1 -0
- package/dist/js/utils/field-references.d.ts.map +1 -1
- package/dist/js/utils/field-references.row-scope.d.ts +11 -0
- package/dist/js/utils/field-references.row-scope.d.ts.map +1 -0
- package/package.json +8 -3
- package/src/css/formie-theme.css +2 -0
- package/src/css/theme/_buttons.css +16 -0
- package/src/css/theme/_tokens.css +2 -0
- package/src/css/theme/fields/_address-state.css +94 -0
- package/src/css/theme/fields/_combobox.css +246 -0
- package/src/css/theme/fields/_select.css +13 -0
- package/src/css/theme/fields/_signature.css +19 -0
- package/src/css/theme/fields/_upload-manager.css +279 -0
- package/src/js/core/create-formie-client.ts +27 -0
- package/src/js/core/submit-result-ui.ts +4 -4
- package/src/js/core/validation-focus.ts +117 -0
- package/src/js/modules/address/api.ts +7 -0
- package/src/js/modules/address/constants.ts +82 -2
- package/src/js/modules/address/host.ts +16 -6
- package/src/js/modules/address/loqate.ts +3 -17
- package/src/js/modules/fields/address-country.ts +156 -0
- package/src/js/modules/fields/address-state.ts +946 -0
- package/src/js/modules/fields/calculations.ts +32 -0
- package/src/js/modules/fields/combobox.ts +188 -0
- package/src/js/modules/fields/custom-google-maps.ts +416 -0
- package/src/js/modules/fields/custom-link.ts +95 -0
- package/src/js/modules/fields/custom-maps.ts +339 -0
- package/src/js/modules/fields/date-picker.ts +96 -3
- package/src/js/modules/fields/index.ts +7 -0
- package/src/js/modules/fields/phone-country.ts +6 -0
- package/src/js/modules/fields/signature.ts +199 -15
- package/src/js/modules/fields/upload-manager.ts +1277 -0
- package/src/js/submit/pipeline.ts +11 -1
- package/src/js/utils/country-from-ip.ts +54 -0
- package/src/js/utils/field-references.row-scope.ts +303 -0
- package/src/js/utils/field-references.ts +1 -0
- package/src/vendor.d.ts +1 -1
- package/dist/chunks/address-finder-BU4roxT6.js +0 -37
- package/dist/chunks/bpoint-uNg5j6rt.js +0 -26
- package/dist/chunks/calculations-C9t6Zop9.js +0 -147
- package/dist/chunks/captcha-eu-BAPzHNts.js +0 -34
- package/dist/chunks/checkbox-radio-CHeLNVIK.js +0 -141
- package/dist/chunks/conditions-DKxVCsjD.js +0 -459
- package/dist/chunks/date-picker-B3iQ797Z.js +0 -4994
- package/dist/chunks/eway-DCielIML.js +0 -74
- package/dist/chunks/file-upload-DP2TOUYV.js +0 -316
- package/dist/chunks/friendly-captcha-v1-DdlcGiFY.js +0 -33
- package/dist/chunks/friendly-captcha-v2-76w-YnFm.js +0 -38
- package/dist/chunks/go-cardless-rwCkzbNK.js +0 -24
- package/dist/chunks/google-address-BD8IZ86V.js +0 -181
- package/dist/chunks/hcaptcha-BNNTL6uf.js +0 -56
- package/dist/chunks/hidden-CW6Rcf1N.js +0 -31
- package/dist/chunks/index-BEsfee-D.js +0 -1839
- package/dist/chunks/index-BP7Unm5Y.js +0 -863
- package/dist/chunks/index-D6npYxvr.js +0 -4423
- package/dist/chunks/loqate-B-ZNS65B.js +0 -55
- package/dist/chunks/mollie-BDAZ-1X4.js +0 -24
- package/dist/chunks/moneris-B1b0uEtY.js +0 -101
- package/dist/chunks/opayo-CC5hraHo.js +0 -122
- package/dist/chunks/paddle-B4GmrDsq.js +0 -60
- package/dist/chunks/paypal-BFS6XsIS.js +0 -100
- package/dist/chunks/payway-DekoJead.js +0 -56
- package/dist/chunks/phone-country-Cm5YUeXI.js +0 -3765
- package/dist/chunks/place-kit-DizZWV_e.js +0 -42
- package/dist/chunks/placekit-autocomplete.esm-LAwfbPZl.js +0 -1299
- package/dist/chunks/recaptcha-enterprise-BbOvO7JZ.js +0 -71
- package/dist/chunks/recaptcha-shared-Dwg1eVFS.js +0 -15
- package/dist/chunks/recaptcha-v2-checkbox-SY3ti5iQ.js +0 -41
- package/dist/chunks/recaptcha-v2-invisible-DSmQnn6c.js +0 -59
- package/dist/chunks/recaptcha-v3-DNh7YCnR.js +0 -28
- package/dist/chunks/repeater-DM0vuFNQ.js +0 -124
- package/dist/chunks/rich-text-C8aGYp70.js +0 -304
- package/dist/chunks/scripts-CKTFtxPO.js +0 -36
- package/dist/chunks/sdk-3SicTWby.js +0 -1559
- package/dist/chunks/shared--BU5CFoL.js +0 -80
- package/dist/chunks/signature-pFhZfH7d.js +0 -550
- package/dist/chunks/snaptcha-VuOUOaLj.js +0 -8
- package/dist/chunks/square-BR9pvToy.js +0 -46
- package/dist/chunks/stripe-OHUwoV9g.js +0 -202
- package/dist/chunks/styles-C3aqgtek.js +0 -21
- package/dist/chunks/summary-Da7Ktg3N.js +0 -126
- package/dist/chunks/table-C0PmR9bI.js +0 -95
- package/dist/chunks/text-limit-mLEwZDIa.js +0 -144
- package/dist/chunks/turnstile-9tM7q_LK.js +0 -59
- package/dist/chunks/utils-BntI3W4S.js +0 -6149
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { addThemeClasses } from '#theme/theme-classes';
|
|
2
|
+
|
|
3
|
+
function appendDescribedBy(input: HTMLElement, describedById: string): void {
|
|
4
|
+
const current = (input.getAttribute('aria-describedby') || '').trim();
|
|
5
|
+
const items = current ? current.split(/\s+/) : [];
|
|
6
|
+
|
|
7
|
+
if (!items.includes(describedById)) {
|
|
8
|
+
items.push(describedById);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
input.setAttribute('aria-describedby', items.join(' ').trim());
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getFirstErroredField(form: HTMLFormElement): HTMLElement | null {
|
|
15
|
+
const fields = Array.from(form.querySelectorAll('[data-formie-field-handle]')) as HTMLElement[];
|
|
16
|
+
|
|
17
|
+
return fields.find((field) => {
|
|
18
|
+
if (field.getAttribute('data-formie-field-has-error') === 'true') {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return field.querySelector('[data-formie-field-error]') !== null;
|
|
23
|
+
}) || null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getFieldFocusTarget(field: HTMLElement): HTMLElement | null {
|
|
27
|
+
const invalidControl = field.querySelector('[aria-invalid="true"]') as HTMLElement | null;
|
|
28
|
+
|
|
29
|
+
if (invalidControl) {
|
|
30
|
+
return invalidControl;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return field.querySelector(
|
|
34
|
+
'input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled])',
|
|
35
|
+
) as HTMLElement | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getFormLevelErrorTarget(form: HTMLFormElement): HTMLElement | null {
|
|
39
|
+
return form.querySelector(
|
|
40
|
+
'[data-formie-message-error], [data-formie-error-container], [data-formie-errors]',
|
|
41
|
+
) as HTMLElement | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function enhanceServerRenderedFieldErrors(form: HTMLFormElement): void {
|
|
45
|
+
form.querySelectorAll('[data-formie-field-handle]').forEach((fieldNode) => {
|
|
46
|
+
const field = fieldNode as HTMLElement;
|
|
47
|
+
const hasServerError = field.getAttribute('data-formie-field-has-error') === 'true'
|
|
48
|
+
|| field.querySelector('[data-formie-field-error]') !== null;
|
|
49
|
+
|
|
50
|
+
if (!hasServerError) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
field.setAttribute('data-formie-field-has-error', 'true');
|
|
55
|
+
addThemeClasses(field, form, 'fieldLayoutError');
|
|
56
|
+
|
|
57
|
+
const errorContainer = field.querySelector('[data-formie-field-errors]') as HTMLElement | null;
|
|
58
|
+
const errorContainerId = errorContainer?.id || '';
|
|
59
|
+
const primaryError = field.querySelector('[data-formie-field-error]') as HTMLElement | null;
|
|
60
|
+
const primaryErrorId = primaryError?.id || '';
|
|
61
|
+
|
|
62
|
+
field.querySelectorAll('input, select, textarea').forEach((input) => {
|
|
63
|
+
const element = input as HTMLElement;
|
|
64
|
+
element.setAttribute('aria-invalid', 'true');
|
|
65
|
+
addThemeClasses(element, form, 'fieldControlError');
|
|
66
|
+
element.setAttribute('data-formie-input-has-error', 'true');
|
|
67
|
+
|
|
68
|
+
if (errorContainerId) {
|
|
69
|
+
appendDescribedBy(element, errorContainerId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (primaryErrorId) {
|
|
73
|
+
element.setAttribute('aria-errormessage', primaryErrorId);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function hasServerRenderedValidationErrors(form: HTMLFormElement): boolean {
|
|
80
|
+
return !!getFirstErroredField(form) || !!getFormLevelErrorTarget(form);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function focusFirstValidationError(form: HTMLFormElement): boolean {
|
|
84
|
+
const field = getFirstErroredField(form);
|
|
85
|
+
|
|
86
|
+
if (field) {
|
|
87
|
+
const target = getFieldFocusTarget(field);
|
|
88
|
+
|
|
89
|
+
if (target) {
|
|
90
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
91
|
+
|
|
92
|
+
if (typeof target.focus === 'function') {
|
|
93
|
+
try {
|
|
94
|
+
target.focus({ preventScroll: true });
|
|
95
|
+
} catch {
|
|
96
|
+
target.focus();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
field.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
104
|
+
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const formError = getFormLevelErrorTarget(form);
|
|
109
|
+
|
|
110
|
+
if (!formError) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
formError.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
115
|
+
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
@@ -8,6 +8,13 @@ import type {
|
|
|
8
8
|
AddressModuleOptions as AddressModuleManifestOptions,
|
|
9
9
|
} from '#modules/address/host';
|
|
10
10
|
|
|
11
|
+
export {
|
|
12
|
+
ADDRESS_LEGACY_SELECTORS,
|
|
13
|
+
ADDRESS_SELECTORS,
|
|
14
|
+
findAddressFieldInput,
|
|
15
|
+
type AddressFieldInputKey,
|
|
16
|
+
} from '#modules/address/constants';
|
|
17
|
+
|
|
11
18
|
// This file is the intended authoring surface for address providers.
|
|
12
19
|
// Built-in providers import from here so third-party authors can follow the same patterns.
|
|
13
20
|
export const defineAddressModule = createManagedAddressModule;
|
|
@@ -2,7 +2,21 @@
|
|
|
2
2
|
export const DEFAULT_AUTOCOMPLETE_SELECTOR = '[data-formie-address-autocomplete-input]';
|
|
3
3
|
export const ADDRESS_LOCATION_SELECTOR = '[data-formie-address-location]';
|
|
4
4
|
|
|
5
|
-
/**
|
|
5
|
+
/** Keys for address sub-field inputs targeted by provider modules. */
|
|
6
|
+
export type AddressFieldInputKey =
|
|
7
|
+
| 'autoComplete'
|
|
8
|
+
| 'address1'
|
|
9
|
+
| 'address2'
|
|
10
|
+
| 'address3'
|
|
11
|
+
| 'city'
|
|
12
|
+
| 'state'
|
|
13
|
+
| 'zip'
|
|
14
|
+
| 'country';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Canonical address sub-field selectors (PHP output, UI reference, provider modules).
|
|
18
|
+
* All address-owned runtime hooks use the `data-formie-address-*` namespace.
|
|
19
|
+
*/
|
|
6
20
|
export const ADDRESS_SELECTORS = {
|
|
7
21
|
autoComplete: '[data-formie-address-autocomplete-input]',
|
|
8
22
|
address1: '[data-formie-address-line1-input]',
|
|
@@ -12,4 +26,70 @@ export const ADDRESS_SELECTORS = {
|
|
|
12
26
|
state: '[data-formie-address-state-input]',
|
|
13
27
|
zip: '[data-formie-address-zip-input]',
|
|
14
28
|
country: '[data-formie-address-country-input]',
|
|
15
|
-
} as const
|
|
29
|
+
} as const satisfies Record<AddressFieldInputKey, string>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Legacy bare hooks still present on forms saved before the selector normalization.
|
|
33
|
+
* `findAddressFieldInput()` checks canonical selectors first, then these.
|
|
34
|
+
*/
|
|
35
|
+
export const ADDRESS_LEGACY_SELECTORS = {
|
|
36
|
+
autoComplete: '[data-formie-address-autocomplete-input]',
|
|
37
|
+
address1: '[data-address1]',
|
|
38
|
+
address2: '[data-address2]',
|
|
39
|
+
address3: '[data-address3]',
|
|
40
|
+
city: '[data-city]',
|
|
41
|
+
state: '[data-state]',
|
|
42
|
+
zip: '[data-zip]',
|
|
43
|
+
country: '[data-country]',
|
|
44
|
+
} as const satisfies Record<AddressFieldInputKey, string>;
|
|
45
|
+
|
|
46
|
+
const ADDRESS_FIELD_INPUT_SELECTOR_ORDER: Record<AddressFieldInputKey, readonly string[]> = {
|
|
47
|
+
autoComplete: [
|
|
48
|
+
ADDRESS_SELECTORS.autoComplete,
|
|
49
|
+
ADDRESS_LEGACY_SELECTORS.autoComplete,
|
|
50
|
+
],
|
|
51
|
+
address1: [
|
|
52
|
+
ADDRESS_SELECTORS.address1,
|
|
53
|
+
ADDRESS_LEGACY_SELECTORS.address1,
|
|
54
|
+
],
|
|
55
|
+
address2: [
|
|
56
|
+
ADDRESS_SELECTORS.address2,
|
|
57
|
+
ADDRESS_LEGACY_SELECTORS.address2,
|
|
58
|
+
],
|
|
59
|
+
address3: [
|
|
60
|
+
ADDRESS_SELECTORS.address3,
|
|
61
|
+
ADDRESS_LEGACY_SELECTORS.address3,
|
|
62
|
+
],
|
|
63
|
+
city: [
|
|
64
|
+
ADDRESS_SELECTORS.city,
|
|
65
|
+
ADDRESS_LEGACY_SELECTORS.city,
|
|
66
|
+
],
|
|
67
|
+
state: [
|
|
68
|
+
ADDRESS_SELECTORS.state,
|
|
69
|
+
ADDRESS_LEGACY_SELECTORS.state,
|
|
70
|
+
],
|
|
71
|
+
zip: [
|
|
72
|
+
ADDRESS_SELECTORS.zip,
|
|
73
|
+
ADDRESS_LEGACY_SELECTORS.zip,
|
|
74
|
+
],
|
|
75
|
+
country: [
|
|
76
|
+
ADDRESS_SELECTORS.country,
|
|
77
|
+
ADDRESS_LEGACY_SELECTORS.country,
|
|
78
|
+
],
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Resolve an address sub-field input against canonical and legacy selectors. */
|
|
82
|
+
export function findAddressFieldInput(
|
|
83
|
+
scope: ParentNode,
|
|
84
|
+
key: AddressFieldInputKey,
|
|
85
|
+
): HTMLInputElement | HTMLSelectElement | null {
|
|
86
|
+
for (const selector of ADDRESS_FIELD_INPUT_SELECTOR_ORDER[key]) {
|
|
87
|
+
const candidate = scope.querySelector(selector);
|
|
88
|
+
|
|
89
|
+
if (candidate instanceof HTMLInputElement || candidate instanceof HTMLSelectElement) {
|
|
90
|
+
return candidate;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { ModuleSetupContext } from '#contracts/modules';
|
|
2
2
|
import {
|
|
3
3
|
ADDRESS_LOCATION_SELECTOR,
|
|
4
|
-
ADDRESS_SELECTORS,
|
|
5
4
|
DEFAULT_AUTOCOMPLETE_SELECTOR,
|
|
5
|
+
findAddressFieldInput,
|
|
6
|
+
type AddressFieldInputKey,
|
|
6
7
|
} from '#modules/address/constants';
|
|
7
8
|
|
|
8
9
|
type Cleanup = () => void;
|
|
@@ -24,7 +25,7 @@ export type AddressHostServices = {
|
|
|
24
25
|
form: HTMLFormElement | null;
|
|
25
26
|
input: {
|
|
26
27
|
getAutocomplete: () => HTMLInputElement | null;
|
|
27
|
-
setValue: (selector:
|
|
28
|
+
setValue: (selector: AddressFieldInputKey, value: string, fallback?: string) => void;
|
|
28
29
|
};
|
|
29
30
|
location: {
|
|
30
31
|
getButton: () => HTMLElement | null;
|
|
@@ -90,12 +91,21 @@ export function createAddressHostServices(
|
|
|
90
91
|
return field.querySelector(autocompleteSelector) as HTMLInputElement | null;
|
|
91
92
|
},
|
|
92
93
|
setValue: (selectorKey, value, fallback) => {
|
|
93
|
-
const
|
|
94
|
-
const el = field.querySelector(selector) as HTMLInputElement | null;
|
|
94
|
+
const el = findAddressFieldInput(field, selectorKey);
|
|
95
95
|
|
|
96
|
-
if (el) {
|
|
97
|
-
|
|
96
|
+
if (!el) {
|
|
97
|
+
return;
|
|
98
98
|
}
|
|
99
|
+
|
|
100
|
+
const nextValue = value || fallback || '';
|
|
101
|
+
|
|
102
|
+
if (el.value === nextValue) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
el.value = nextValue;
|
|
107
|
+
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
108
|
+
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
99
109
|
},
|
|
100
110
|
},
|
|
101
111
|
location: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { defineAddressModule } from '#modules/address/api';
|
|
2
|
-
import {
|
|
2
|
+
import { findAddressFieldInput, type AddressFieldInputKey } from '#modules/address/constants';
|
|
3
3
|
import { loadScriptAndEnsureGlobal } from '#utils/scripts';
|
|
4
4
|
|
|
5
5
|
type PcaFieldMode = {
|
|
@@ -63,27 +63,13 @@ export const loqateModule = defineAddressModule<
|
|
|
63
63
|
throw new Error('Loqate API key is required');
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
const handleToSelector: Record<string, string> = {
|
|
67
|
-
autoComplete: ADDRESS_SELECTORS.autoComplete,
|
|
68
|
-
address1: ADDRESS_SELECTORS.address1,
|
|
69
|
-
address2: ADDRESS_SELECTORS.address2,
|
|
70
|
-
address3: ADDRESS_SELECTORS.address3,
|
|
71
|
-
city: ADDRESS_SELECTORS.city,
|
|
72
|
-
state: ADDRESS_SELECTORS.state,
|
|
73
|
-
zip: ADDRESS_SELECTORS.zip,
|
|
74
|
-
country: ADDRESS_SELECTORS.country,
|
|
75
|
-
};
|
|
76
|
-
|
|
77
66
|
// Loqate PCA expects element to be an id string, name string, or RegExp matching ids – not CSS selectors
|
|
78
|
-
const getLoqateElementRef = (handle:
|
|
67
|
+
const getLoqateElementRef = (handle: AddressFieldInputKey): string => {
|
|
79
68
|
if (namespace) {
|
|
80
69
|
return `${namespace}[${handle}]`;
|
|
81
70
|
}
|
|
82
71
|
|
|
83
|
-
const
|
|
84
|
-
const el = cssSelector
|
|
85
|
-
? (field.querySelector(cssSelector) as HTMLInputElement | null)
|
|
86
|
-
: null;
|
|
72
|
+
const el = findAddressFieldInput(field, handle);
|
|
87
73
|
|
|
88
74
|
if (el?.name) {
|
|
89
75
|
return el.name;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { FormieModuleDefinition } from '#contracts/modules';
|
|
2
|
+
import { ADDRESS_SELECTORS, findAddressFieldInput } from '#modules/address/constants';
|
|
3
|
+
import { getModuleFieldContainers } from '#modules/fields/shared';
|
|
4
|
+
import { fetchCountryFromIp } from '#utils/country-from-ip';
|
|
5
|
+
import { createDebug } from '#utils/debug';
|
|
6
|
+
|
|
7
|
+
const MODULE_ID = 'address-country';
|
|
8
|
+
const COUNTRY_SELECTOR = ADDRESS_SELECTORS.country;
|
|
9
|
+
const debug = createDebug('fields', 'address-country');
|
|
10
|
+
|
|
11
|
+
type ComboboxSelect = HTMLSelectElement & {
|
|
12
|
+
_formieTomSelect?: {
|
|
13
|
+
setValue: (value: string, silent?: boolean) => void;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type AddressCountryOptions = {
|
|
18
|
+
countryPreselectFromIp?: boolean;
|
|
19
|
+
countryAllowed?: string[];
|
|
20
|
+
countryOptionValue?: 'short' | 'full';
|
|
21
|
+
countryFromIpAction?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function resolveCountrySelectValue(
|
|
25
|
+
countryCode: string,
|
|
26
|
+
countryName: string | null | undefined,
|
|
27
|
+
optionValue: 'short' | 'full',
|
|
28
|
+
): string {
|
|
29
|
+
if (optionValue === 'full' && countryName) {
|
|
30
|
+
return countryName;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return countryCode.toUpperCase();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isCountryAllowed(countryCode: string, countryAllowed: string[] = []): boolean {
|
|
37
|
+
if (!countryAllowed.length) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const upperCode = countryCode.toUpperCase();
|
|
42
|
+
|
|
43
|
+
return countryAllowed.some((allowed) => allowed.toUpperCase() === upperCode);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function optionExists(select: HTMLSelectElement, value: string): boolean {
|
|
47
|
+
return Array.from(select.options).some((option) => option.value === value);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function setCountrySelectValue(select: HTMLSelectElement, value: string): Promise<void> {
|
|
51
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
52
|
+
const combobox = (select as ComboboxSelect)._formieTomSelect;
|
|
53
|
+
|
|
54
|
+
if (combobox || !select.hasAttribute('data-formie-combobox-input')) {
|
|
55
|
+
if (combobox) {
|
|
56
|
+
combobox.setValue(value, true);
|
|
57
|
+
} else {
|
|
58
|
+
select.value = value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
await new Promise((resolve) => {
|
|
66
|
+
window.setTimeout(resolve, 50);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
debug.warn('Timed out waiting for country combobox initialisation.');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function preselectCountry(
|
|
74
|
+
addressRoot: HTMLElement,
|
|
75
|
+
options: AddressCountryOptions,
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
const countryControl = findAddressFieldInput(addressRoot, 'country');
|
|
78
|
+
|
|
79
|
+
if (!(countryControl instanceof HTMLSelectElement)) {
|
|
80
|
+
debug.warn('Country control not found or not a select; skipping preselect.');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (countryControl.value.trim()) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const response = await fetchCountryFromIp(options.countryFromIpAction);
|
|
89
|
+
|
|
90
|
+
if (!response?.countryCode) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!isCountryAllowed(response.countryCode, options.countryAllowed)) {
|
|
95
|
+
debug.log('Detected country is not in the allowed list; skipping preselect.', {
|
|
96
|
+
countryCode: response.countryCode,
|
|
97
|
+
});
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const optionValue = options.countryOptionValue === 'full' ? 'full' : 'short';
|
|
102
|
+
const selectValue = resolveCountrySelectValue(
|
|
103
|
+
response.countryCode,
|
|
104
|
+
response.countryName,
|
|
105
|
+
optionValue,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (!optionExists(countryControl, selectValue)) {
|
|
109
|
+
debug.warn('Detected country is not available in the country dropdown; skipping preselect.', {
|
|
110
|
+
selectValue,
|
|
111
|
+
});
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await setCountrySelectValue(countryControl, selectValue);
|
|
116
|
+
debug.log('Preselected country from IP.', { selectValue });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export const addressCountryModule: FormieModuleDefinition = {
|
|
120
|
+
id: MODULE_ID,
|
|
121
|
+
kind: 'field',
|
|
122
|
+
match: (ctx) => {
|
|
123
|
+
return !!ctx.target.querySelector(COUNTRY_SELECTOR);
|
|
124
|
+
},
|
|
125
|
+
setup: async (ctx) => {
|
|
126
|
+
const options = (ctx.options || {}) as AddressCountryOptions;
|
|
127
|
+
|
|
128
|
+
if (!options.countryPreselectFromIp) {
|
|
129
|
+
return {
|
|
130
|
+
destroy: () => {},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const fields = getModuleFieldContainers(ctx);
|
|
135
|
+
const tasks = fields.map(async (field) => {
|
|
136
|
+
const addressRoot = field.closest('[data-formie-field-type="address"]')
|
|
137
|
+
|| field.closest('[data-formie-address-field-layout]')?.closest('[data-formie-field]')
|
|
138
|
+
|| field;
|
|
139
|
+
|
|
140
|
+
if (!(addressRoot instanceof HTMLElement)) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await preselectCountry(addressRoot, options);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await Promise.all(tasks);
|
|
148
|
+
debug.log('Module setup.', { fieldCount: fields.length });
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
destroy: () => {
|
|
152
|
+
debug.log('Module destroy.', { fieldCount: fields.length });
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
};
|