@verbb/formie-browser 1.0.3 → 1.0.5
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 +65 -17
- 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-CgVJcQ2P.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-CZ3FU5QO.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-BF5GU3_9.js +2674 -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-Cv8ADyk-.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-CpgqHCEx.js +463 -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-B09elPDB.js +125 -0
- package/dist/chunks/theme-classes-Tv7q7ToE.js +44 -0
- package/dist/chunks/turnstile-D_Pi1CKM.js +58 -0
- package/dist/chunks/utils-Vqp8i_RM.js +49401 -0
- package/dist/css/formie-theme.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/index.js +2447 -39
- package/dist/js/core/create-formie-client.d.ts.map +1 -1
- package/dist/js/core/field-error-container.d.ts +4 -0
- package/dist/js/core/field-error-container.d.ts.map +1 -0
- package/dist/js/core/submit-result-state.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/checkbox-radio.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/file-upload.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/text-limit.d.ts.map +1 -1
- 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/dist/js/utils/i18n.d.ts.map +1 -1
- package/dist/js/validation/rules/email.d.ts.map +1 -1
- package/dist/js/validation/rules/number.d.ts.map +1 -1
- package/dist/js/validation/validator.d.ts.map +1 -1
- package/package.json +6 -3
- package/src/css/formie-theme.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/js/core/create-formie-client.ts +27 -0
- package/src/js/core/field-error-container.ts +39 -0
- package/src/js/core/submit-result-state.ts +12 -2
- package/src/js/core/submit-result-ui.ts +7 -13
- 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/checkbox-radio.ts +21 -15
- 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/file-upload.ts +12 -9
- package/src/js/modules/fields/index.ts +6 -0
- package/src/js/modules/fields/phone-country.ts +6 -0
- package/src/js/modules/fields/text-limit.ts +87 -22
- 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/js/utils/i18n.ts +184 -5
- package/src/js/validation/rules/email.ts +3 -2
- package/src/js/validation/rules/match.ts +2 -2
- package/src/js/validation/rules/minmax.ts +4 -4
- package/src/js/validation/rules/number.ts +9 -5
- package/src/js/validation/rules/pattern.ts +4 -4
- package/src/js/validation/rules/required.ts +1 -1
- package/src/js/validation/rules/url.ts +1 -1
- package/src/js/validation/validator.ts +7 -7
- package/dist/chunks/address-finder-BjMuEiwF.js +0 -37
- package/dist/chunks/bpoint-KudsBCo2.js +0 -26
- package/dist/chunks/calculations-DlGSkQ3W.js +0 -147
- package/dist/chunks/captcha-eu-CterYFok.js +0 -34
- package/dist/chunks/checkbox-radio-CBvgk3hR.js +0 -136
- package/dist/chunks/conditions-DYy1-Hwt.js +0 -459
- package/dist/chunks/date-picker-BlD--zfe.js +0 -4994
- package/dist/chunks/eway-qwc-B5Us.js +0 -74
- package/dist/chunks/file-upload-CIgHrXfp.js +0 -316
- package/dist/chunks/friendly-captcha-v1-EDFac33c.js +0 -33
- package/dist/chunks/friendly-captcha-v2-BDpSvFGR.js +0 -38
- package/dist/chunks/go-cardless-DuDMVsnz.js +0 -24
- package/dist/chunks/google-address-Bmjp01pH.js +0 -181
- package/dist/chunks/hcaptcha-BTQ6An6g.js +0 -56
- package/dist/chunks/hidden-DbPdOxKb.js +0 -31
- package/dist/chunks/index-BEsfee-D.js +0 -1839
- package/dist/chunks/index-BP7Unm5Y.js +0 -863
- package/dist/chunks/index-BcqwpPNE.js +0 -4314
- package/dist/chunks/loqate-BdNGv93o.js +0 -55
- package/dist/chunks/mollie-DFOjTuGX.js +0 -24
- package/dist/chunks/moneris-BbFfl-EL.js +0 -101
- package/dist/chunks/opayo-CmgYa9ya.js +0 -122
- package/dist/chunks/paddle-BUi7w1_o.js +0 -60
- package/dist/chunks/paypal-A-ePfh6k.js +0 -100
- package/dist/chunks/payway-BqFCbhyo.js +0 -56
- package/dist/chunks/phone-country-DPMiqk4L.js +0 -3765
- package/dist/chunks/place-kit-CX7tamAg.js +0 -42
- package/dist/chunks/placekit-autocomplete.esm-LAwfbPZl.js +0 -1299
- package/dist/chunks/recaptcha-enterprise-DxF6cW95.js +0 -71
- package/dist/chunks/recaptcha-shared-DhGcbiaq.js +0 -15
- package/dist/chunks/recaptcha-v2-checkbox-R9EfbZ4k.js +0 -41
- package/dist/chunks/recaptcha-v2-invisible-DV9hswAp.js +0 -59
- package/dist/chunks/recaptcha-v3-V0xc-Edh.js +0 -28
- package/dist/chunks/repeater-DKUT6TGI.js +0 -124
- package/dist/chunks/rich-text-SH7KiaKH.js +0 -304
- package/dist/chunks/scripts-D9N2d5rN.js +0 -36
- package/dist/chunks/sdk-3SicTWby.js +0 -1559
- package/dist/chunks/shared-BmntPmnW.js +0 -80
- package/dist/chunks/signature-BA3tQsGT.js +0 -550
- package/dist/chunks/snaptcha-DV7p6J9k.js +0 -8
- package/dist/chunks/square-Bs-ndqYK.js +0 -46
- package/dist/chunks/stripe-DkZAHxtn.js +0 -202
- package/dist/chunks/styles-C3aqgtek.js +0 -21
- package/dist/chunks/summary-Dq2i9TQk.js +0 -126
- package/dist/chunks/table-Cu4WfVcw.js +0 -95
- package/dist/chunks/text-limit-2ycmB-Ae.js +0 -135
- package/dist/chunks/turnstile-CId90tRr.js +0 -59
- package/dist/chunks/utils-BntI3W4S.js +0 -6149
|
@@ -12,9 +12,21 @@ import {
|
|
|
12
12
|
import type { FormieModuleDefinition } from '#contracts/modules';
|
|
13
13
|
import { dispatchFieldEvent, getModuleFieldContainers } from '#modules/fields/shared';
|
|
14
14
|
import { buildFieldValueRegistry, fieldKeyToInputName, normalizeFieldKey, resolveFieldReferenceLive } from '#utils/field-references';
|
|
15
|
+
import { getRowScopedWatchNames, resolveRowScopedFieldReference, type RowScopeParams } from '#utils/field-references.row-scope';
|
|
15
16
|
import { createDebug } from '#utils/debug';
|
|
16
17
|
|
|
17
18
|
const INPUT_SELECTOR = 'input[data-formie-calculation-input]';
|
|
19
|
+
|
|
20
|
+
function getRowScopeParams(variable: CalculationVariable): RowScopeParams {
|
|
21
|
+
const scoped = variable as CalculationVariable & RowScopeParams;
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
scope: scoped.scope,
|
|
25
|
+
index: scoped.index,
|
|
26
|
+
rows: scoped.rows,
|
|
27
|
+
fieldKind: scoped.fieldKind,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
18
30
|
const MODULE_ID = 'calculations';
|
|
19
31
|
const debug = createDebug('fields', 'calculations');
|
|
20
32
|
|
|
@@ -27,6 +39,15 @@ function resolveVariables(
|
|
|
27
39
|
const variables: Record<string, unknown> = {};
|
|
28
40
|
|
|
29
41
|
variableEntries.forEach(([variableKey, variable]) => {
|
|
42
|
+
const scopeParams = getRowScopeParams(variable);
|
|
43
|
+
const scope = String(scopeParams.scope || '').trim();
|
|
44
|
+
|
|
45
|
+
if (scope) {
|
|
46
|
+
const resolved = resolveRowScopedFieldReference(variable.sourceKey || '', scopeParams, registry);
|
|
47
|
+
variables[variableKey] = readCalculationVariableValue(variable, resolved.value);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
30
51
|
const resolved = resolveFieldReferenceLive(variable.sourceKey || '', registry);
|
|
31
52
|
variables[variableKey] = readCalculationVariableValue(variable, resolved.value);
|
|
32
53
|
});
|
|
@@ -39,6 +60,17 @@ function getWatchNames(root: Element, variableEntries: CalculationVariableEntry[
|
|
|
39
60
|
const watchNames = new Set<string>();
|
|
40
61
|
|
|
41
62
|
variableEntries.forEach(([, variable]) => {
|
|
63
|
+
const scopeParams = getRowScopeParams(variable);
|
|
64
|
+
const scope = String(scopeParams.scope || '').trim();
|
|
65
|
+
|
|
66
|
+
if (scope) {
|
|
67
|
+
getRowScopedWatchNames(variable.sourceKey || '', scopeParams, registry).forEach((name) => {
|
|
68
|
+
watchNames.add(name);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
42
74
|
const key = normalizeFieldKey(variable.sourceKey || '');
|
|
43
75
|
const entry = registry.get(key);
|
|
44
76
|
|
|
@@ -31,6 +31,14 @@ function getMinMaxRule(getRule: (rule: string) => unknown): { min: number | null
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
function getSelectedOptionCount(field: HTMLElement): number {
|
|
35
|
+
return Array.from(field.querySelectorAll('input[type="checkbox"]')).filter((input): input is HTMLInputElement => {
|
|
36
|
+
return input instanceof HTMLInputElement && !isToggleCheckbox(input);
|
|
37
|
+
}).filter((input) => {
|
|
38
|
+
return input.checked;
|
|
39
|
+
}).length;
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
function registerValidators(form: HTMLFormElement | null): void {
|
|
35
43
|
// Several checkbox/radio module instances can exist in one form, but the
|
|
36
44
|
// custom validator should only be registered once per form lifecycle.
|
|
@@ -42,12 +50,7 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
42
50
|
return true;
|
|
43
51
|
}
|
|
44
52
|
|
|
45
|
-
const
|
|
46
|
-
return input instanceof HTMLInputElement && !isToggleCheckbox(input);
|
|
47
|
-
});
|
|
48
|
-
const selected = inputs.filter((input) => {
|
|
49
|
-
return input.checked;
|
|
50
|
-
}).length;
|
|
53
|
+
const selected = getSelectedOptionCount(field);
|
|
51
54
|
const { min, max } = getMinMaxRule(getRule);
|
|
52
55
|
|
|
53
56
|
if (min !== null && selected < min) {
|
|
@@ -61,21 +64,24 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
61
64
|
return true;
|
|
62
65
|
},
|
|
63
66
|
({ field, label, t, getRule }) => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (min !== null && max !== null) {
|
|
67
|
-
return t('{attribute} must select between {min} and {max}.', { attribute: label, min, max });
|
|
67
|
+
if (!field) {
|
|
68
|
+
return t('{label} has an invalid value.', { label });
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
const selected = getSelectedOptionCount(field);
|
|
72
|
+
const { min, max } = getMinMaxRule(getRule);
|
|
73
|
+
|
|
74
|
+
if (min !== null && selected < min) {
|
|
75
|
+
return field.getAttribute('data-formie-validation-min-options-message')
|
|
76
|
+
?? t('{label} should contain at least {min, number} {min, plural, one{option} other{options}}.', { label, min });
|
|
72
77
|
}
|
|
73
78
|
|
|
74
|
-
if (max !== null) {
|
|
75
|
-
return
|
|
79
|
+
if (max !== null && selected > max) {
|
|
80
|
+
return field.getAttribute('data-formie-validation-max-options-message')
|
|
81
|
+
?? t('{label} should contain at most {max, number} {max, plural, one{option} other{options}}.', { label, max });
|
|
76
82
|
}
|
|
77
83
|
|
|
78
|
-
return t('{
|
|
84
|
+
return t('{label} has an invalid value.', { label });
|
|
79
85
|
},
|
|
80
86
|
);
|
|
81
87
|
});
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import TomSelect from 'tom-select/dist/esm/tom-select.complete.js';
|
|
2
|
+
|
|
3
|
+
import type { FormieModuleDefinition } from '#contracts/modules';
|
|
4
|
+
import comboboxThemeCss from '#theme-css/fields/_combobox.css?inline';
|
|
5
|
+
import { dispatchFieldEvent, getModuleFieldContainers } from '#modules/fields/shared';
|
|
6
|
+
import { ensureModuleStyles } from '#modules/styles';
|
|
7
|
+
import { createDebug } from '#utils/debug';
|
|
8
|
+
|
|
9
|
+
const SELECT_SELECTOR = 'select[data-formie-combobox-input]';
|
|
10
|
+
const MODULE_ID = 'combobox';
|
|
11
|
+
const debug = createDebug('fields', 'combobox');
|
|
12
|
+
|
|
13
|
+
ensureModuleStyles(MODULE_ID, [comboboxThemeCss]);
|
|
14
|
+
|
|
15
|
+
const NATIVE_THEME_CLASSES = [
|
|
16
|
+
'formie-select',
|
|
17
|
+
'formie-dropdown-input',
|
|
18
|
+
'formie-input-error',
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export type FormieComboboxOptions = {
|
|
22
|
+
multiple?: boolean;
|
|
23
|
+
placeholder?: string | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type TomSelectInstance = InstanceType<typeof TomSelect> & {
|
|
27
|
+
wrapper: HTMLElement;
|
|
28
|
+
dropdown?: HTMLElement;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type SelectElement = HTMLSelectElement & {
|
|
32
|
+
_formieTomSelect?: TomSelectInstance;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function stripNativeThemeClasses(select: HTMLSelectElement): string[] {
|
|
36
|
+
const removed: string[] = [];
|
|
37
|
+
|
|
38
|
+
NATIVE_THEME_CLASSES.forEach((className) => {
|
|
39
|
+
if (select.classList.contains(className)) {
|
|
40
|
+
select.classList.remove(className);
|
|
41
|
+
removed.push(className);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return removed;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function restoreNativeThemeClasses(select: HTMLSelectElement, classNames: string[]): void {
|
|
49
|
+
classNames.forEach((className) => {
|
|
50
|
+
select.classList.add(className);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function cleanLeakedThemeClasses(element: HTMLElement): void {
|
|
55
|
+
NATIVE_THEME_CLASSES.forEach((className) => {
|
|
56
|
+
element.classList.remove(className);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function resolvePlaceholder(select: HTMLSelectElement, configuredPlaceholder?: string | null): string | null {
|
|
61
|
+
const configured = configuredPlaceholder?.trim();
|
|
62
|
+
|
|
63
|
+
if (configured) {
|
|
64
|
+
return configured;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const emptyOption = select.querySelector('option[value=""]');
|
|
68
|
+
|
|
69
|
+
return emptyOption?.textContent?.trim() || null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function removeEmptyOptionFromCombobox(instance: TomSelectInstance): void {
|
|
73
|
+
if (instance.options['']) {
|
|
74
|
+
instance.removeOption('', true);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function initFormieCombobox(select: SelectElement, options: FormieComboboxOptions = {}): () => void {
|
|
79
|
+
select._formieTomSelect?.destroy();
|
|
80
|
+
|
|
81
|
+
const multiple = options.multiple === true;
|
|
82
|
+
const restoredClasses = stripNativeThemeClasses(select);
|
|
83
|
+
const placeholder = resolvePlaceholder(select, options.placeholder);
|
|
84
|
+
|
|
85
|
+
const mergedOptions: Record<string, unknown> = {
|
|
86
|
+
create: false,
|
|
87
|
+
maxItems: multiple ? null : 1,
|
|
88
|
+
plugins: multiple ? ['remove_button'] : [],
|
|
89
|
+
hideSelected: multiple ? true : null,
|
|
90
|
+
clearAfterSelect: multiple,
|
|
91
|
+
closeAfterSelect: !multiple,
|
|
92
|
+
allowEmptyOption: !multiple,
|
|
93
|
+
openOnFocus: true,
|
|
94
|
+
diacritics: true,
|
|
95
|
+
// Tom Select copies the native select class attribute onto its wrapper,
|
|
96
|
+
// which would duplicate Formie select chrome if left on the <select>.
|
|
97
|
+
copyClassesToDropdown: false,
|
|
98
|
+
wrapperClass: 'ts-wrapper formie-combobox',
|
|
99
|
+
onChange: () => {
|
|
100
|
+
// Mirror Tom Select changes back through the native select so validation,
|
|
101
|
+
// conditions, and calculations respond to combobox-driven updates.
|
|
102
|
+
select.dispatchEvent(new Event('input', { bubbles: true }));
|
|
103
|
+
select.dispatchEvent(new Event('change', { bubbles: true }));
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
if (placeholder) {
|
|
108
|
+
mergedOptions.placeholder = placeholder;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
dispatchFieldEvent(select, MODULE_ID, 'before-init', {
|
|
112
|
+
select,
|
|
113
|
+
options: mergedOptions,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const instance = new TomSelect(select, mergedOptions) as TomSelectInstance;
|
|
117
|
+
removeEmptyOptionFromCombobox(instance);
|
|
118
|
+
cleanLeakedThemeClasses(instance.wrapper);
|
|
119
|
+
|
|
120
|
+
if (instance.dropdown) {
|
|
121
|
+
cleanLeakedThemeClasses(instance.dropdown);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Tom Select keeps the native select as a preceding sibling; ensure it never
|
|
125
|
+
// remains visible once enhanced (notably for multi-select listboxes).
|
|
126
|
+
select.style.display = 'none';
|
|
127
|
+
|
|
128
|
+
select._formieTomSelect = instance;
|
|
129
|
+
|
|
130
|
+
debug.log('Initialized.', {
|
|
131
|
+
inputName: select.name,
|
|
132
|
+
multiple,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
dispatchFieldEvent(select, MODULE_ID, 'after-init', {
|
|
136
|
+
combobox: instance,
|
|
137
|
+
options: mergedOptions,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return () => {
|
|
141
|
+
instance.destroy();
|
|
142
|
+
select.style.removeProperty('display');
|
|
143
|
+
restoreNativeThemeClasses(select, restoredClasses);
|
|
144
|
+
delete select._formieTomSelect;
|
|
145
|
+
debug.log('Destroyed.', {
|
|
146
|
+
inputName: select.name,
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const comboboxModule: FormieModuleDefinition = {
|
|
152
|
+
id: MODULE_ID,
|
|
153
|
+
kind: 'field',
|
|
154
|
+
match: (ctx) => {
|
|
155
|
+
return !!ctx.target.querySelector(SELECT_SELECTOR);
|
|
156
|
+
},
|
|
157
|
+
setup: async (ctx) => {
|
|
158
|
+
const options = (ctx.options || {}) as FormieComboboxOptions;
|
|
159
|
+
const fields = getModuleFieldContainers(ctx);
|
|
160
|
+
const cleanups = fields.map((field) => {
|
|
161
|
+
const select = field.querySelector(SELECT_SELECTOR);
|
|
162
|
+
|
|
163
|
+
if (!(select instanceof HTMLSelectElement)) {
|
|
164
|
+
debug.warn('Field missing combobox select; skipping.');
|
|
165
|
+
return () => { };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return initFormieCombobox(select as SelectElement, options);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
debug.log('Module setup.', { fieldCount: fields.length });
|
|
172
|
+
|
|
173
|
+
await ctx.emit('formie:module:combobox:init', {
|
|
174
|
+
count: cleanups.length,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
destroy: () => {
|
|
179
|
+
cleanups.forEach((cleanup) => {
|
|
180
|
+
cleanup();
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
debug.log('Module destroy.', { fieldCount: fields.length });
|
|
184
|
+
void ctx.emit('formie:module:combobox:destroy', {});
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
},
|
|
188
|
+
};
|