@verbb/formie-browser 1.0.6 → 1.0.8
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 +23 -0
- package/dist/chunks/_survey-presentations-CFsgToRT.js +4 -0
- package/dist/chunks/{address-finder-yXU5yMjG.js → address-finder-k7_BXaRe.js} +1 -1
- package/dist/chunks/{api-DMK8NSUI.js → api-BUb6P-xu.js} +57 -51
- package/dist/chunks/{api-CmwLRq_n.js → api-C8wyLC-s.js} +235 -193
- package/dist/chunks/{bpoint-DSjqRv63.js → bpoint-CwSw_p7A.js} +1 -1
- package/dist/chunks/{captcha-eu-CWJb64_j.js → captcha-eu-CGm8Lbrj.js} +2 -2
- package/dist/chunks/{conditions-DjZOtHfS.js → conditions-B6_nzpeE.js} +95 -83
- package/dist/chunks/{eway-D3ctTbEk.js → eway-B30BMlDp.js} +2 -2
- package/dist/chunks/{friendly-captcha-v1-ByXzENYZ.js → friendly-captcha-v1-C_2reOXr.js} +1 -1
- package/dist/chunks/{friendly-captcha-v2-BPdlmhQu.js → friendly-captcha-v2-CLSAbkqx.js} +1 -1
- package/dist/chunks/{go-cardless-BwASSYSK.js → go-cardless-BiF3iLel.js} +1 -1
- package/dist/chunks/{google-address-OhKilPWE.js → google-address-BH3Zhi06.js} +1 -1
- package/dist/chunks/{hcaptcha-CdCd9oEi.js → hcaptcha-CGLAmgjO.js} +2 -2
- package/dist/chunks/{loqate-Cp2zvwpE.js → loqate-CJPzBmSm.js} +1 -1
- package/dist/chunks/{mollie-COG-ko2D.js → mollie-D__SwtcH.js} +1 -1
- package/dist/chunks/{moneris-CMH8PlRc.js → moneris-KJS1BKx7.js} +1 -1
- package/dist/chunks/{opayo-D2jsR5vh.js → opayo-A_LIkc-v.js} +4 -4
- package/dist/chunks/{paddle-DxF88RfC.js → paddle-BjbOOb0m.js} +2 -2
- package/dist/chunks/{paypal-b5pnwrA2.js → paypal-CyjHIV8v.js} +2 -2
- package/dist/chunks/{payway-BJYu9DIR.js → payway-a_cyrr3j.js} +2 -2
- package/dist/chunks/{recaptcha-enterprise-BSwi8IVX.js → recaptcha-enterprise-XPId5-bS.js} +19 -20
- package/dist/chunks/{recaptcha-shared-B4zAescW.js → recaptcha-shared-CLYJvojk.js} +15 -3
- package/dist/chunks/{recaptcha-v2-checkbox-CnDQmsKa.js → recaptcha-v2-checkbox-Cy-0PEPc.js} +2 -2
- package/dist/chunks/{recaptcha-v2-invisible-Cz5uIlp-.js → recaptcha-v2-invisible-BwRH540O.js} +2 -2
- package/dist/chunks/recaptcha-v3-No4Ykrhk.js +28 -0
- package/dist/chunks/scripts-CbQ7agX3.js +30 -0
- package/dist/chunks/{snaptcha-vz1RnZ15.js → snaptcha-DKx98oon.js} +1 -1
- package/dist/chunks/{square-BP8RWAKB.js → square-Bga6WXHV.js} +2 -2
- package/dist/chunks/{stripe-MihN63uC.js → stripe-BlqVocl3.js} +2 -2
- package/dist/chunks/{summary-D3AjxpYN.js → summary-Dks0gXS3.js} +42 -38
- package/dist/chunks/survey-likert-DmJ3klhP.js +20 -0
- package/dist/chunks/survey-rank-DeUenftw.js +127 -0
- package/dist/chunks/survey-rating-BPUYDgBJ.js +88 -0
- package/dist/chunks/{turnstile-D_Pi1CKM.js → turnstile-DJakPzQD.js} +3 -3
- package/dist/css/formie-theme.css +1 -0
- package/dist/css/theme/fields/_survey-presentations.css +396 -0
- package/dist/index.js +651 -597
- package/dist/js/contracts/schema.d.ts +5 -0
- package/dist/js/contracts/schema.d.ts.map +1 -1
- package/dist/js/core/create-formie-client.d.ts.map +1 -1
- package/dist/js/core/error-aria-live.d.ts +6 -0
- package/dist/js/core/error-aria-live.d.ts.map +1 -0
- package/dist/js/core/page-client-event.d.ts +8 -4
- package/dist/js/core/page-client-event.d.ts.map +1 -1
- 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/modules/captchas/factories.d.ts.map +1 -1
- package/dist/js/modules/captchas/host.d.ts +1 -0
- package/dist/js/modules/captchas/host.d.ts.map +1 -1
- package/dist/js/modules/captchas/recaptcha-enterprise.d.ts.map +1 -1
- package/dist/js/modules/captchas/recaptcha-shared.d.ts +1 -0
- package/dist/js/modules/captchas/recaptcha-shared.d.ts.map +1 -1
- package/dist/js/modules/captchas/recaptcha-v3.d.ts.map +1 -1
- package/dist/js/modules/captchas/turnstile.d.ts.map +1 -1
- package/dist/js/modules/fields/conditions/effects.d.ts.map +1 -1
- package/dist/js/modules/fields/index.d.ts.map +1 -1
- package/dist/js/modules/fields/summary.d.ts.map +1 -1
- package/dist/js/modules/fields/survey-likert.d.ts +3 -0
- package/dist/js/modules/fields/survey-likert.d.ts.map +1 -0
- package/dist/js/modules/fields/survey-rank.d.ts +3 -0
- package/dist/js/modules/fields/survey-rank.d.ts.map +1 -0
- package/dist/js/modules/fields/survey-rating.d.ts +3 -0
- package/dist/js/modules/fields/survey-rating.d.ts.map +1 -0
- package/dist/js/transport/forms-api.d.ts.map +1 -1
- package/dist/js/utils/form-started-at.d.ts +2 -0
- package/dist/js/utils/form-started-at.d.ts.map +1 -0
- package/dist/js/utils/scripts.d.ts.map +1 -1
- package/dist/js/utils/unload-warning.d.ts.map +1 -1
- package/dist/js/validation/enter-key-guard.d.ts +2 -0
- package/dist/js/validation/enter-key-guard.d.ts.map +1 -0
- package/dist/js/validation/types.d.ts +1 -0
- package/dist/js/validation/types.d.ts.map +1 -1
- package/dist/js/validation/validator.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/css/formie-theme.css +1 -0
- package/src/css/theme/fields/_survey-presentations.css +396 -0
- package/src/js/contracts/schema.ts +6 -0
- package/src/js/core/create-formie-client.ts +21 -1
- package/src/js/core/error-aria-live.ts +45 -0
- package/src/js/core/page-client-event.ts +86 -18
- package/src/js/core/submit-result-state.ts +10 -2
- package/src/js/core/submit-result-ui.ts +8 -4
- package/src/js/modules/captchas/factories.ts +4 -0
- package/src/js/modules/captchas/host.ts +14 -2
- package/src/js/modules/captchas/recaptcha-enterprise.ts +14 -11
- package/src/js/modules/captchas/recaptcha-shared.ts +14 -0
- package/src/js/modules/captchas/recaptcha-v3.ts +7 -4
- package/src/js/modules/captchas/turnstile.ts +9 -3
- package/src/js/modules/fields/conditions/effects.ts +42 -0
- package/src/js/modules/fields/index.ts +3 -0
- package/src/js/modules/fields/summary.ts +17 -3
- package/src/js/modules/fields/survey-likert.ts +50 -0
- package/src/js/modules/fields/survey-rank.ts +385 -0
- package/src/js/modules/fields/survey-rating.ts +182 -0
- package/src/js/transport/forms-api.ts +1 -0
- package/src/js/utils/form-started-at.ts +25 -0
- package/src/js/utils/scripts.ts +16 -0
- package/src/js/utils/unload-warning.ts +1 -0
- package/src/js/validation/enter-key-guard.ts +63 -0
- package/src/js/validation/types.ts +1 -0
- package/src/js/validation/validator.ts +14 -5
- package/dist/chunks/recaptcha-v3-BtbbP111.js +0 -27
- package/dist/chunks/scripts-6CYSUGKQ.js +0 -24
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { FormSubmitResult } from '#contracts/schema';
|
|
2
|
+
import { applyErrorAriaLive, getErrorAriaLivePreference, resolveSubmitErrorAriaLive } from '#core/error-aria-live';
|
|
2
3
|
import { ensureFieldErrorContainer as resolveFieldErrorContainer } from '#core/field-error-container';
|
|
3
4
|
import { syncPageTabErrors } from '#core/page-tab-errors';
|
|
4
5
|
import { focusFirstValidationError } from '#core/validation-focus';
|
|
@@ -124,8 +125,10 @@ function ensureFormErrorMessageContainer(form: HTMLFormElement, container: HTMLE
|
|
|
124
125
|
// Form-level errors use a live region because they are often the only output
|
|
125
126
|
// for submit-stage failures such as provider or transport errors.
|
|
126
127
|
messageContainer.setAttribute('role', 'alert');
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
applyErrorAriaLive(
|
|
129
|
+
messageContainer,
|
|
130
|
+
resolveSubmitErrorAriaLive(getErrorAriaLivePreference(form)),
|
|
131
|
+
);
|
|
129
132
|
|
|
130
133
|
return messageContainer;
|
|
131
134
|
}
|
|
@@ -295,6 +298,8 @@ function appendDescribedBy(input: HTMLElement, describedById: string): void {
|
|
|
295
298
|
}
|
|
296
299
|
|
|
297
300
|
export function renderFieldErrors(form: HTMLFormElement, fieldErrors: Record<string, string[]>): void {
|
|
301
|
+
const errorAriaLive = resolveSubmitErrorAriaLive(getErrorAriaLivePreference(form));
|
|
302
|
+
|
|
298
303
|
Object.entries(fieldErrors).forEach(([handle, messages]) => {
|
|
299
304
|
const fieldNode = form.querySelector(`[data-formie-field-handle="${handle}"]`);
|
|
300
305
|
|
|
@@ -307,8 +312,7 @@ export function renderFieldErrors(form: HTMLFormElement, fieldErrors: Record<str
|
|
|
307
312
|
? container.id
|
|
308
313
|
: `${handle}-errors`;
|
|
309
314
|
container.id = containerId;
|
|
310
|
-
container
|
|
311
|
-
container.setAttribute('aria-atomic', 'true');
|
|
315
|
+
applyErrorAriaLive(container, errorAriaLive);
|
|
312
316
|
addThemeClasses(fieldNode as HTMLElement, form, 'fieldLayoutError');
|
|
313
317
|
(fieldNode as HTMLElement).setAttribute('data-formie-field-has-error', 'true');
|
|
314
318
|
|
|
@@ -436,6 +436,10 @@ export function createManagedCaptchaModule<
|
|
|
436
436
|
return;
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
// Reconcile immediately so captcha placeholders on the newly
|
|
440
|
+
// visible page are mounted before screen-stage policy runs.
|
|
441
|
+
visibility.reconcileImmediate();
|
|
442
|
+
|
|
439
443
|
// By the time provider `screen()` runs, the widget is
|
|
440
444
|
// guaranteed to be mounted and the active placeholder is
|
|
441
445
|
// resolved. That lets provider code focus on challenge
|
|
@@ -17,6 +17,7 @@ type Cleanup = () => void;
|
|
|
17
17
|
type ObserveVisiblePlaceholdersResult = {
|
|
18
18
|
cleanup: Cleanup;
|
|
19
19
|
reconcile: () => void;
|
|
20
|
+
reconcileImmediate: () => void;
|
|
20
21
|
getVisible: () => HTMLElement[];
|
|
21
22
|
};
|
|
22
23
|
|
|
@@ -115,7 +116,12 @@ const CAPTCHA_VISIBILITY_EVENTS = [
|
|
|
115
116
|
'formie:page:navigate',
|
|
116
117
|
'formie:page:navigate:after',
|
|
117
118
|
'formie:submit:result',
|
|
118
|
-
];
|
|
119
|
+
] as const;
|
|
120
|
+
|
|
121
|
+
const CAPTCHA_IMMEDIATE_RECONCILE_EVENTS = new Set<string>([
|
|
122
|
+
'formie:page:navigate',
|
|
123
|
+
'formie:page:navigate:after',
|
|
124
|
+
]);
|
|
119
125
|
|
|
120
126
|
function bindDomEvent(target: EventTarget, eventName: string, callback: EventListener): Cleanup {
|
|
121
127
|
target.addEventListener(eventName, callback);
|
|
@@ -152,7 +158,7 @@ function isElementVisible(target: Element): target is HTMLElement {
|
|
|
152
158
|
|
|
153
159
|
const style = window.getComputedStyle(target);
|
|
154
160
|
|
|
155
|
-
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
161
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && target.getClientRects().length > 0;
|
|
156
162
|
}
|
|
157
163
|
|
|
158
164
|
function getPrimaryPlaceholder(root: Element, selector: string): HTMLElement | null {
|
|
@@ -271,6 +277,11 @@ function observeVisiblePlaceholders(
|
|
|
271
277
|
}),
|
|
272
278
|
...CAPTCHA_VISIBILITY_EVENTS.map((eventName) => {
|
|
273
279
|
return bindDomEvent(root, eventName, () => {
|
|
280
|
+
if (CAPTCHA_IMMEDIATE_RECONCILE_EVENTS.has(eventName)) {
|
|
281
|
+
reconcileNow();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
|
|
274
285
|
reconcile();
|
|
275
286
|
});
|
|
276
287
|
}),
|
|
@@ -292,6 +303,7 @@ function observeVisiblePlaceholders(
|
|
|
292
303
|
visible.clear();
|
|
293
304
|
},
|
|
294
305
|
reconcile,
|
|
306
|
+
reconcileImmediate: reconcileNow,
|
|
295
307
|
getVisible: () => {
|
|
296
308
|
return queryCaptchaPlaceholders(root, selector).filter((placeholder) => isElementVisible(placeholder));
|
|
297
309
|
},
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
import { CAPTCHA_EXECUTE_WAIT_FOR_VALUE_MS } from '#modules/captchas/constants';
|
|
5
5
|
import {
|
|
6
6
|
loadRecaptchaGlobal,
|
|
7
|
+
whenRecaptchaReady,
|
|
7
8
|
type RecaptchaGlobal,
|
|
8
9
|
type RecaptchaProviderOptions,
|
|
9
10
|
} from '#modules/captchas/recaptcha-shared';
|
|
@@ -94,19 +95,21 @@ export const recaptchaEnterpriseModule = defineCaptchaModule<RecaptchaEnterprise
|
|
|
94
95
|
return;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
await whenRecaptchaReady(api, async() => {
|
|
99
|
+
if (provider.enterpriseType === 'score' || provider.enterpriseType === 'policy') {
|
|
100
|
+
// Score and policy-based keys execute by site key and can resolve
|
|
101
|
+
// directly to a token. We write that token into the shared transport
|
|
102
|
+
// layer so the backend can validate it in the usual way.
|
|
103
|
+
const token = await enterpriseApi.execute(provider.siteKey || '', { action: provider.action || 'submit' });
|
|
102
104
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
if (typeof token === 'string' && token.trim() !== '') {
|
|
106
|
+
services.tokens.write(token.trim());
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
// Fallback branch for any future Enterprise widget-like mode.
|
|
110
|
+
enterpriseApi.execute(widget);
|
|
105
111
|
}
|
|
106
|
-
}
|
|
107
|
-
// Fallback branch for any future Enterprise widget-like mode.
|
|
108
|
-
enterpriseApi.execute(widget);
|
|
109
|
-
}
|
|
112
|
+
});
|
|
110
113
|
|
|
111
114
|
const hasToken = await services.tokens.wait(CAPTCHA_EXECUTE_WAIT_FOR_VALUE_MS);
|
|
112
115
|
|
|
@@ -67,3 +67,17 @@ export async function loadRecaptchaGlobal(
|
|
|
67
67
|
timeoutMs: CAPTCHA_PROVIDER_LOAD_TIMEOUT_MS,
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
export function whenRecaptchaReady(api: RecaptchaGlobal, callback: () => void | Promise<void>): Promise<void> {
|
|
72
|
+
const readyApi = api.enterprise || api;
|
|
73
|
+
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
try {
|
|
76
|
+
readyApi.ready(() => {
|
|
77
|
+
Promise.resolve(callback()).then(resolve).catch(reject);
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
reject(error);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
import { CAPTCHA_EXECUTE_WAIT_FOR_VALUE_MS } from '#modules/captchas/constants';
|
|
5
5
|
import {
|
|
6
6
|
loadRecaptchaGlobal,
|
|
7
|
+
whenRecaptchaReady,
|
|
7
8
|
type RecaptchaGlobal,
|
|
8
9
|
type RecaptchaProviderOptions,
|
|
9
10
|
} from '#modules/captchas/recaptcha-shared';
|
|
@@ -40,11 +41,13 @@ export const recaptchaV3Module = defineCaptchaModule<RecaptchaProviderOptions, R
|
|
|
40
41
|
|
|
41
42
|
// v3 returns its token directly from `execute()`, unlike widget-based
|
|
42
43
|
// captchas that signal completion later via callbacks.
|
|
43
|
-
|
|
44
|
+
await whenRecaptchaReady(api, async() => {
|
|
45
|
+
const token = await api.execute(provider.siteKey || '', { action: provider.action || 'submit' });
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
if (typeof token === 'string' && token.trim() !== '') {
|
|
48
|
+
services.tokens.write(token.trim());
|
|
49
|
+
}
|
|
50
|
+
});
|
|
48
51
|
|
|
49
52
|
// We still wait on the shared token layer so the module follows the
|
|
50
53
|
// same transport contract as every other captcha provider.
|
|
@@ -14,6 +14,7 @@ type TurnstileGlobal = {
|
|
|
14
14
|
render: (container: HTMLElement, options: Record<string, unknown>) => string;
|
|
15
15
|
execute: (widgetId: string) => void;
|
|
16
16
|
reset: (widgetId: string) => void;
|
|
17
|
+
remove: (widgetId: string) => void;
|
|
17
18
|
};
|
|
18
19
|
|
|
19
20
|
// Provider-specific options only. Generic captcha concerns are handled by the
|
|
@@ -123,9 +124,14 @@ export const turnstileModule = defineCaptchaModule<TurnstileProviderOptions, Tur
|
|
|
123
124
|
});
|
|
124
125
|
},
|
|
125
126
|
unmount: ({ api, widget, services }) => {
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
api.
|
|
127
|
+
// Remove the widget before clearing placeholder DOM so Turnstile does not
|
|
128
|
+
// log errors about orphaned challenge iframes during remounts.
|
|
129
|
+
if (typeof api.remove === 'function') {
|
|
130
|
+
api.remove(widget);
|
|
131
|
+
} else {
|
|
132
|
+
api.reset(widget);
|
|
133
|
+
}
|
|
134
|
+
|
|
129
135
|
services.tokens.clear();
|
|
130
136
|
},
|
|
131
137
|
});
|
|
@@ -72,7 +72,49 @@ function setVisibilityState(node: Element, hidden: boolean): boolean {
|
|
|
72
72
|
return wasHidden !== hidden;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
const SUBMIT_CONTROL_SELECTOR = 'button[type="submit"], button[data-formie-action], input[type="submit"]';
|
|
76
|
+
|
|
77
|
+
function syncControlDisabledState(control: Element, hidden: boolean): void {
|
|
78
|
+
if (!(control instanceof HTMLButtonElement || control instanceof HTMLInputElement)) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (hidden) {
|
|
83
|
+
if (!control.hasAttribute(CONDITION_DISABLED_ATTR)) {
|
|
84
|
+
if (control.hasAttribute('disabled')) {
|
|
85
|
+
control.setAttribute(PRESERVED_DISABLED_ATTR, 'true');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
control.setAttribute(CONDITION_DISABLED_ATTR, 'true');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
control.setAttribute('disabled', 'true');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!control.hasAttribute(CONDITION_DISABLED_ATTR)) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (control.hasAttribute(PRESERVED_DISABLED_ATTR)) {
|
|
100
|
+
control.setAttribute('disabled', 'true');
|
|
101
|
+
control.removeAttribute(PRESERVED_DISABLED_ATTR);
|
|
102
|
+
} else {
|
|
103
|
+
control.removeAttribute('disabled');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
control.removeAttribute(CONDITION_DISABLED_ATTR);
|
|
107
|
+
}
|
|
108
|
+
|
|
75
109
|
function syncDisabledState(node: Element, hidden: boolean): void {
|
|
110
|
+
if (node.matches(SUBMIT_CONTROL_SELECTOR)) {
|
|
111
|
+
syncControlDisabledState(node, hidden);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
node.querySelectorAll(SUBMIT_CONTROL_SELECTOR).forEach((control) => {
|
|
115
|
+
syncControlDisabledState(control, hidden);
|
|
116
|
+
});
|
|
117
|
+
|
|
76
118
|
node.querySelectorAll('input, textarea, select').forEach((input) => {
|
|
77
119
|
if (hidden) {
|
|
78
120
|
if (!input.hasAttribute(CONDITION_DISABLED_ATTR)) {
|
|
@@ -21,6 +21,9 @@ export const builtinFieldModuleLoaders: Record<string, () => Promise<FormieModul
|
|
|
21
21
|
'rich-text': () => import('#modules/fields/rich-text').then((module) => module.richTextModule),
|
|
22
22
|
'signature': () => import('#modules/fields/signature').then((module) => module.signatureModule),
|
|
23
23
|
'summary': () => import('#modules/fields/summary').then((module) => module.summaryModule),
|
|
24
|
+
'survey-likert': () => import('#modules/fields/survey-likert').then((module) => module.surveyLikertModule),
|
|
25
|
+
'survey-rank': () => import('#modules/fields/survey-rank').then((module) => module.surveyRankModule),
|
|
26
|
+
'survey-rating': () => import('#modules/fields/survey-rating').then((module) => module.surveyRatingModule),
|
|
24
27
|
'table': () => import('#modules/fields/table').then((module) => module.tableModule),
|
|
25
28
|
'text-limit': () => import('#modules/fields/text-limit').then((module) => module.textLimitModule),
|
|
26
29
|
};
|
|
@@ -11,6 +11,8 @@ import { requestText } from '#utils/http';
|
|
|
11
11
|
const BLOCKS_SELECTOR = '[data-formie-summary-blocks]';
|
|
12
12
|
const CONTAINER_SELECTOR = '[data-formie-summary-container]';
|
|
13
13
|
const SUMMARY_ACTION = 'formie/fields/get-summary-html';
|
|
14
|
+
const THEME_CONFIG_ATTR = 'data-formie-theme-config';
|
|
15
|
+
const FRONTEND_THEME_ATTR = 'data-formie-frontend-theme';
|
|
14
16
|
const MODULE_ID = 'summary';
|
|
15
17
|
const debug = createDebug('fields', 'summary');
|
|
16
18
|
|
|
@@ -18,6 +20,8 @@ ensureModuleStyles(MODULE_ID, [summaryCss]);
|
|
|
18
20
|
|
|
19
21
|
type SummaryRequestState = {
|
|
20
22
|
accessToken: string | null;
|
|
23
|
+
themeConfig: string | null;
|
|
24
|
+
frontendTheme: string | null;
|
|
21
25
|
};
|
|
22
26
|
|
|
23
27
|
function getSummaryRequestUrl(): string {
|
|
@@ -27,12 +31,14 @@ function getSummaryRequestUrl(): string {
|
|
|
27
31
|
return url.toString();
|
|
28
32
|
}
|
|
29
33
|
|
|
30
|
-
function getSummaryRequestState(field: HTMLElement): SummaryRequestState {
|
|
34
|
+
function getSummaryRequestState(field: HTMLElement, form: HTMLFormElement): SummaryRequestState {
|
|
31
35
|
const summaryTokenInput = field.querySelector('[data-formie-summary-token]') as HTMLInputElement | null;
|
|
32
36
|
const accessToken = summaryTokenInput?.value?.trim() || null;
|
|
33
37
|
|
|
34
38
|
return {
|
|
35
39
|
accessToken,
|
|
40
|
+
themeConfig: form.getAttribute(THEME_CONFIG_ATTR)?.trim() || null,
|
|
41
|
+
frontendTheme: form.getAttribute(FRONTEND_THEME_ATTR)?.trim() || null,
|
|
36
42
|
};
|
|
37
43
|
}
|
|
38
44
|
|
|
@@ -47,6 +53,14 @@ async function requestSummaryHtml(form: HTMLFormElement, state: SummaryRequestSt
|
|
|
47
53
|
formData.set('action', SUMMARY_ACTION);
|
|
48
54
|
formData.set('accessToken', state.accessToken);
|
|
49
55
|
|
|
56
|
+
if (state.themeConfig) {
|
|
57
|
+
formData.set('themeConfig', state.themeConfig);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (state.frontendTheme) {
|
|
61
|
+
formData.set('frontendTheme', state.frontendTheme);
|
|
62
|
+
}
|
|
63
|
+
|
|
50
64
|
return requestText(getSummaryRequestUrl(), {
|
|
51
65
|
method: 'POST',
|
|
52
66
|
body: formData,
|
|
@@ -101,7 +115,7 @@ function initSummaryField(field: HTMLElement, root: Element): () => void {
|
|
|
101
115
|
toggleThemeClasses(blocks, form, 'loading', false);
|
|
102
116
|
};
|
|
103
117
|
|
|
104
|
-
const initialState = getSummaryRequestState(field);
|
|
118
|
+
const initialState = getSummaryRequestState(field, form);
|
|
105
119
|
setLoadingState(!!initialState.accessToken);
|
|
106
120
|
|
|
107
121
|
const queueFetch = (): void => {
|
|
@@ -114,7 +128,7 @@ function initSummaryField(field: HTMLElement, root: Element): () => void {
|
|
|
114
128
|
};
|
|
115
129
|
|
|
116
130
|
const fetchSummary = debounce(async() => {
|
|
117
|
-
const state = getSummaryRequestState(field);
|
|
131
|
+
const state = getSummaryRequestState(field, form);
|
|
118
132
|
|
|
119
133
|
if (!getBlocks() || !state.accessToken) {
|
|
120
134
|
debug.warn('Missing state for fetch.', state);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import surveyPresentationsCss from '#theme-css/fields/_survey-presentations.css?inline';
|
|
2
|
+
|
|
3
|
+
import type { FormieModuleDefinition } from '#contracts/modules';
|
|
4
|
+
import { ensureModuleStyles } from '#modules/styles';
|
|
5
|
+
import { createDebug } from '#utils/debug';
|
|
6
|
+
|
|
7
|
+
const FIELD_SELECTOR = '[data-formie-likert-field-layout]';
|
|
8
|
+
const MODULE_ID = 'survey-likert';
|
|
9
|
+
const debug = createDebug('fields', 'survey-likert');
|
|
10
|
+
|
|
11
|
+
ensureModuleStyles(MODULE_ID, [surveyPresentationsCss]);
|
|
12
|
+
|
|
13
|
+
export const surveyLikertModule: FormieModuleDefinition = {
|
|
14
|
+
id: MODULE_ID,
|
|
15
|
+
kind: 'field',
|
|
16
|
+
match: (ctx) => {
|
|
17
|
+
return ctx.target instanceof HTMLElement && (
|
|
18
|
+
ctx.target.matches(FIELD_SELECTOR) ||
|
|
19
|
+
!!ctx.target.querySelector(FIELD_SELECTOR)
|
|
20
|
+
);
|
|
21
|
+
},
|
|
22
|
+
setup: async(ctx) => {
|
|
23
|
+
if (!(ctx.target instanceof HTMLElement)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const fields = ctx.target.matches(FIELD_SELECTOR)
|
|
28
|
+
? [ctx.target]
|
|
29
|
+
: Array.from(ctx.target.querySelectorAll(FIELD_SELECTOR)).filter((field): field is HTMLElement => {
|
|
30
|
+
return field instanceof HTMLElement;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
debug.log('Module setup.', {
|
|
34
|
+
fieldCount: fields.length,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
await ctx.emit('formie:module:survey-likert:init', {
|
|
38
|
+
count: fields.length,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
destroy: () => {
|
|
43
|
+
debug.log('Module destroy.', {
|
|
44
|
+
fieldCount: fields.length,
|
|
45
|
+
});
|
|
46
|
+
void ctx.emit('formie:module:survey-likert:destroy', {});
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
};
|