@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.
Files changed (104) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/chunks/_survey-presentations-CFsgToRT.js +4 -0
  3. package/dist/chunks/{address-finder-yXU5yMjG.js → address-finder-k7_BXaRe.js} +1 -1
  4. package/dist/chunks/{api-DMK8NSUI.js → api-BUb6P-xu.js} +57 -51
  5. package/dist/chunks/{api-CmwLRq_n.js → api-C8wyLC-s.js} +235 -193
  6. package/dist/chunks/{bpoint-DSjqRv63.js → bpoint-CwSw_p7A.js} +1 -1
  7. package/dist/chunks/{captcha-eu-CWJb64_j.js → captcha-eu-CGm8Lbrj.js} +2 -2
  8. package/dist/chunks/{conditions-DjZOtHfS.js → conditions-B6_nzpeE.js} +95 -83
  9. package/dist/chunks/{eway-D3ctTbEk.js → eway-B30BMlDp.js} +2 -2
  10. package/dist/chunks/{friendly-captcha-v1-ByXzENYZ.js → friendly-captcha-v1-C_2reOXr.js} +1 -1
  11. package/dist/chunks/{friendly-captcha-v2-BPdlmhQu.js → friendly-captcha-v2-CLSAbkqx.js} +1 -1
  12. package/dist/chunks/{go-cardless-BwASSYSK.js → go-cardless-BiF3iLel.js} +1 -1
  13. package/dist/chunks/{google-address-OhKilPWE.js → google-address-BH3Zhi06.js} +1 -1
  14. package/dist/chunks/{hcaptcha-CdCd9oEi.js → hcaptcha-CGLAmgjO.js} +2 -2
  15. package/dist/chunks/{loqate-Cp2zvwpE.js → loqate-CJPzBmSm.js} +1 -1
  16. package/dist/chunks/{mollie-COG-ko2D.js → mollie-D__SwtcH.js} +1 -1
  17. package/dist/chunks/{moneris-CMH8PlRc.js → moneris-KJS1BKx7.js} +1 -1
  18. package/dist/chunks/{opayo-D2jsR5vh.js → opayo-A_LIkc-v.js} +4 -4
  19. package/dist/chunks/{paddle-DxF88RfC.js → paddle-BjbOOb0m.js} +2 -2
  20. package/dist/chunks/{paypal-b5pnwrA2.js → paypal-CyjHIV8v.js} +2 -2
  21. package/dist/chunks/{payway-BJYu9DIR.js → payway-a_cyrr3j.js} +2 -2
  22. package/dist/chunks/{recaptcha-enterprise-BSwi8IVX.js → recaptcha-enterprise-XPId5-bS.js} +19 -20
  23. package/dist/chunks/{recaptcha-shared-B4zAescW.js → recaptcha-shared-CLYJvojk.js} +15 -3
  24. package/dist/chunks/{recaptcha-v2-checkbox-CnDQmsKa.js → recaptcha-v2-checkbox-Cy-0PEPc.js} +2 -2
  25. package/dist/chunks/{recaptcha-v2-invisible-Cz5uIlp-.js → recaptcha-v2-invisible-BwRH540O.js} +2 -2
  26. package/dist/chunks/recaptcha-v3-No4Ykrhk.js +28 -0
  27. package/dist/chunks/scripts-CbQ7agX3.js +30 -0
  28. package/dist/chunks/{snaptcha-vz1RnZ15.js → snaptcha-DKx98oon.js} +1 -1
  29. package/dist/chunks/{square-BP8RWAKB.js → square-Bga6WXHV.js} +2 -2
  30. package/dist/chunks/{stripe-MihN63uC.js → stripe-BlqVocl3.js} +2 -2
  31. package/dist/chunks/{summary-D3AjxpYN.js → summary-Dks0gXS3.js} +42 -38
  32. package/dist/chunks/survey-likert-DmJ3klhP.js +20 -0
  33. package/dist/chunks/survey-rank-DeUenftw.js +127 -0
  34. package/dist/chunks/survey-rating-BPUYDgBJ.js +88 -0
  35. package/dist/chunks/{turnstile-D_Pi1CKM.js → turnstile-DJakPzQD.js} +3 -3
  36. package/dist/css/formie-theme.css +1 -0
  37. package/dist/css/theme/fields/_survey-presentations.css +396 -0
  38. package/dist/index.js +651 -597
  39. package/dist/js/contracts/schema.d.ts +5 -0
  40. package/dist/js/contracts/schema.d.ts.map +1 -1
  41. package/dist/js/core/create-formie-client.d.ts.map +1 -1
  42. package/dist/js/core/error-aria-live.d.ts +6 -0
  43. package/dist/js/core/error-aria-live.d.ts.map +1 -0
  44. package/dist/js/core/page-client-event.d.ts +8 -4
  45. package/dist/js/core/page-client-event.d.ts.map +1 -1
  46. package/dist/js/core/submit-result-state.d.ts.map +1 -1
  47. package/dist/js/core/submit-result-ui.d.ts.map +1 -1
  48. package/dist/js/modules/captchas/factories.d.ts.map +1 -1
  49. package/dist/js/modules/captchas/host.d.ts +1 -0
  50. package/dist/js/modules/captchas/host.d.ts.map +1 -1
  51. package/dist/js/modules/captchas/recaptcha-enterprise.d.ts.map +1 -1
  52. package/dist/js/modules/captchas/recaptcha-shared.d.ts +1 -0
  53. package/dist/js/modules/captchas/recaptcha-shared.d.ts.map +1 -1
  54. package/dist/js/modules/captchas/recaptcha-v3.d.ts.map +1 -1
  55. package/dist/js/modules/captchas/turnstile.d.ts.map +1 -1
  56. package/dist/js/modules/fields/conditions/effects.d.ts.map +1 -1
  57. package/dist/js/modules/fields/index.d.ts.map +1 -1
  58. package/dist/js/modules/fields/summary.d.ts.map +1 -1
  59. package/dist/js/modules/fields/survey-likert.d.ts +3 -0
  60. package/dist/js/modules/fields/survey-likert.d.ts.map +1 -0
  61. package/dist/js/modules/fields/survey-rank.d.ts +3 -0
  62. package/dist/js/modules/fields/survey-rank.d.ts.map +1 -0
  63. package/dist/js/modules/fields/survey-rating.d.ts +3 -0
  64. package/dist/js/modules/fields/survey-rating.d.ts.map +1 -0
  65. package/dist/js/transport/forms-api.d.ts.map +1 -1
  66. package/dist/js/utils/form-started-at.d.ts +2 -0
  67. package/dist/js/utils/form-started-at.d.ts.map +1 -0
  68. package/dist/js/utils/scripts.d.ts.map +1 -1
  69. package/dist/js/utils/unload-warning.d.ts.map +1 -1
  70. package/dist/js/validation/enter-key-guard.d.ts +2 -0
  71. package/dist/js/validation/enter-key-guard.d.ts.map +1 -0
  72. package/dist/js/validation/types.d.ts +1 -0
  73. package/dist/js/validation/types.d.ts.map +1 -1
  74. package/dist/js/validation/validator.d.ts.map +1 -1
  75. package/package.json +2 -2
  76. package/src/css/formie-theme.css +1 -0
  77. package/src/css/theme/fields/_survey-presentations.css +396 -0
  78. package/src/js/contracts/schema.ts +6 -0
  79. package/src/js/core/create-formie-client.ts +21 -1
  80. package/src/js/core/error-aria-live.ts +45 -0
  81. package/src/js/core/page-client-event.ts +86 -18
  82. package/src/js/core/submit-result-state.ts +10 -2
  83. package/src/js/core/submit-result-ui.ts +8 -4
  84. package/src/js/modules/captchas/factories.ts +4 -0
  85. package/src/js/modules/captchas/host.ts +14 -2
  86. package/src/js/modules/captchas/recaptcha-enterprise.ts +14 -11
  87. package/src/js/modules/captchas/recaptcha-shared.ts +14 -0
  88. package/src/js/modules/captchas/recaptcha-v3.ts +7 -4
  89. package/src/js/modules/captchas/turnstile.ts +9 -3
  90. package/src/js/modules/fields/conditions/effects.ts +42 -0
  91. package/src/js/modules/fields/index.ts +3 -0
  92. package/src/js/modules/fields/summary.ts +17 -3
  93. package/src/js/modules/fields/survey-likert.ts +50 -0
  94. package/src/js/modules/fields/survey-rank.ts +385 -0
  95. package/src/js/modules/fields/survey-rating.ts +182 -0
  96. package/src/js/transport/forms-api.ts +1 -0
  97. package/src/js/utils/form-started-at.ts +25 -0
  98. package/src/js/utils/scripts.ts +16 -0
  99. package/src/js/utils/unload-warning.ts +1 -0
  100. package/src/js/validation/enter-key-guard.ts +63 -0
  101. package/src/js/validation/types.ts +1 -0
  102. package/src/js/validation/validator.ts +14 -5
  103. package/dist/chunks/recaptcha-v3-BtbbP111.js +0 -27
  104. 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
- messageContainer.setAttribute('aria-live', 'polite');
128
- messageContainer.setAttribute('aria-atomic', 'true');
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.setAttribute('aria-live', 'polite');
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
- if (provider.enterpriseType === 'score' || provider.enterpriseType === 'policy') {
98
- // Score and policy-based keys execute by site key and can resolve
99
- // directly to a token. We write that token into the shared transport
100
- // layer so the backend can validate it in the usual way.
101
- const token = await enterpriseApi.execute(provider.siteKey || '', { action: provider.action || 'submit' });
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
- if (typeof token === 'string' && token.trim() !== '') {
104
- services.tokens.write(token.trim());
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
- } else {
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
- const token = await api.execute(provider.siteKey || '', { action: provider.action || 'submit' });
44
+ await whenRecaptchaReady(api, async() => {
45
+ const token = await api.execute(provider.siteKey || '', { action: provider.action || 'submit' });
44
46
 
45
- if (typeof token === 'string' && token.trim() !== '') {
46
- services.tokens.write(token.trim());
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
- // Reset provider state whenever the widget is torn down so the DOM
127
- // transport layer and the provider widget stay in sync.
128
- api.reset(widget);
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
+ };