@10x-media/form-builder 0.1.0-beta.6 → 0.1.0-beta.7

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 (167) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/dist/actions/buildActionBlocks.js +12 -9
  3. package/dist/actions/buildActionBlocks.js.map +1 -1
  4. package/dist/actions/builtin/confirmation.js +13 -68
  5. package/dist/actions/builtin/confirmation.js.map +1 -1
  6. package/dist/actions/builtin/emailAction.d.ts +17 -0
  7. package/dist/actions/builtin/emailAction.js +83 -0
  8. package/dist/actions/builtin/emailAction.js.map +1 -0
  9. package/dist/actions/builtin/emailTeam.js +14 -74
  10. package/dist/actions/builtin/emailTeam.js.map +1 -1
  11. package/dist/actions/builtin/index.d.ts +9 -12
  12. package/dist/actions/builtin/index.js +11 -11
  13. package/dist/actions/builtin/index.js.map +1 -1
  14. package/dist/actions/defineAction.d.ts +2 -2
  15. package/dist/actions/defineAction.js.map +1 -1
  16. package/dist/actions/emailRecipients.js +75 -31
  17. package/dist/actions/emailRecipients.js.map +1 -1
  18. package/dist/actions/registry.d.ts +2 -1
  19. package/dist/actions/registry.js +2 -6
  20. package/dist/actions/registry.js.map +1 -1
  21. package/dist/actions/task.js +5 -3
  22. package/dist/actions/task.js.map +1 -1
  23. package/dist/calc/evaluate.js +1 -2
  24. package/dist/calc/evaluate.js.map +1 -1
  25. package/dist/calc/normalizeCalc.js +1 -2
  26. package/dist/calc/normalizeCalc.js.map +1 -1
  27. package/dist/calc/types.js.map +1 -1
  28. package/dist/collections/buttonFields.js +1 -1
  29. package/dist/collections/formSubmissions.js +2 -1
  30. package/dist/collections/formSubmissions.js.map +1 -1
  31. package/dist/collections/forms.js +30 -99
  32. package/dist/collections/forms.js.map +1 -1
  33. package/dist/collections/formsEndpoints.js +101 -0
  34. package/dist/collections/formsEndpoints.js.map +1 -0
  35. package/dist/conditions/evaluate.js +1 -1
  36. package/dist/conditions/evaluate.js.map +1 -1
  37. package/dist/conditions/fieldTypes.d.ts +3 -1
  38. package/dist/conditions/fieldTypes.js +4 -1
  39. package/dist/conditions/fieldTypes.js.map +1 -1
  40. package/dist/conditions/normalizeConditions.js +18 -5
  41. package/dist/conditions/normalizeConditions.js.map +1 -1
  42. package/dist/consent/captureConsent.d.ts +7 -0
  43. package/dist/consent/captureConsent.js +7 -0
  44. package/dist/consent/captureConsent.js.map +1 -1
  45. package/dist/consent/consentSourcesField.d.ts +4 -0
  46. package/dist/consent/consentSourcesField.js +5 -1
  47. package/dist/consent/consentSourcesField.js.map +1 -1
  48. package/dist/consent/resolveConsentEntries.d.ts +1 -1
  49. package/dist/consent/resolveConsentEntries.js +1 -4
  50. package/dist/consent/resolveConsentEntries.js.map +1 -1
  51. package/dist/consent/resolveConsentSourcesRequest.js +0 -1
  52. package/dist/consent/resolveConsentSourcesRequest.js.map +1 -1
  53. package/dist/consent/resolveConsentStatements.d.ts +1 -1
  54. package/dist/consent/resolveConsentStatements.js.map +1 -1
  55. package/dist/consent/types.d.ts +9 -1
  56. package/dist/email/departmentsField.js +1 -1
  57. package/dist/exports/react.d.ts +3 -2
  58. package/dist/exports/react.js +3 -2
  59. package/dist/exports/rsc.js +1 -1
  60. package/dist/exports/types.d.ts +1 -1
  61. package/dist/fields/builtin/email.js +1 -1
  62. package/dist/fields/builtin/email.js.map +1 -1
  63. package/dist/fields/builtin/message.js +1 -1
  64. package/dist/fields/builtin/repeater.js +1 -1
  65. package/dist/fields/builtin/select.js +1 -1
  66. package/dist/fields/registry.d.ts +2 -1
  67. package/dist/fields/registry.js +2 -6
  68. package/dist/fields/registry.js.map +1 -1
  69. package/dist/fields/sharedConfig.js +1 -1
  70. package/dist/flow/normalizeFlow.js +7 -3
  71. package/dist/flow/normalizeFlow.js.map +1 -1
  72. package/dist/index.d.ts +4 -201
  73. package/dist/index.js +11 -4
  74. package/dist/index.js.map +1 -1
  75. package/dist/options.d.ts +228 -0
  76. package/dist/plugin/applyRegistryConfig.d.ts +11 -0
  77. package/dist/plugin/applyRegistryConfig.js +20 -0
  78. package/dist/plugin/applyRegistryConfig.js.map +1 -0
  79. package/dist/plugin/registerCollections.js +2 -1
  80. package/dist/plugin/registerCollections.js.map +1 -1
  81. package/dist/poll/buildPollOptionSourceFields.js +2 -2
  82. package/dist/poll/buildPollOptionSourceFields.js.map +1 -1
  83. package/dist/poll/definePollOptionSource.d.ts +2 -2
  84. package/dist/poll/definePollOptionSource.js.map +1 -1
  85. package/dist/poll/pollTypeRegistry.js +2 -4
  86. package/dist/poll/pollTypeRegistry.js.map +1 -1
  87. package/dist/poll/registry.d.ts +2 -1
  88. package/dist/poll/registry.js +2 -6
  89. package/dist/poll/registry.js.map +1 -1
  90. package/dist/poll/resolvePollOptionsRequest.js +0 -1
  91. package/dist/poll/resolvePollOptionsRequest.js.map +1 -1
  92. package/dist/prefill/valuesFromSearchParams.js +2 -7
  93. package/dist/prefill/valuesFromSearchParams.js.map +1 -1
  94. package/dist/react/Form.d.ts +3 -1
  95. package/dist/react/Form.js +93 -155
  96. package/dist/react/Form.js.map +1 -1
  97. package/dist/react/FormContext.d.ts +5 -1
  98. package/dist/react/FormContext.js.map +1 -1
  99. package/dist/react/FormFields.d.ts +18 -0
  100. package/dist/react/FormFields.js +114 -0
  101. package/dist/react/FormFields.js.map +1 -0
  102. package/dist/react/FormLayout.d.ts +4 -2
  103. package/dist/react/FormLayout.js +3 -2
  104. package/dist/react/FormLayout.js.map +1 -1
  105. package/dist/react/Poll.js +13 -3
  106. package/dist/react/Poll.js.map +1 -1
  107. package/dist/react/presentation/DialogSurface.js +1 -4
  108. package/dist/react/presentation/DialogSurface.js.map +1 -1
  109. package/dist/react/renderers/calculation.js +1 -3
  110. package/dist/react/renderers/calculation.js.map +1 -1
  111. package/dist/react/renderers/checkbox.js +1 -3
  112. package/dist/react/renderers/checkbox.js.map +1 -1
  113. package/dist/react/renderers/consent.js +2 -3
  114. package/dist/react/renderers/consent.js.map +1 -1
  115. package/dist/react/renderers/date.js +1 -3
  116. package/dist/react/renderers/date.js.map +1 -1
  117. package/dist/react/renderers/email.js +1 -3
  118. package/dist/react/renderers/email.js.map +1 -1
  119. package/dist/react/renderers/file.js +6 -7
  120. package/dist/react/renderers/file.js.map +1 -1
  121. package/dist/react/renderers/number.js +1 -3
  122. package/dist/react/renderers/number.js.map +1 -1
  123. package/dist/react/renderers/region.js +1 -3
  124. package/dist/react/renderers/region.js.map +1 -1
  125. package/dist/react/renderers/repeater.js +20 -4
  126. package/dist/react/renderers/repeater.js.map +1 -1
  127. package/dist/react/renderers/select.js +1 -3
  128. package/dist/react/renderers/select.js.map +1 -1
  129. package/dist/react/renderers/text.js +1 -3
  130. package/dist/react/renderers/text.js.map +1 -1
  131. package/dist/react/renderers/textarea.js +1 -3
  132. package/dist/react/renderers/textarea.js.map +1 -1
  133. package/dist/spam/constants.js +7 -1
  134. package/dist/spam/constants.js.map +1 -1
  135. package/dist/spam/resolveSpam.js +2 -1
  136. package/dist/spam/resolveSpam.js.map +1 -1
  137. package/dist/spam/spamGuard.js +22 -6
  138. package/dist/spam/spamGuard.js.map +1 -1
  139. package/dist/spam/types.d.ts +17 -2
  140. package/dist/submissions/coerceBoolean.js +14 -0
  141. package/dist/submissions/coerceBoolean.js.map +1 -0
  142. package/dist/submissions/runSubmission.js +7 -7
  143. package/dist/submissions/runSubmission.js.map +1 -1
  144. package/dist/submissions/validateSubmission.js +3 -2
  145. package/dist/submissions/validateSubmission.js.map +1 -1
  146. package/dist/translations/de.js +10 -0
  147. package/dist/translations/de.js.map +1 -1
  148. package/dist/translations/en.js +10 -0
  149. package/dist/translations/en.js.map +1 -1
  150. package/dist/translations/keys.d.ts +10 -0
  151. package/dist/translations/keys.js +10 -0
  152. package/dist/translations/keys.js.map +1 -1
  153. package/dist/translations/server.js +8 -1
  154. package/dist/translations/server.js.map +1 -1
  155. package/dist/uploads/captureFileRef.d.ts +10 -3
  156. package/dist/uploads/captureFileRef.js +18 -9
  157. package/dist/uploads/captureFileRef.js.map +1 -1
  158. package/dist/validation/builtin/email.js +1 -1
  159. package/dist/validation/builtin/email.js.map +1 -1
  160. package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
  161. package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
  162. package/dist/validation/emailPattern.js +10 -0
  163. package/dist/validation/emailPattern.js.map +1 -0
  164. package/dist/validation/registry.d.ts +2 -1
  165. package/dist/validation/registry.js +2 -9
  166. package/dist/validation/registry.js.map +1 -1
  167. package/package.json +4 -4
@@ -1,9 +1,9 @@
1
1
  "use client";
2
- import { interpolate } from "../recall/interpolate.js";
3
2
  import { keys } from "../translations/keys.js";
3
+ import { interpolate } from "../recall/interpolate.js";
4
4
  import { serializeBody } from "../actions/body/serializeBody.js";
5
5
  import { noopEventSink } from "../events/noopSink.js";
6
- import { CAPTCHA_TOKEN_KEY } from "../spam/constants.js";
6
+ import { CAPTCHA_TOKEN_KEY, HONEYPOT_VALUE_KEY } from "../spam/constants.js";
7
7
  import { fieldKey, isNamedField } from "../fields/fieldKey.js";
8
8
  import { calcExpressionOf, computeCalcFields } from "../calc/computeCalcFields.js";
9
9
  import { evaluateCondition } from "../conditions/evaluate.js";
@@ -16,82 +16,24 @@ import { makeTranslate } from "../translations/makeTranslate.js";
16
16
  import { emitFormEvent } from "./events.js";
17
17
  import { FormContext } from "./FormContext.js";
18
18
  import { FormControls } from "./FormControls.js";
19
- import { FormLayout, widthProps } from "./FormLayout.js";
19
+ import { FormFields } from "./FormFields.js";
20
20
  import { Honeypot } from "./Honeypot.js";
21
21
  import { defaultPresentations } from "./presentation/presentations.js";
22
22
  import { resolvePresentations } from "./presentation/registry.js";
23
- import { applyRecall } from "./recall.js";
24
23
  import { resolveRenderers } from "./registry.js";
25
- import { useField } from "./useField.js";
26
24
  import { defaultRenderers } from "./renderers/index.js";
27
25
  import { buildFieldTypeRegistry, buildValidationRuleRegistry, visibleFields } from "./resolveForm.js";
28
26
  import { formReducer, initialFormState, seedFieldValues } from "./state.js";
29
27
  import { submitForm } from "./submitForm.js";
30
28
  import { validateFieldValue } from "./validateField.js";
31
29
  import { jsx, jsxs } from "react/jsx-runtime";
32
- import { createElement, useCallback, useEffect, useId, useMemo, useReducer, useRef, useState } from "react";
30
+ import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
33
31
  //#region src/react/Form.tsx
34
32
  const isEmpty = (value) => value == null || value === "" || Array.isArray(value) && value.length === 0;
35
33
  /** A stored button label counts only when it is a non-empty string; anything else falls through. */
36
34
  const storedLabel = (value) => typeof value === "string" && value.length > 0 ? value : void 0;
37
- const FIELD_WIDTHS = new Set([
38
- "full",
39
- "half",
40
- "third",
41
- "twoThirds"
42
- ]);
43
- const FieldHost = ({ field, renderer, locale, t }) => {
44
- const id = useId();
45
- const { value, errors, setValue, onBlur } = useField(field.name);
46
- return createElement(renderer, {
47
- field,
48
- id,
49
- name: field.name,
50
- value,
51
- onChange: setValue,
52
- onBlur,
53
- errors,
54
- required: Boolean(field.required),
55
- locale,
56
- t
57
- });
58
- };
59
- /** Hosts a derived (calc) field: read-only, value supplied from `effectiveValues`, never bound via `useField`. */
60
- const CalcFieldHost = ({ field, renderer, value, locale, t }) => {
61
- return createElement(renderer, {
62
- field,
63
- id: useId(),
64
- name: field.name,
65
- value,
66
- onChange: () => {},
67
- onBlur: () => {},
68
- errors: [],
69
- required: false,
70
- disabled: true,
71
- locale,
72
- t
73
- });
74
- };
75
- /**
76
- * Hosts a nameless (bare) display block, e.g. a message: no `useField` binding at all. The
77
- * renderer reads only the instance and the form context; `name` is the row key for consistency.
78
- */
79
- const StaticFieldHost = ({ field, renderer, locale, t }) => {
80
- return createElement(renderer, {
81
- field,
82
- id: useId(),
83
- name: fieldKey(field),
84
- value: void 0,
85
- onChange: () => {},
86
- onBlur: () => {},
87
- errors: [],
88
- required: false,
89
- locale,
90
- t
91
- });
92
- };
93
35
  /** The headless form controller: state, progressive client validation, conditional visibility, submission, events. */
94
- const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSuccess, onError, events, t, locale = "en", layout, submitLabel, nextLabel, prevLabel, closeLabel = "Close", successMessage = "Thank you.", presentation, presentations, onClose, title, initialValues, honeypot, captchaToken, children, className, renderSubmit, renderNext, renderBack, submitButtonClassName, nextButtonClassName, backButtonClassName }) => {
36
+ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSuccess, onError, events, t, locale = "en", layout, submitLabel, nextLabel, prevLabel, closeLabel, successMessage, presentation, presentations, onClose, title, initialValues, honeypot, captchaToken, children, header, className, renderSubmit, renderNext, renderBack, submitButtonClassName, nextButtonClassName, backButtonClassName }) => {
95
37
  const honeypotName = honeypot === false ? null : honeypot?.name ?? "website";
96
38
  const honeypotRef = useRef(null);
97
39
  const registry = useMemo(() => buildFieldTypeRegistry(fieldTypes), [fieldTypes]);
@@ -101,6 +43,8 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
101
43
  const activePresentation = typeof presentation === "object" ? presentation : presentationRegistry.get(presentation ?? "page") ?? presentationRegistry.get("page") ?? defaultPresentationDescriptors.page;
102
44
  const fieldsByName = useMemo(() => new Map(form.fields.filter(isNamedField).map((field) => [field.name, field])), [form.fields]);
103
45
  const translate = useMemo(() => t ?? makeTranslate(en), [t]);
46
+ const resolvedCloseLabel = closeLabel ?? translate(keys.formClose);
47
+ const resolvedSuccessMessage = successMessage ?? translate(keys.formSuccess);
104
48
  const docButtons = form.buttons;
105
49
  const labels = useMemo(() => ({
106
50
  prev: prevLabel ?? storedLabel(docButtons?.prevLabel) ?? translate(keys.formBack),
@@ -141,6 +85,7 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
141
85
  const startedRef = useRef(false);
142
86
  const submittedRef = useRef(false);
143
87
  const submittingRef = useRef(false);
88
+ const advancingRef = useRef(false);
144
89
  const flowRef = useRef(flow);
145
90
  flowRef.current = flow;
146
91
  const dispatch = useCallback((action) => {
@@ -211,46 +156,86 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
211
156
  value: recall(field.name)
212
157
  }));
213
158
  const stepVisible = (flow && currentStepId ? stepFieldNames(flow, currentStepId) : []).map((key) => visible.find((field) => fieldKey(field) === key)).filter((field) => Boolean(field));
159
+ const validateRepeaterSubFields = async (repeaters) => {
160
+ const errors = {};
161
+ for (const field of repeaters.filter((f) => f.blockType === "repeater" && isNamedField(f))) {
162
+ const rows = Array.isArray(effectiveValues[field.name]) ? effectiveValues[field.name] : [];
163
+ const subFields = (Array.isArray(field.subFields) ? field.subFields : []).filter(isNamedField);
164
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
165
+ const row = rows[rowIndex] ?? {};
166
+ for (const subField of subFields) {
167
+ if (!evaluateCondition(subField.visibleWhen, row)) continue;
168
+ if (!evaluateCondition(subField.validateWhen, row)) continue;
169
+ const subResult = await validateFieldValue({
170
+ field: subField,
171
+ value: row[subField.name],
172
+ registry,
173
+ ruleRegistry,
174
+ answers: row,
175
+ locale,
176
+ t: translate
177
+ });
178
+ const compositeKey = `${field.name}[${rowIndex}].${subField.name}`;
179
+ if (subResult.errors.length > 0) errors[compositeKey] = subResult.errors;
180
+ }
181
+ }
182
+ }
183
+ return errors;
184
+ };
214
185
  const goNext = async () => {
215
- if (!flow || !currentStepId) return;
216
- const results = await Promise.all(stepVisible.filter((field) => !calcExpressionOf(field)).filter((field) => registry.get(field.blockType)?.value !== "none").filter(isNamedField).filter((field) => evaluateCondition(field.validateWhen, effectiveValues)).map(async (field) => ({
217
- field,
218
- ...await validateFieldValue({
186
+ if (!flow || !currentStepId || advancingRef.current) return;
187
+ advancingRef.current = true;
188
+ try {
189
+ const results = await Promise.all(stepVisible.filter((field) => !calcExpressionOf(field)).filter((field) => registry.get(field.blockType)?.value !== "none").filter(isNamedField).filter((field) => evaluateCondition(field.validateWhen, effectiveValues)).map(async (field) => ({
219
190
  field,
220
- value: effectiveValues[field.name],
221
- registry,
222
- ruleRegistry,
223
- answers: effectiveValues,
224
- locale,
225
- t: translate
226
- })
227
- })));
228
- let hasError = false;
229
- for (const result of results) {
230
- rawDispatch({
231
- type: "TOUCH",
232
- name: result.field.name
191
+ ...await validateFieldValue({
192
+ field,
193
+ value: effectiveValues[field.name],
194
+ registry,
195
+ ruleRegistry,
196
+ answers: effectiveValues,
197
+ locale,
198
+ t: translate
199
+ })
200
+ })));
201
+ let hasError = false;
202
+ for (const result of results) {
203
+ rawDispatch({
204
+ type: "TOUCH",
205
+ name: result.field.name
206
+ });
207
+ rawDispatch({
208
+ type: "SET_FIELD_ISSUES",
209
+ name: result.field.name,
210
+ errors: result.errors
211
+ });
212
+ if (result.errors.length > 0) hasError = true;
213
+ }
214
+ const repeaterErrors = await validateRepeaterSubFields(stepVisible);
215
+ for (const [compositeKey, errs] of Object.entries(repeaterErrors)) {
216
+ rawDispatch({
217
+ type: "SET_FIELD_ISSUES",
218
+ name: compositeKey,
219
+ errors: errs
220
+ });
221
+ hasError = true;
222
+ }
223
+ if (hasError) return;
224
+ const next = resolveNextStepId(flow, currentStepId, effectiveValues);
225
+ if (!next) return;
226
+ emitFormEvent(sinkRef.current, formIdRef.current, {
227
+ type: "step.completed",
228
+ stepId: currentStepId
233
229
  });
234
- rawDispatch({
235
- type: "SET_FIELD_ISSUES",
236
- name: result.field.name,
237
- errors: result.errors
230
+ setHistory((prev) => [...prev, currentStepId]);
231
+ setCurrentStepId(next);
232
+ emitFormEvent(sinkRef.current, formIdRef.current, {
233
+ type: "step.viewed",
234
+ stepId: next
238
235
  });
239
- if (result.errors.length > 0) hasError = true;
236
+ } finally {
237
+ advancingRef.current = false;
240
238
  }
241
- if (hasError) return;
242
- const next = resolveNextStepId(flow, currentStepId, effectiveValues);
243
- if (!next) return;
244
- emitFormEvent(sinkRef.current, formIdRef.current, {
245
- type: "step.completed",
246
- stepId: currentStepId
247
- });
248
- setHistory((prev) => [...prev, currentStepId]);
249
- setCurrentStepId(next);
250
- emitFormEvent(sinkRef.current, formIdRef.current, {
251
- type: "step.viewed",
252
- stepId: next
253
- });
254
239
  };
255
240
  const goBack = () => {
256
241
  const prev = history[history.length - 1];
@@ -306,28 +291,7 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
306
291
  message: firstError
307
292
  });
308
293
  }
309
- for (const field of visible.filter((f) => f.blockType === "repeater" && isNamedField(f))) {
310
- const rows = Array.isArray(effectiveValues[field.name]) ? effectiveValues[field.name] : [];
311
- const subFields = (Array.isArray(field.subFields) ? field.subFields : []).filter(isNamedField);
312
- for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
313
- const row = rows[rowIndex] ?? {};
314
- for (const subField of subFields) {
315
- if (!evaluateCondition(subField.visibleWhen, row)) continue;
316
- if (!evaluateCondition(subField.validateWhen, row)) continue;
317
- const subResult = await validateFieldValue({
318
- field: subField,
319
- value: row[subField.name],
320
- registry,
321
- ruleRegistry,
322
- answers: row,
323
- locale,
324
- t: translate
325
- });
326
- const compositeKey = `${field.name}[${rowIndex}].${subField.name}`;
327
- if (subResult.errors.length > 0) errors[compositeKey] = subResult.errors;
328
- }
329
- }
330
- }
294
+ Object.assign(errors, await validateRepeaterSubFields(visible));
331
295
  rawDispatch({
332
296
  type: "SET_ALL_ISSUES",
333
297
  errors
@@ -341,7 +305,7 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
341
305
  if (honeypotName) {
342
306
  const decoy = honeypotRef.current?.value ?? "";
343
307
  if (decoy !== "") values.push({
344
- field: honeypotName,
308
+ field: HONEYPOT_VALUE_KEY,
345
309
  value: decoy
346
310
  });
347
311
  }
@@ -374,7 +338,7 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
374
338
  type: "SET_ALL_ISSUES",
375
339
  errors: result.fieldErrors
376
340
  });
377
- const message = result.message ?? "Submission failed";
341
+ const message = result.message ?? translate(keys.formSubmitFailed);
378
342
  rawDispatch({
379
343
  type: "SUBMIT_ERROR",
380
344
  message
@@ -410,7 +374,9 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
410
374
  rendererRegistry,
411
375
  labels,
412
376
  t: translate,
413
- effectiveValues
377
+ effectiveValues,
378
+ recall,
379
+ renderedFields: (flow ? stepVisible : visible).filter((field) => field.hidden !== true && field.calcDisplay !== false)
414
380
  };
415
381
  const PresentationWrapper = activePresentation.Wrapper;
416
382
  const wrap = (content) => PresentationWrapper ? /* @__PURE__ */ jsx(PresentationWrapper, {
@@ -418,7 +384,7 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
418
384
  open: true,
419
385
  onClose: handleClose,
420
386
  title,
421
- closeLabel,
387
+ closeLabel: resolvedCloseLabel,
422
388
  children: content
423
389
  }) : content;
424
390
  if (children !== void 0) return /* @__PURE__ */ jsx(FormContext.Provider, {
@@ -454,11 +420,10 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
454
420
  className: cn("fb-form__success", className),
455
421
  "data-fb-presentation": activePresentation.name,
456
422
  "data-fb-density": activePresentation.density,
457
- children: interpolate(successMessage, recall)
423
+ children: interpolate(resolvedSuccessMessage, recall)
458
424
  }))
459
425
  });
460
426
  }
461
- const rendered = (flow ? stepVisible : visible).filter((field) => field.hidden !== true && field.calcDisplay !== false);
462
427
  return /* @__PURE__ */ jsx(FormContext.Provider, {
463
428
  value: contextValue,
464
429
  children: wrap(/* @__PURE__ */ jsxs("form", {
@@ -472,35 +437,8 @@ const Form = ({ form, fieldTypes, rules, renderers, apiRoute, onSubmit, onSucces
472
437
  name: honeypotName,
473
438
  inputRef: honeypotRef
474
439
  }) : null,
475
- /* @__PURE__ */ jsx(FormLayout, {
476
- enabled: layout !== false,
477
- children: rendered.map((field) => {
478
- const renderer = rendererRegistry.get(field.blockType);
479
- if (!renderer) return null;
480
- const width = typeof field.width === "string" && FIELD_WIDTHS.has(field.width) ? field.width : void 0;
481
- const recalledField = applyRecall(field, recall);
482
- return /* @__PURE__ */ jsx("div", {
483
- ...widthProps(width),
484
- children: !isNamedField(recalledField) ? /* @__PURE__ */ jsx(StaticFieldHost, {
485
- field: recalledField,
486
- renderer,
487
- locale,
488
- t: translate
489
- }) : calcExpressionOf(recalledField) ? /* @__PURE__ */ jsx(CalcFieldHost, {
490
- field: recalledField,
491
- renderer,
492
- value: effectiveValues[recalledField.name],
493
- locale,
494
- t: translate
495
- }) : /* @__PURE__ */ jsx(FieldHost, {
496
- field: recalledField,
497
- renderer,
498
- locale,
499
- t: translate
500
- })
501
- }, fieldKey(field));
502
- })
503
- }),
440
+ header,
441
+ /* @__PURE__ */ jsx(FormFields, { layout }),
504
442
  state.submitError ? /* @__PURE__ */ jsx("p", {
505
443
  role: "alert",
506
444
  className: "fb-form__submit-error",
@@ -1 +1 @@
1
- {"version":3,"file":"Form.js","names":[],"sources":["../../src/react/Form.tsx"],"sourcesContent":["'use client'\n\nimport {\n\tcreateElement,\n\ttype FormEvent as ReactFormEvent,\n\ttype ReactNode,\n\tuseCallback,\n\tuseEffect,\n\tuseId,\n\tuseMemo,\n\tuseReducer,\n\tuseRef,\n\tuseState,\n} from 'react'\nimport { serializeBody } from '../actions/body/serializeBody'\nimport { calcExpressionOf, computeCalcFields } from '../calc/computeCalcFields'\nimport { evaluateCondition } from '../conditions/evaluate'\nimport { noopEventSink } from '../events/noopSink'\nimport type { FormEventSink } from '../events/types'\nimport { fieldKey, isNamedField, type NamedFormFieldInstance } from '../fields/fieldKey'\nimport type { AnyFormFieldDefinition } from '../fields/types'\nimport { firstStepId, isTerminalStepId, resolveNextStepId, stepFieldNames } from '../flow/engine'\nimport type {\n\tFormButtonSettings,\n\tFormDocument,\n\tFormPollSettings,\n\tFormResponseSettings,\n} from '../form/types'\nimport {\n\tDEFAULT_PRESENTATION_NAME,\n\tdefaultPresentationDescriptors,\n} from '../presentations/defaults'\nimport { interpolate } from '../recall/interpolate'\nimport { buildRecallResolver, descriptorsFor } from '../recall/resolver'\nimport { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD } from '../spam/constants'\nimport type { FormFieldInstance, SubmissionValue } from '../submissions/types'\nimport { en } from '../translations/en'\nimport { keys } from '../translations/keys'\nimport { makeTranslate } from '../translations/makeTranslate'\nimport type { AnyValidationRuleDefinition } from '../validation/types'\nimport { cn } from './cn'\nimport type { FieldRenderer, RendererTranslate } from './contract'\nimport { emitFormEvent } from './events'\nimport { FormContext, type FormContextValue, type FormStepInfo } from './FormContext'\nimport {\n\ttype BackButtonRenderProps,\n\tFormControls,\n\ttype NextButtonRenderProps,\n\ttype SubmitButtonRenderProps,\n} from './FormControls'\nimport { type FieldWidth, FormLayout, widthProps } from './FormLayout'\nimport { Honeypot } from './Honeypot'\nimport { defaultPresentations } from './presentation/presentations'\nimport { type PresentationsConfig, resolvePresentations } from './presentation/registry'\nimport type { FormPresentation } from './presentation/types'\nimport { applyRecall } from './recall'\nimport { type RenderersConfig, resolveRenderers } from './registry'\nimport { defaultRenderers } from './renderers'\nimport { buildFieldTypeRegistry, buildValidationRuleRegistry, visibleFields } from './resolveForm'\nimport {\n\ttype FieldErrors,\n\ttype FormAction,\n\tformReducer,\n\tinitialFormState,\n\tseedFieldValues,\n} from './state'\nimport { type SubmitFormResult, type SubmitHandler, submitForm } from './submitForm'\nimport { useField } from './useField'\nimport { validateFieldValue } from './validateField'\n\nexport type {\n\tBackButtonRenderProps,\n\tNextButtonRenderProps,\n\tSubmitButtonRenderProps,\n} from './FormControls'\n// FormResponseSettings, FormButtonSettings, FormPollSettings, and FormDocument live in\n// `../form/types` (no 'use client') so server code (e.g. `toFormDocument` in a Server Component)\n// can use them without pulling in this client module. Re-exported here so `./react` and existing\n// `from './Form'` imports keep working unchanged.\nexport type { FormButtonSettings, FormDocument, FormPollSettings, FormResponseSettings }\n\nexport type FormProps = {\n\tform: FormDocument\n\tfieldTypes?: AnyFormFieldDefinition[]\n\trules?: AnyValidationRuleDefinition[]\n\trenderers?: RenderersConfig\n\tapiRoute?: string\n\tonSubmit?: SubmitHandler\n\tonSuccess?: (submissionId?: string) => void\n\tonError?: (message: string) => void\n\tevents?: FormEventSink\n\tt?: RendererTranslate\n\tlocale?: string\n\tlayout?: boolean\n\t/** Submit button label. Precedence: this prop, then the form's `buttons.submitLabel`, then the translated default. */\n\tsubmitLabel?: string\n\t/** \"Next\" button label for multi-step forms. Precedence: this prop, then the form's `buttons.nextLabel`, then the translated default. */\n\tnextLabel?: string\n\t/** \"Back\" button label for multi-step forms. Precedence: this prop, then the form's `buttons.prevLabel`, then the translated default. */\n\tprevLabel?: string\n\t/** Label for the overlay close control (modal/drawer). */\n\tcloseLabel?: string\n\tsuccessMessage?: string\n\t/** Active presentation: a name into the registry or an inline presentation. Defaults to `'page'` when omitted. */\n\tpresentation?: string | FormPresentation\n\t/** Per-render presentation overrides merged onto the defaults (add, replace, or `false` to remove). */\n\tpresentations?: PresentationsConfig\n\t/** Invoked when an overlay presentation dismisses (close button, Escape, outside click, or `dismissOnSuccess`). */\n\tonClose?: () => void\n\t/**\n\t * Accessible name for an overlay surface (modal/drawer). Hosts choosing between a trigger\n\t * label and the form's own admin title should prefer `form.title` when set, falling back to\n\t * their own label otherwise.\n\t */\n\ttitle?: string\n\t/** Seed initial field values (e.g. from `valuesFromSearchParams`). Still validated on submit. */\n\tinitialValues?: Record<string, unknown>\n\t/** Honeypot decoy (on by default). `false` removes it; `{ name }` matches a customized server `spam.honeypot.fieldName`. */\n\thoneypot?: false | { name?: string }\n\t/** A token from your captcha widget; verified server-side when a captcha provider is configured. */\n\tcaptchaToken?: string\n\t/** Custom layout: render fields with `useField`/`useFormState` instead of the auto-rendered field loop. */\n\tchildren?: ReactNode\n\t/** Additional CSS class names applied to the root `<form>` element (and the success node). */\n\tclassName?: string\n\t/** Replace the default submit button entirely. Receives the resolved label and submitting state. */\n\trenderSubmit?: (props: SubmitButtonRenderProps) => ReactNode\n\t/** Replace the default \"Next\" button in multi-step forms. */\n\trenderNext?: (props: NextButtonRenderProps) => ReactNode\n\t/** Replace the default \"Back\" button in multi-step forms. */\n\trenderBack?: (props: BackButtonRenderProps) => ReactNode\n\t/** CSS class forwarded to the default submit `<button>`. Ignored when `renderSubmit` is provided. */\n\tsubmitButtonClassName?: string\n\t/** CSS class forwarded to the default \"Next\" `<button>`. Ignored when `renderNext` is provided. */\n\tnextButtonClassName?: string\n\t/** CSS class forwarded to the default \"Back\" `<button>`. Ignored when `renderBack` is provided. */\n\tbackButtonClassName?: string\n}\n\nconst isEmpty = (value: unknown): boolean =>\n\tvalue == null || value === '' || (Array.isArray(value) && value.length === 0)\n\n/** A stored button label counts only when it is a non-empty string; anything else falls through. */\nconst storedLabel = (value: unknown): string | undefined =>\n\ttypeof value === 'string' && value.length > 0 ? value : undefined\n\nconst FIELD_WIDTHS = new Set<string>(['full', 'half', 'third', 'twoThirds'])\n\ntype FieldHostProps = {\n\tfield: NamedFormFieldInstance\n\trenderer: FieldRenderer\n\tlocale: string\n\tt: RendererTranslate\n}\n\nconst FieldHost = ({ field, renderer, locale, t }: FieldHostProps) => {\n\tconst id = useId()\n\tconst { value, errors, setValue, onBlur } = useField(field.name)\n\treturn createElement(renderer, {\n\t\tfield,\n\t\tid,\n\t\tname: field.name,\n\t\tvalue,\n\t\tonChange: setValue,\n\t\tonBlur,\n\t\terrors,\n\t\trequired: Boolean(field.required),\n\t\tlocale,\n\t\tt,\n\t})\n}\n\ntype CalcFieldHostProps = FieldHostProps & { value: unknown }\n\n/** Hosts a derived (calc) field: read-only, value supplied from `effectiveValues`, never bound via `useField`. */\nconst CalcFieldHost = ({ field, renderer, value, locale, t }: CalcFieldHostProps) => {\n\tconst id = useId()\n\treturn createElement(renderer, {\n\t\tfield,\n\t\tid,\n\t\tname: field.name,\n\t\tvalue,\n\t\tonChange: () => {},\n\t\tonBlur: () => {},\n\t\terrors: [],\n\t\trequired: false,\n\t\tdisabled: true,\n\t\tlocale,\n\t\tt,\n\t})\n}\n\ntype StaticFieldHostProps = Omit<FieldHostProps, 'field'> & { field: FormFieldInstance }\n\n/**\n * Hosts a nameless (bare) display block, e.g. a message: no `useField` binding at all. The\n * renderer reads only the instance and the form context; `name` is the row key for consistency.\n */\nconst StaticFieldHost = ({ field, renderer, locale, t }: StaticFieldHostProps) => {\n\tconst id = useId()\n\treturn createElement(renderer, {\n\t\tfield,\n\t\tid,\n\t\tname: fieldKey(field),\n\t\tvalue: undefined,\n\t\tonChange: () => {},\n\t\tonBlur: () => {},\n\t\terrors: [],\n\t\trequired: false,\n\t\tlocale,\n\t\tt,\n\t})\n}\n\n/** The headless form controller: state, progressive client validation, conditional visibility, submission, events. */\nexport const Form = ({\n\tform,\n\tfieldTypes,\n\trules,\n\trenderers,\n\tapiRoute,\n\tonSubmit,\n\tonSuccess,\n\tonError,\n\tevents,\n\tt,\n\tlocale = 'en',\n\tlayout,\n\tsubmitLabel,\n\tnextLabel,\n\tprevLabel,\n\tcloseLabel = 'Close',\n\tsuccessMessage = 'Thank you.',\n\tpresentation,\n\tpresentations,\n\tonClose,\n\ttitle,\n\tinitialValues,\n\thoneypot,\n\tcaptchaToken,\n\tchildren,\n\tclassName,\n\trenderSubmit,\n\trenderNext,\n\trenderBack,\n\tsubmitButtonClassName,\n\tnextButtonClassName,\n\tbackButtonClassName,\n}: FormProps) => {\n\tconst honeypotName = honeypot === false ? null : (honeypot?.name ?? DEFAULT_HONEYPOT_FIELD)\n\tconst honeypotRef = useRef<HTMLInputElement>(null)\n\tconst registry = useMemo(() => buildFieldTypeRegistry(fieldTypes), [fieldTypes])\n\tconst ruleRegistry = useMemo(() => buildValidationRuleRegistry(rules), [rules])\n\tconst rendererRegistry = useMemo(() => resolveRenderers(defaultRenderers, renderers), [renderers])\n\tconst presentationRegistry = useMemo(\n\t\t() => resolvePresentations(defaultPresentations, presentations),\n\t\t[presentations]\n\t)\n\tconst activePresentation: FormPresentation =\n\t\ttypeof presentation === 'object'\n\t\t\t? presentation\n\t\t\t: (presentationRegistry.get(presentation ?? DEFAULT_PRESENTATION_NAME) ??\n\t\t\t\tpresentationRegistry.get(DEFAULT_PRESENTATION_NAME) ??\n\t\t\t\tdefaultPresentationDescriptors.page)\n\tconst fieldsByName = useMemo(\n\t\t() => new Map(form.fields.filter(isNamedField).map((field) => [field.name, field])),\n\t\t[form.fields]\n\t)\n\tconst translate = useMemo<RendererTranslate>(() => t ?? makeTranslate(en), [t])\n\tconst docButtons: FormButtonSettings | undefined = form.buttons\n\tconst labels = useMemo(\n\t\t() => ({\n\t\t\tprev: prevLabel ?? storedLabel(docButtons?.prevLabel) ?? translate(keys.formBack),\n\t\t\tnext: nextLabel ?? storedLabel(docButtons?.nextLabel) ?? translate(keys.formNext),\n\t\t\tsubmit: submitLabel ?? storedLabel(docButtons?.submitLabel) ?? translate(keys.formSubmit),\n\t\t}),\n\t\t[prevLabel, nextLabel, submitLabel, docButtons, translate]\n\t)\n\n\t// Latest-value refs so event emission and the mount/unmount effect tolerate an inline `events` prop or a changing form id.\n\tconst sinkRef = useRef<FormEventSink>(noopEventSink)\n\tsinkRef.current = events ?? noopEventSink\n\tconst formIdRef = useRef('')\n\tformIdRef.current = String(form.id)\n\n\tconst [state, rawDispatch] = useReducer(formReducer, form.fields, (fields) =>\n\t\tinitialFormState({\n\t\t\t...seedFieldValues(fields),\n\t\t\t...(initialValues ?? {}),\n\t\t})\n\t)\n\n\t// Authoritative values for derived (calc) fields, recomputed from user answers on every change. The\n\t// server recomputes these too at submit; the client copy drives the live calc renderer, recall, and submit.\n\tconst effectiveValues = useMemo(\n\t\t() => computeCalcFields(form.fields, state.values),\n\t\t[form.fields, state.values]\n\t)\n\n\tconst recall = useMemo(\n\t\t() =>\n\t\t\tbuildRecallResolver({\n\t\t\t\tfields: form.fields,\n\t\t\t\tvalues: effectiveValues,\n\t\t\t\tregistry,\n\t\t\t\tlocale,\n\t\t\t\tt: translate,\n\t\t\t}),\n\t\t[form.fields, effectiveValues, registry, locale, translate]\n\t)\n\n\t// Multi-step is active only when the form is flagged `multistep` and its flow declares two or more\n\t// steps. With the flag off the form renders as a single page even if flow data is still stored.\n\tconst flow =\n\t\tform.multistep === true && form.flow && form.flow.steps.length >= 2 ? form.flow : undefined\n\tconst [currentStepId, setCurrentStepId] = useState<string | undefined>(() =>\n\t\tflow ? firstStepId(flow) : undefined\n\t)\n\tconst [history, setHistory] = useState<string[]>([])\n\n\tconst startedRef = useRef(false)\n\tconst submittedRef = useRef(false)\n\tconst submittingRef = useRef(false)\n\tconst flowRef = useRef(flow)\n\tflowRef.current = flow\n\n\tconst dispatch = useCallback((action: FormAction) => {\n\t\tif (action.type === 'SET_VALUE' && !startedRef.current) {\n\t\t\tstartedRef.current = true\n\t\t\temitFormEvent(sinkRef.current, formIdRef.current, { type: 'form.started' })\n\t\t}\n\t\trawDispatch(action)\n\t}, [])\n\n\tconst validateField = useCallback(\n\t\t(name: string, value: unknown) => {\n\t\t\tconst field = fieldsByName.get(name)\n\t\t\tif (!field) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst answers = { ...effectiveValues, [name]: value }\n\t\t\t// Mirror the server: a field whose `validateWhen` is unmet is not validated; clear any stale error.\n\t\t\tif (!evaluateCondition(field.validateWhen, answers)) {\n\t\t\t\trawDispatch({ type: 'SET_FIELD_ISSUES', name, errors: [] })\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvoid validateFieldValue({\n\t\t\t\tfield,\n\t\t\t\tvalue,\n\t\t\t\tregistry,\n\t\t\t\truleRegistry,\n\t\t\t\tanswers,\n\t\t\t\tlocale,\n\t\t\t\tt: translate,\n\t\t\t}).then(({ errors }) => {\n\t\t\t\trawDispatch({ type: 'SET_FIELD_ISSUES', name, errors })\n\t\t\t\tconst [firstError] = errors\n\t\t\t\tif (firstError !== undefined) {\n\t\t\t\t\temitFormEvent(sinkRef.current, formIdRef.current, {\n\t\t\t\t\t\ttype: 'field.errored',\n\t\t\t\t\t\tfield: name,\n\t\t\t\t\t\tmessage: firstError,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\t[fieldsByName, effectiveValues, registry, ruleRegistry, locale, translate]\n\t)\n\n\tconst visible = visibleFields(form.fields, effectiveValues)\n\n\t/** Visible, answered named fields. Display-only ('none' kind) and nameless (bare) fields never contribute. */\n\tconst answerableFields = (): NamedFormFieldInstance[] =>\n\t\tvisibleFields(form.fields, effectiveValues)\n\t\t\t.filter((field) => registry.get(field.blockType)?.value !== 'none')\n\t\t\t.filter(isNamedField)\n\t\t\t.filter((field) => !isEmpty(effectiveValues[field.name]))\n\n\t/** Answered visible fields as raw submission values, sent to the server on submit. */\n\tconst answeredValues = (): SubmissionValue[] =>\n\t\tanswerableFields().map((field) => ({ field: field.name, value: effectiveValues[field.name] }))\n\n\t/**\n\t * Same fields, formatted via `recall` (option labels, Yes/No, localized dates): recall-fidelity\n\t * values for client-rendered templates, matching what the server gives email-team's body.\n\t */\n\tconst formattedValues = (): SubmissionValue[] =>\n\t\tanswerableFields().map((field) => ({ field: field.name, value: recall(field.name) }))\n\n\t// Steps address fields by key: machine names for named fields, block row ids for bare blocks.\n\tconst stepKeys = flow && currentStepId ? stepFieldNames(flow, currentStepId) : []\n\tconst stepVisible: FormFieldInstance[] = stepKeys\n\t\t.map((key) => visible.find((field) => fieldKey(field) === key))\n\t\t.filter((field): field is FormFieldInstance => Boolean(field))\n\n\tconst goNext = async () => {\n\t\tif (!flow || !currentStepId) {\n\t\t\treturn\n\t\t}\n\t\tconst results = await Promise.all(\n\t\t\tstepVisible\n\t\t\t\t.filter((field) => !calcExpressionOf(field))\n\t\t\t\t.filter((field) => registry.get(field.blockType)?.value !== 'none')\n\t\t\t\t.filter(isNamedField)\n\t\t\t\t.filter((field) => evaluateCondition(field.validateWhen, effectiveValues))\n\t\t\t\t.map(async (field) => ({\n\t\t\t\t\tfield,\n\t\t\t\t\t...(await validateFieldValue({\n\t\t\t\t\t\tfield,\n\t\t\t\t\t\tvalue: effectiveValues[field.name],\n\t\t\t\t\t\tregistry,\n\t\t\t\t\t\truleRegistry,\n\t\t\t\t\t\tanswers: effectiveValues,\n\t\t\t\t\t\tlocale,\n\t\t\t\t\t\tt: translate,\n\t\t\t\t\t})),\n\t\t\t\t}))\n\t\t)\n\t\tlet hasError = false\n\t\tfor (const result of results) {\n\t\t\trawDispatch({ type: 'TOUCH', name: result.field.name })\n\t\t\trawDispatch({\n\t\t\t\ttype: 'SET_FIELD_ISSUES',\n\t\t\t\tname: result.field.name,\n\t\t\t\terrors: result.errors,\n\t\t\t})\n\t\t\tif (result.errors.length > 0) {\n\t\t\t\thasError = true\n\t\t\t}\n\t\t}\n\t\tif (hasError) {\n\t\t\treturn\n\t\t}\n\t\tconst next = resolveNextStepId(flow, currentStepId, effectiveValues)\n\t\tif (!next) {\n\t\t\treturn\n\t\t}\n\t\temitFormEvent(sinkRef.current, formIdRef.current, {\n\t\t\ttype: 'step.completed',\n\t\t\tstepId: currentStepId,\n\t\t})\n\t\tsetHistory((prev) => [...prev, currentStepId])\n\t\tsetCurrentStepId(next)\n\t\temitFormEvent(sinkRef.current, formIdRef.current, { type: 'step.viewed', stepId: next })\n\t}\n\n\tconst goBack = () => {\n\t\tconst prev = history[history.length - 1]\n\t\tif (prev === undefined) {\n\t\t\treturn\n\t\t}\n\t\tsetHistory((entries) => entries.slice(0, -1))\n\t\tsetCurrentStepId(prev)\n\t\temitFormEvent(sinkRef.current, formIdRef.current, { type: 'step.viewed', stepId: prev })\n\t}\n\n\tuseEffect(() => {\n\t\temitFormEvent(sinkRef.current, formIdRef.current, { type: 'form.viewed' })\n\t\tconst mountFlow = flowRef.current\n\t\tif (mountFlow) {\n\t\t\tconst first = firstStepId(mountFlow)\n\t\t\tif (first) {\n\t\t\t\temitFormEvent(sinkRef.current, formIdRef.current, { type: 'step.viewed', stepId: first })\n\t\t\t}\n\t\t}\n\t\treturn () => {\n\t\t\tif (!submittedRef.current && !submittingRef.current) {\n\t\t\t\temitFormEvent(sinkRef.current, formIdRef.current, { type: 'form.abandoned' })\n\t\t\t}\n\t\t}\n\t}, [])\n\n\tconst handleClose = useCallback(() => {\n\t\tonClose?.()\n\t}, [onClose])\n\n\tconst handleSubmit = async (event: ReactFormEvent<HTMLFormElement>) => {\n\t\tevent.preventDefault()\n\t\t// Re-entrancy guard: claim the in-flight slot before the async validation window so a fast second\n\t\t// activation (double-click, Enter + click) cannot reach the transport and POST the submission twice.\n\t\tif (submittingRef.current) {\n\t\t\treturn\n\t\t}\n\t\tsubmittingRef.current = true\n\t\tconst visible = visibleFields(form.fields, effectiveValues)\n\t\tconst results = await Promise.all(\n\t\t\t// Calc fields carry no rules and have no input; they are always satisfied, so skip validating them.\n\t\t\t// Display-only ('none' kind, e.g. message) and nameless (bare) fields are skipped too, mirroring the server.\n\t\t\t// A field whose `validateWhen` is unmet is skipped too, mirroring the server (no client/server divergence).\n\t\t\tvisible\n\t\t\t\t.filter((field) => !calcExpressionOf(field))\n\t\t\t\t.filter((field) => registry.get(field.blockType)?.value !== 'none')\n\t\t\t\t.filter(isNamedField)\n\t\t\t\t.filter((field) => evaluateCondition(field.validateWhen, effectiveValues))\n\t\t\t\t.map(async (field) => ({\n\t\t\t\t\tfield,\n\t\t\t\t\t...(await validateFieldValue({\n\t\t\t\t\t\tfield,\n\t\t\t\t\t\tvalue: effectiveValues[field.name],\n\t\t\t\t\t\tregistry,\n\t\t\t\t\t\truleRegistry,\n\t\t\t\t\t\tanswers: effectiveValues,\n\t\t\t\t\t\tlocale,\n\t\t\t\t\t\tt: translate,\n\t\t\t\t\t})),\n\t\t\t\t}))\n\t\t)\n\t\tconst errors: FieldErrors = {}\n\t\tfor (const result of results) {\n\t\t\tif (result.errors.length > 0) {\n\t\t\t\terrors[result.field.name] = result.errors\n\t\t\t\tconst [firstError] = result.errors\n\t\t\t\tif (firstError !== undefined) {\n\t\t\t\t\temitFormEvent(sinkRef.current, formIdRef.current, {\n\t\t\t\t\t\ttype: 'field.errored',\n\t\t\t\t\t\tfield: result.field.name,\n\t\t\t\t\t\tmessage: firstError,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate sub-fields within each visible repeater, mirroring the server's per-row pass.\n\t\t// Errors are stored under the composite key `fieldName[rowIndex].subFieldName` so the\n\t\t// repeater renderer can look them up from form state and display them inline.\n\t\tfor (const field of visible.filter(\n\t\t\t(f): f is NamedFormFieldInstance => f.blockType === 'repeater' && isNamedField(f)\n\t\t)) {\n\t\t\tconst rows = Array.isArray(effectiveValues[field.name])\n\t\t\t\t? (effectiveValues[field.name] as Array<Record<string, unknown>>)\n\t\t\t\t: []\n\t\t\tconst subFields = (\n\t\t\t\tArray.isArray(field.subFields) ? (field.subFields as FormFieldInstance[]) : []\n\t\t\t).filter(isNamedField)\n\t\t\tfor (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n\t\t\t\tconst row = rows[rowIndex] ?? {}\n\t\t\t\tfor (const subField of subFields) {\n\t\t\t\t\tif (!evaluateCondition(subField.visibleWhen, row)) continue\n\t\t\t\t\tif (!evaluateCondition(subField.validateWhen, row)) continue\n\t\t\t\t\tconst subResult = await validateFieldValue({\n\t\t\t\t\t\tfield: subField,\n\t\t\t\t\t\tvalue: row[subField.name],\n\t\t\t\t\t\tregistry,\n\t\t\t\t\t\truleRegistry,\n\t\t\t\t\t\tanswers: row,\n\t\t\t\t\t\tlocale,\n\t\t\t\t\t\tt: translate,\n\t\t\t\t\t})\n\t\t\t\t\tconst compositeKey = `${field.name}[${rowIndex}].${subField.name}`\n\t\t\t\t\tif (subResult.errors.length > 0) errors[compositeKey] = subResult.errors\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\trawDispatch({ type: 'SET_ALL_ISSUES', errors })\n\t\tif (Object.keys(errors).length > 0) {\n\t\t\tsubmittingRef.current = false\n\t\t\treturn\n\t\t}\n\t\trawDispatch({ type: 'SUBMIT_START' })\n\t\tconst values: SubmissionValue[] = answeredValues()\n\t\tif (honeypotName) {\n\t\t\tconst decoy = honeypotRef.current?.value ?? ''\n\t\t\tif (decoy !== '') {\n\t\t\t\tvalues.push({ field: honeypotName, value: decoy })\n\t\t\t}\n\t\t}\n\t\tif (captchaToken) {\n\t\t\tvalues.push({ field: CAPTCHA_TOKEN_KEY, value: captchaToken })\n\t\t}\n\t\tconst result: SubmitFormResult = onSubmit\n\t\t\t? await onSubmit({ formId: form.id, values })\n\t\t\t: await submitForm({ formId: form.id, values, apiRoute })\n\t\tsubmittingRef.current = false\n\t\tif (result.ok) {\n\t\t\tsubmittedRef.current = true\n\t\t\trawDispatch({ type: 'SUBMIT_SUCCESS' })\n\t\t\temitFormEvent(sinkRef.current, formIdRef.current, {\n\t\t\t\ttype: 'submission.created',\n\t\t\t\tsubmissionId: result.submissionId,\n\t\t\t})\n\t\t\tonSuccess?.(result.submissionId)\n\t\t\tif (activePresentation.dismissOnSuccess) {\n\t\t\t\thandleClose()\n\t\t\t}\n\t\t\tconst redirectUrl =\n\t\t\t\tform.response?.type === 'redirect' ? form.response.redirect?.url : undefined\n\t\t\t// Part of submit handling, not rendering: fires on the custom-`children` path too.\n\t\t\t// Browser-only: no-op during SSR or in non-DOM test environments.\n\t\t\tif (\n\t\t\t\ttypeof redirectUrl === 'string' &&\n\t\t\t\tredirectUrl.length > 0 &&\n\t\t\t\ttypeof window !== 'undefined'\n\t\t\t) {\n\t\t\t\twindow.location.assign(redirectUrl)\n\t\t\t}\n\t\t} else {\n\t\t\tif (result.fieldErrors) {\n\t\t\t\trawDispatch({ type: 'SET_ALL_ISSUES', errors: result.fieldErrors })\n\t\t\t}\n\t\t\tconst message = result.message ?? 'Submission failed'\n\t\t\trawDispatch({ type: 'SUBMIT_ERROR', message })\n\t\t\tonError?.(message)\n\t\t}\n\t}\n\n\tconst step: FormStepInfo = flow\n\t\t? {\n\t\t\t\tflow,\n\t\t\t\tcurrentStepId,\n\t\t\t\tstepIndex: flow.steps.findIndex((s) => s.id === currentStepId),\n\t\t\t\tstepCount: flow.steps.length,\n\t\t\t\tisFirst: history.length === 0,\n\t\t\t\tisTerminal: currentStepId ? isTerminalStepId(flow, currentStepId, effectiveValues) : true,\n\t\t\t\tgoNext: () => {\n\t\t\t\t\tvoid goNext()\n\t\t\t\t},\n\t\t\t\tgoBack,\n\t\t\t}\n\t\t: {\n\t\t\t\tstepIndex: 0,\n\t\t\t\tstepCount: 1,\n\t\t\t\tisFirst: true,\n\t\t\t\tisTerminal: true,\n\t\t\t\tgoNext: () => {},\n\t\t\t\tgoBack: () => {},\n\t\t\t}\n\n\tconst contextValue: FormContextValue = {\n\t\tform,\n\t\tstate,\n\t\tdispatch,\n\t\tvalidateField,\n\t\tlocale,\n\t\tstep,\n\t\trendererRegistry,\n\t\tlabels,\n\t\tt: translate,\n\t\teffectiveValues,\n\t}\n\n\tconst PresentationWrapper = activePresentation.Wrapper\n\tconst wrap = (content: ReactNode): ReactNode =>\n\t\tPresentationWrapper ? (\n\t\t\t<PresentationWrapper\n\t\t\t\tpresentation={activePresentation}\n\t\t\t\topen\n\t\t\t\tonClose={handleClose}\n\t\t\t\ttitle={title}\n\t\t\t\tcloseLabel={closeLabel}\n\t\t\t>\n\t\t\t\t{content}\n\t\t\t</PresentationWrapper>\n\t\t) : (\n\t\t\tcontent\n\t\t)\n\n\tif (children !== undefined) {\n\t\treturn (\n\t\t\t<FormContext.Provider value={contextValue}>\n\t\t\t\t{wrap(\n\t\t\t\t\t<form\n\t\t\t\t\t\tclassName={cn('fb-form-root', className)}\n\t\t\t\t\t\tnoValidate\n\t\t\t\t\t\tonSubmit={handleSubmit}\n\t\t\t\t\t\tdata-fb-presentation={activePresentation.name}\n\t\t\t\t\t\tdata-fb-density={activePresentation.density}\n\t\t\t\t\t>\n\t\t\t\t\t\t{honeypotName ? <Honeypot name={honeypotName} inputRef={honeypotRef} /> : null}\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</form>\n\t\t\t\t)}\n\t\t\t</FormContext.Provider>\n\t\t)\n\t}\n\n\tif (state.submitted) {\n\t\tconst responseMessage =\n\t\t\tform.response?.type === 'message' || form.response?.type == null\n\t\t\t\t? form.response?.message\n\t\t\t\t: undefined\n\t\tconst responseHtml = responseMessage\n\t\t\t? serializeBody(responseMessage, {\n\t\t\t\t\tvalues: formattedValues(),\n\t\t\t\t\tdescriptors: descriptorsFor(answerableFields()),\n\t\t\t\t})\n\t\t\t: undefined\n\t\treturn (\n\t\t\t<FormContext.Provider value={contextValue}>\n\t\t\t\t{wrap(\n\t\t\t\t\tresponseHtml ? (\n\t\t\t\t\t\t// Safe to inject: serializeBody HTML-escapes all text (recall values included) and sanitizes link URLs.\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\trole=\"status\"\n\t\t\t\t\t\t\tclassName={cn('fb-form__success', className)}\n\t\t\t\t\t\t\tdata-fb-presentation={activePresentation.name}\n\t\t\t\t\t\t\tdata-fb-density={activePresentation.density}\n\t\t\t\t\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: HTML is produced by our escaping serializer, never raw user input\n\t\t\t\t\t\t\tdangerouslySetInnerHTML={{ __html: responseHtml }}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<p\n\t\t\t\t\t\t\trole=\"status\"\n\t\t\t\t\t\t\tclassName={cn('fb-form__success', className)}\n\t\t\t\t\t\t\tdata-fb-presentation={activePresentation.name}\n\t\t\t\t\t\t\tdata-fb-density={activePresentation.density}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{interpolate(successMessage, recall)}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t)\n\t\t\t\t)}\n\t\t\t</FormContext.Provider>\n\t\t)\n\t}\n\n\tconst rendered = (flow ? stepVisible : visible).filter(\n\t\t(field) => field.hidden !== true && field.calcDisplay !== false\n\t)\n\n\treturn (\n\t\t<FormContext.Provider value={contextValue}>\n\t\t\t{wrap(\n\t\t\t\t<form\n\t\t\t\t\tclassName={cn('fb-form-root', className)}\n\t\t\t\t\tnoValidate\n\t\t\t\t\tonSubmit={handleSubmit}\n\t\t\t\t\tdata-fb-presentation={activePresentation.name}\n\t\t\t\t\tdata-fb-density={activePresentation.density}\n\t\t\t\t>\n\t\t\t\t\t{honeypotName ? <Honeypot name={honeypotName} inputRef={honeypotRef} /> : null}\n\t\t\t\t\t<FormLayout enabled={layout !== false}>\n\t\t\t\t\t\t{rendered.map((field) => {\n\t\t\t\t\t\t\tconst renderer = rendererRegistry.get(field.blockType)\n\t\t\t\t\t\t\tif (!renderer) {\n\t\t\t\t\t\t\t\treturn null\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tconst width: FieldWidth | undefined =\n\t\t\t\t\t\t\t\ttypeof field.width === 'string' && FIELD_WIDTHS.has(field.width)\n\t\t\t\t\t\t\t\t\t? (field.width as FieldWidth)\n\t\t\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t\tconst recalledField = applyRecall(field, recall)\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t<div key={fieldKey(field)} {...widthProps(width)}>\n\t\t\t\t\t\t\t\t\t{!isNamedField(recalledField) ? (\n\t\t\t\t\t\t\t\t\t\t<StaticFieldHost\n\t\t\t\t\t\t\t\t\t\t\tfield={recalledField}\n\t\t\t\t\t\t\t\t\t\t\trenderer={renderer}\n\t\t\t\t\t\t\t\t\t\t\tlocale={locale}\n\t\t\t\t\t\t\t\t\t\t\tt={translate}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t) : calcExpressionOf(recalledField) ? (\n\t\t\t\t\t\t\t\t\t\t<CalcFieldHost\n\t\t\t\t\t\t\t\t\t\t\tfield={recalledField}\n\t\t\t\t\t\t\t\t\t\t\trenderer={renderer}\n\t\t\t\t\t\t\t\t\t\t\tvalue={effectiveValues[recalledField.name]}\n\t\t\t\t\t\t\t\t\t\t\tlocale={locale}\n\t\t\t\t\t\t\t\t\t\t\tt={translate}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t<FieldHost\n\t\t\t\t\t\t\t\t\t\t\tfield={recalledField}\n\t\t\t\t\t\t\t\t\t\t\trenderer={renderer}\n\t\t\t\t\t\t\t\t\t\t\tlocale={locale}\n\t\t\t\t\t\t\t\t\t\t\tt={translate}\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t})}\n\t\t\t\t\t</FormLayout>\n\t\t\t\t\t{state.submitError ? (\n\t\t\t\t\t\t<p role=\"alert\" className=\"fb-form__submit-error\">\n\t\t\t\t\t\t\t{state.submitError}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t) : null}\n\t\t\t\t\t<FormControls\n\t\t\t\t\t\tbackButtonClassName={backButtonClassName}\n\t\t\t\t\t\tnextButtonClassName={nextButtonClassName}\n\t\t\t\t\t\tsubmitButtonClassName={submitButtonClassName}\n\t\t\t\t\t\trenderBack={renderBack}\n\t\t\t\t\t\trenderNext={renderNext}\n\t\t\t\t\t\trenderSubmit={renderSubmit}\n\t\t\t\t\t/>\n\t\t\t\t</form>\n\t\t\t)}\n\t\t</FormContext.Provider>\n\t)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Form.js","names":[],"sources":["../../src/react/Form.tsx"],"sourcesContent":["'use client'\n\nimport {\n\ttype FormEvent as ReactFormEvent,\n\ttype ReactNode,\n\tuseCallback,\n\tuseEffect,\n\tuseMemo,\n\tuseReducer,\n\tuseRef,\n\tuseState,\n} from 'react'\nimport { serializeBody } from '../actions/body/serializeBody'\nimport { calcExpressionOf, computeCalcFields } from '../calc/computeCalcFields'\nimport { evaluateCondition } from '../conditions/evaluate'\nimport { noopEventSink } from '../events/noopSink'\nimport type { FormEventSink } from '../events/types'\nimport { fieldKey, isNamedField, type NamedFormFieldInstance } from '../fields/fieldKey'\nimport type { AnyFormFieldDefinition } from '../fields/types'\nimport { firstStepId, isTerminalStepId, resolveNextStepId, stepFieldNames } from '../flow/engine'\nimport type {\n\tFormButtonSettings,\n\tFormDocument,\n\tFormPollSettings,\n\tFormResponseSettings,\n} from '../form/types'\nimport {\n\tDEFAULT_PRESENTATION_NAME,\n\tdefaultPresentationDescriptors,\n} from '../presentations/defaults'\nimport { interpolate } from '../recall/interpolate'\nimport { buildRecallResolver, descriptorsFor } from '../recall/resolver'\nimport { CAPTCHA_TOKEN_KEY, DEFAULT_HONEYPOT_FIELD, HONEYPOT_VALUE_KEY } from '../spam/constants'\nimport type { FormFieldInstance, SubmissionValue } from '../submissions/types'\nimport { en } from '../translations/en'\nimport { keys } from '../translations/keys'\nimport { makeTranslate } from '../translations/makeTranslate'\nimport type { AnyValidationRuleDefinition } from '../validation/types'\nimport { cn } from './cn'\nimport type { RendererTranslate } from './contract'\nimport { emitFormEvent } from './events'\nimport { FormContext, type FormContextValue, type FormStepInfo } from './FormContext'\nimport {\n\ttype BackButtonRenderProps,\n\tFormControls,\n\ttype NextButtonRenderProps,\n\ttype SubmitButtonRenderProps,\n} from './FormControls'\nimport { FormFields } from './FormFields'\nimport { Honeypot } from './Honeypot'\nimport { defaultPresentations } from './presentation/presentations'\nimport { type PresentationsConfig, resolvePresentations } from './presentation/registry'\nimport type { FormPresentation } from './presentation/types'\nimport { type RenderersConfig, resolveRenderers } from './registry'\nimport { defaultRenderers } from './renderers'\nimport { buildFieldTypeRegistry, buildValidationRuleRegistry, visibleFields } from './resolveForm'\nimport {\n\ttype FieldErrors,\n\ttype FormAction,\n\tformReducer,\n\tinitialFormState,\n\tseedFieldValues,\n} from './state'\nimport { type SubmitFormResult, type SubmitHandler, submitForm } from './submitForm'\nimport { validateFieldValue } from './validateField'\n\nexport type {\n\tBackButtonRenderProps,\n\tNextButtonRenderProps,\n\tSubmitButtonRenderProps,\n} from './FormControls'\n// FormResponseSettings, FormButtonSettings, FormPollSettings, and FormDocument live in\n// `../form/types` (no 'use client') so server code (e.g. `toFormDocument` in a Server Component)\n// can use them without pulling in this client module. Re-exported here so `./react` and existing\n// `from './Form'` imports keep working unchanged.\nexport type { FormButtonSettings, FormDocument, FormPollSettings, FormResponseSettings }\n\nexport type FormProps = {\n\tform: FormDocument\n\tfieldTypes?: AnyFormFieldDefinition[]\n\trules?: AnyValidationRuleDefinition[]\n\trenderers?: RenderersConfig\n\tapiRoute?: string\n\tonSubmit?: SubmitHandler\n\tonSuccess?: (submissionId?: string) => void\n\tonError?: (message: string) => void\n\tevents?: FormEventSink\n\tt?: RendererTranslate\n\tlocale?: string\n\tlayout?: boolean\n\t/** Submit button label. Precedence: this prop, then the form's `buttons.submitLabel`, then the translated default. */\n\tsubmitLabel?: string\n\t/** \"Next\" button label for multi-step forms. Precedence: this prop, then the form's `buttons.nextLabel`, then the translated default. */\n\tnextLabel?: string\n\t/** \"Back\" button label for multi-step forms. Precedence: this prop, then the form's `buttons.prevLabel`, then the translated default. */\n\tprevLabel?: string\n\t/** Label for the overlay close control (modal/drawer). */\n\tcloseLabel?: string\n\tsuccessMessage?: string\n\t/** Active presentation: a name into the registry or an inline presentation. Defaults to `'page'` when omitted. */\n\tpresentation?: string | FormPresentation\n\t/** Per-render presentation overrides merged onto the defaults (add, replace, or `false` to remove). */\n\tpresentations?: PresentationsConfig\n\t/** Invoked when an overlay presentation dismisses (close button, Escape, outside click, or `dismissOnSuccess`). */\n\tonClose?: () => void\n\t/**\n\t * Accessible name for an overlay surface (modal/drawer). Hosts choosing between a trigger\n\t * label and the form's own admin title should prefer `form.title` when set, falling back to\n\t * their own label otherwise.\n\t */\n\ttitle?: string\n\t/** Seed initial field values (e.g. from `valuesFromSearchParams`). Still validated on submit. */\n\tinitialValues?: Record<string, unknown>\n\t/** Honeypot decoy (on by default). `false` removes it; `{ name }` matches a customized server `spam.honeypot.fieldName`. */\n\thoneypot?: false | { name?: string }\n\t/** A token from your captcha widget; verified server-side when a captcha provider is configured. */\n\tcaptchaToken?: string\n\t/** Custom layout: render fields with `useField`/`useFormState` instead of the auto-rendered field loop. */\n\tchildren?: ReactNode\n\t/** Chrome rendered inside the form, above the fields, in default mode (e.g. `<FormSteps />`). */\n\theader?: ReactNode\n\t/** Additional CSS class names applied to the root `<form>` element (and the success node). */\n\tclassName?: string\n\t/** Replace the default submit button entirely. Receives the resolved label and submitting state. */\n\trenderSubmit?: (props: SubmitButtonRenderProps) => ReactNode\n\t/** Replace the default \"Next\" button in multi-step forms. */\n\trenderNext?: (props: NextButtonRenderProps) => ReactNode\n\t/** Replace the default \"Back\" button in multi-step forms. */\n\trenderBack?: (props: BackButtonRenderProps) => ReactNode\n\t/** CSS class forwarded to the default submit `<button>`. Ignored when `renderSubmit` is provided. */\n\tsubmitButtonClassName?: string\n\t/** CSS class forwarded to the default \"Next\" `<button>`. Ignored when `renderNext` is provided. */\n\tnextButtonClassName?: string\n\t/** CSS class forwarded to the default \"Back\" `<button>`. Ignored when `renderBack` is provided. */\n\tbackButtonClassName?: string\n}\n\nconst isEmpty = (value: unknown): boolean =>\n\tvalue == null || value === '' || (Array.isArray(value) && value.length === 0)\n\n/** A stored button label counts only when it is a non-empty string; anything else falls through. */\nconst storedLabel = (value: unknown): string | undefined =>\n\ttypeof value === 'string' && value.length > 0 ? value : undefined\n\n/** The headless form controller: state, progressive client validation, conditional visibility, submission, events. */\nexport const Form = ({\n\tform,\n\tfieldTypes,\n\trules,\n\trenderers,\n\tapiRoute,\n\tonSubmit,\n\tonSuccess,\n\tonError,\n\tevents,\n\tt,\n\tlocale = 'en',\n\tlayout,\n\tsubmitLabel,\n\tnextLabel,\n\tprevLabel,\n\tcloseLabel,\n\tsuccessMessage,\n\tpresentation,\n\tpresentations,\n\tonClose,\n\ttitle,\n\tinitialValues,\n\thoneypot,\n\tcaptchaToken,\n\tchildren,\n\theader,\n\tclassName,\n\trenderSubmit,\n\trenderNext,\n\trenderBack,\n\tsubmitButtonClassName,\n\tnextButtonClassName,\n\tbackButtonClassName,\n}: FormProps) => {\n\tconst honeypotName = honeypot === false ? null : (honeypot?.name ?? DEFAULT_HONEYPOT_FIELD)\n\tconst honeypotRef = useRef<HTMLInputElement>(null)\n\tconst registry = useMemo(() => buildFieldTypeRegistry(fieldTypes), [fieldTypes])\n\tconst ruleRegistry = useMemo(() => buildValidationRuleRegistry(rules), [rules])\n\tconst rendererRegistry = useMemo(() => resolveRenderers(defaultRenderers, renderers), [renderers])\n\tconst presentationRegistry = useMemo(\n\t\t() => resolvePresentations(defaultPresentations, presentations),\n\t\t[presentations]\n\t)\n\tconst activePresentation: FormPresentation =\n\t\ttypeof presentation === 'object'\n\t\t\t? presentation\n\t\t\t: (presentationRegistry.get(presentation ?? DEFAULT_PRESENTATION_NAME) ??\n\t\t\t\tpresentationRegistry.get(DEFAULT_PRESENTATION_NAME) ??\n\t\t\t\tdefaultPresentationDescriptors.page)\n\tconst fieldsByName = useMemo(\n\t\t() => new Map(form.fields.filter(isNamedField).map((field) => [field.name, field])),\n\t\t[form.fields]\n\t)\n\tconst translate = useMemo<RendererTranslate>(() => t ?? makeTranslate(en), [t])\n\tconst resolvedCloseLabel = closeLabel ?? translate(keys.formClose)\n\tconst resolvedSuccessMessage = successMessage ?? translate(keys.formSuccess)\n\tconst docButtons: FormButtonSettings | undefined = form.buttons\n\tconst labels = useMemo(\n\t\t() => ({\n\t\t\tprev: prevLabel ?? storedLabel(docButtons?.prevLabel) ?? translate(keys.formBack),\n\t\t\tnext: nextLabel ?? storedLabel(docButtons?.nextLabel) ?? translate(keys.formNext),\n\t\t\tsubmit: submitLabel ?? storedLabel(docButtons?.submitLabel) ?? translate(keys.formSubmit),\n\t\t}),\n\t\t[prevLabel, nextLabel, submitLabel, docButtons, translate]\n\t)\n\n\t// Latest-value refs so event emission and the mount/unmount effect tolerate an inline `events` prop or a changing form id.\n\tconst sinkRef = useRef<FormEventSink>(noopEventSink)\n\tsinkRef.current = events ?? noopEventSink\n\tconst formIdRef = useRef('')\n\tformIdRef.current = String(form.id)\n\n\tconst [state, rawDispatch] = useReducer(formReducer, form.fields, (fields) =>\n\t\tinitialFormState({\n\t\t\t...seedFieldValues(fields),\n\t\t\t...(initialValues ?? {}),\n\t\t})\n\t)\n\n\t// Authoritative values for derived (calc) fields, recomputed from user answers on every change. The\n\t// server recomputes these too at submit; the client copy drives the live calc renderer, recall, and submit.\n\tconst effectiveValues = useMemo(\n\t\t() => computeCalcFields(form.fields, state.values),\n\t\t[form.fields, state.values]\n\t)\n\n\tconst recall = useMemo(\n\t\t() =>\n\t\t\tbuildRecallResolver({\n\t\t\t\tfields: form.fields,\n\t\t\t\tvalues: effectiveValues,\n\t\t\t\tregistry,\n\t\t\t\tlocale,\n\t\t\t\tt: translate,\n\t\t\t}),\n\t\t[form.fields, effectiveValues, registry, locale, translate]\n\t)\n\n\t// Multi-step is active only when the form is flagged `multistep` and its flow declares two or more\n\t// steps. With the flag off the form renders as a single page even if flow data is still stored.\n\tconst flow =\n\t\tform.multistep === true && form.flow && form.flow.steps.length >= 2 ? form.flow : undefined\n\tconst [currentStepId, setCurrentStepId] = useState<string | undefined>(() =>\n\t\tflow ? firstStepId(flow) : undefined\n\t)\n\tconst [history, setHistory] = useState<string[]>([])\n\n\tconst startedRef = useRef(false)\n\tconst submittedRef = useRef(false)\n\tconst submittingRef = useRef(false)\n\tconst advancingRef = useRef(false)\n\tconst flowRef = useRef(flow)\n\tflowRef.current = flow\n\n\tconst dispatch = useCallback((action: FormAction) => {\n\t\tif (action.type === 'SET_VALUE' && !startedRef.current) {\n\t\t\tstartedRef.current = true\n\t\t\temitFormEvent(sinkRef.current, formIdRef.current, { type: 'form.started' })\n\t\t}\n\t\trawDispatch(action)\n\t}, [])\n\n\tconst validateField = useCallback(\n\t\t(name: string, value: unknown) => {\n\t\t\tconst field = fieldsByName.get(name)\n\t\t\tif (!field) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst answers = { ...effectiveValues, [name]: value }\n\t\t\t// Mirror the server: a field whose `validateWhen` is unmet is not validated; clear any stale error.\n\t\t\tif (!evaluateCondition(field.validateWhen, answers)) {\n\t\t\t\trawDispatch({ type: 'SET_FIELD_ISSUES', name, errors: [] })\n\t\t\t\treturn\n\t\t\t}\n\t\t\tvoid validateFieldValue({\n\t\t\t\tfield,\n\t\t\t\tvalue,\n\t\t\t\tregistry,\n\t\t\t\truleRegistry,\n\t\t\t\tanswers,\n\t\t\t\tlocale,\n\t\t\t\tt: translate,\n\t\t\t}).then(({ errors }) => {\n\t\t\t\trawDispatch({ type: 'SET_FIELD_ISSUES', name, errors })\n\t\t\t\tconst [firstError] = errors\n\t\t\t\tif (firstError !== undefined) {\n\t\t\t\t\temitFormEvent(sinkRef.current, formIdRef.current, {\n\t\t\t\t\t\ttype: 'field.errored',\n\t\t\t\t\t\tfield: name,\n\t\t\t\t\t\tmessage: firstError,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t})\n\t\t},\n\t\t[fieldsByName, effectiveValues, registry, ruleRegistry, locale, translate]\n\t)\n\n\tconst visible = visibleFields(form.fields, effectiveValues)\n\n\t/** Visible, answered named fields. Display-only ('none' kind) and nameless (bare) fields never contribute. */\n\tconst answerableFields = (): NamedFormFieldInstance[] =>\n\t\tvisibleFields(form.fields, effectiveValues)\n\t\t\t.filter((field) => registry.get(field.blockType)?.value !== 'none')\n\t\t\t.filter(isNamedField)\n\t\t\t.filter((field) => !isEmpty(effectiveValues[field.name]))\n\n\t/** Answered visible fields as raw submission values, sent to the server on submit. */\n\tconst answeredValues = (): SubmissionValue[] =>\n\t\tanswerableFields().map((field) => ({ field: field.name, value: effectiveValues[field.name] }))\n\n\t/**\n\t * Same fields, formatted via `recall` (option labels, Yes/No, localized dates): recall-fidelity\n\t * values for client-rendered templates, matching what the server gives email-team's body.\n\t */\n\tconst formattedValues = (): SubmissionValue[] =>\n\t\tanswerableFields().map((field) => ({ field: field.name, value: recall(field.name) }))\n\n\t// Steps address fields by key: machine names for named fields, block row ids for bare blocks.\n\tconst stepKeys = flow && currentStepId ? stepFieldNames(flow, currentStepId) : []\n\tconst stepVisible: FormFieldInstance[] = stepKeys\n\t\t.map((key) => visible.find((field) => fieldKey(field) === key))\n\t\t.filter((field): field is FormFieldInstance => Boolean(field))\n\n\t// Validate the sub-fields of the given repeaters, one pass per visible row, returning composite-key\n\t// errors (`fieldName[rowIndex].subFieldName`). Shared by submit and step navigation so both mirror\n\t// the server's per-row pass and neither lets an invalid required sub-field slip through.\n\tconst validateRepeaterSubFields = async (\n\t\trepeaters: FormFieldInstance[]\n\t): Promise<FieldErrors> => {\n\t\tconst errors: FieldErrors = {}\n\t\tfor (const field of repeaters.filter(\n\t\t\t(f): f is NamedFormFieldInstance => f.blockType === 'repeater' && isNamedField(f)\n\t\t)) {\n\t\t\tconst rows = Array.isArray(effectiveValues[field.name])\n\t\t\t\t? (effectiveValues[field.name] as Array<Record<string, unknown>>)\n\t\t\t\t: []\n\t\t\tconst subFields = (\n\t\t\t\tArray.isArray(field.subFields) ? (field.subFields as FormFieldInstance[]) : []\n\t\t\t).filter(isNamedField)\n\t\t\tfor (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {\n\t\t\t\tconst row = rows[rowIndex] ?? {}\n\t\t\t\tfor (const subField of subFields) {\n\t\t\t\t\tif (!evaluateCondition(subField.visibleWhen, row)) continue\n\t\t\t\t\tif (!evaluateCondition(subField.validateWhen, row)) continue\n\t\t\t\t\tconst subResult = await validateFieldValue({\n\t\t\t\t\t\tfield: subField,\n\t\t\t\t\t\tvalue: row[subField.name],\n\t\t\t\t\t\tregistry,\n\t\t\t\t\t\truleRegistry,\n\t\t\t\t\t\tanswers: row,\n\t\t\t\t\t\tlocale,\n\t\t\t\t\t\tt: translate,\n\t\t\t\t\t})\n\t\t\t\t\tconst compositeKey = `${field.name}[${rowIndex}].${subField.name}`\n\t\t\t\t\tif (subResult.errors.length > 0) errors[compositeKey] = subResult.errors\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn errors\n\t}\n\n\tconst goNext = async () => {\n\t\t// Re-entrancy guard: a double-click during async validation must not push the same step onto\n\t\t// history twice (which would need two Back presses to undo).\n\t\tif (!flow || !currentStepId || advancingRef.current) {\n\t\t\treturn\n\t\t}\n\t\tadvancingRef.current = true\n\t\ttry {\n\t\t\tconst results = await Promise.all(\n\t\t\t\tstepVisible\n\t\t\t\t\t.filter((field) => !calcExpressionOf(field))\n\t\t\t\t\t.filter((field) => registry.get(field.blockType)?.value !== 'none')\n\t\t\t\t\t.filter(isNamedField)\n\t\t\t\t\t.filter((field) => evaluateCondition(field.validateWhen, effectiveValues))\n\t\t\t\t\t.map(async (field) => ({\n\t\t\t\t\t\tfield,\n\t\t\t\t\t\t...(await validateFieldValue({\n\t\t\t\t\t\t\tfield,\n\t\t\t\t\t\t\tvalue: effectiveValues[field.name],\n\t\t\t\t\t\t\tregistry,\n\t\t\t\t\t\t\truleRegistry,\n\t\t\t\t\t\t\tanswers: effectiveValues,\n\t\t\t\t\t\t\tlocale,\n\t\t\t\t\t\t\tt: translate,\n\t\t\t\t\t\t})),\n\t\t\t\t\t}))\n\t\t\t)\n\t\t\tlet hasError = false\n\t\t\tfor (const result of results) {\n\t\t\t\trawDispatch({ type: 'TOUCH', name: result.field.name })\n\t\t\t\trawDispatch({\n\t\t\t\t\ttype: 'SET_FIELD_ISSUES',\n\t\t\t\t\tname: result.field.name,\n\t\t\t\t\terrors: result.errors,\n\t\t\t\t})\n\t\t\t\tif (result.errors.length > 0) {\n\t\t\t\t\thasError = true\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Mirror submit: validate the step's repeater sub-fields too, so a required sub-field can't be\n\t\t\t// skipped past on a non-terminal step (errors surface inline via the composite key).\n\t\t\tconst repeaterErrors = await validateRepeaterSubFields(stepVisible)\n\t\t\tfor (const [compositeKey, errs] of Object.entries(repeaterErrors)) {\n\t\t\t\trawDispatch({ type: 'SET_FIELD_ISSUES', name: compositeKey, errors: errs })\n\t\t\t\thasError = true\n\t\t\t}\n\t\t\tif (hasError) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst next = resolveNextStepId(flow, currentStepId, effectiveValues)\n\t\t\tif (!next) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\temitFormEvent(sinkRef.current, formIdRef.current, {\n\t\t\t\ttype: 'step.completed',\n\t\t\t\tstepId: currentStepId,\n\t\t\t})\n\t\t\tsetHistory((prev) => [...prev, currentStepId])\n\t\t\tsetCurrentStepId(next)\n\t\t\temitFormEvent(sinkRef.current, formIdRef.current, { type: 'step.viewed', stepId: next })\n\t\t} finally {\n\t\t\tadvancingRef.current = false\n\t\t}\n\t}\n\n\tconst goBack = () => {\n\t\tconst prev = history[history.length - 1]\n\t\tif (prev === undefined) {\n\t\t\treturn\n\t\t}\n\t\tsetHistory((entries) => entries.slice(0, -1))\n\t\tsetCurrentStepId(prev)\n\t\temitFormEvent(sinkRef.current, formIdRef.current, { type: 'step.viewed', stepId: prev })\n\t}\n\n\tuseEffect(() => {\n\t\temitFormEvent(sinkRef.current, formIdRef.current, { type: 'form.viewed' })\n\t\tconst mountFlow = flowRef.current\n\t\tif (mountFlow) {\n\t\t\tconst first = firstStepId(mountFlow)\n\t\t\tif (first) {\n\t\t\t\temitFormEvent(sinkRef.current, formIdRef.current, { type: 'step.viewed', stepId: first })\n\t\t\t}\n\t\t}\n\t\treturn () => {\n\t\t\tif (!submittedRef.current && !submittingRef.current) {\n\t\t\t\temitFormEvent(sinkRef.current, formIdRef.current, { type: 'form.abandoned' })\n\t\t\t}\n\t\t}\n\t}, [])\n\n\tconst handleClose = useCallback(() => {\n\t\tonClose?.()\n\t}, [onClose])\n\n\tconst handleSubmit = async (event: ReactFormEvent<HTMLFormElement>) => {\n\t\tevent.preventDefault()\n\t\t// Re-entrancy guard: claim the in-flight slot before the async validation window so a fast second\n\t\t// activation (double-click, Enter + click) cannot reach the transport and POST the submission twice.\n\t\tif (submittingRef.current) {\n\t\t\treturn\n\t\t}\n\t\tsubmittingRef.current = true\n\t\tconst visible = visibleFields(form.fields, effectiveValues)\n\t\tconst results = await Promise.all(\n\t\t\t// Calc fields carry no rules and have no input; they are always satisfied, so skip validating them.\n\t\t\t// Display-only ('none' kind, e.g. message) and nameless (bare) fields are skipped too, mirroring the server.\n\t\t\t// A field whose `validateWhen` is unmet is skipped too, mirroring the server (no client/server divergence).\n\t\t\tvisible\n\t\t\t\t.filter((field) => !calcExpressionOf(field))\n\t\t\t\t.filter((field) => registry.get(field.blockType)?.value !== 'none')\n\t\t\t\t.filter(isNamedField)\n\t\t\t\t.filter((field) => evaluateCondition(field.validateWhen, effectiveValues))\n\t\t\t\t.map(async (field) => ({\n\t\t\t\t\tfield,\n\t\t\t\t\t...(await validateFieldValue({\n\t\t\t\t\t\tfield,\n\t\t\t\t\t\tvalue: effectiveValues[field.name],\n\t\t\t\t\t\tregistry,\n\t\t\t\t\t\truleRegistry,\n\t\t\t\t\t\tanswers: effectiveValues,\n\t\t\t\t\t\tlocale,\n\t\t\t\t\t\tt: translate,\n\t\t\t\t\t})),\n\t\t\t\t}))\n\t\t)\n\t\tconst errors: FieldErrors = {}\n\t\tfor (const result of results) {\n\t\t\tif (result.errors.length > 0) {\n\t\t\t\terrors[result.field.name] = result.errors\n\t\t\t\tconst [firstError] = result.errors\n\t\t\t\tif (firstError !== undefined) {\n\t\t\t\t\temitFormEvent(sinkRef.current, formIdRef.current, {\n\t\t\t\t\t\ttype: 'field.errored',\n\t\t\t\t\t\tfield: result.field.name,\n\t\t\t\t\t\tmessage: firstError,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Validate sub-fields within each visible repeater (composite keys `fieldName[rowIndex].subFieldName`),\n\t\t// mirroring the server's per-row pass, so the repeater renderer surfaces them inline.\n\t\tObject.assign(errors, await validateRepeaterSubFields(visible))\n\n\t\trawDispatch({ type: 'SET_ALL_ISSUES', errors })\n\t\tif (Object.keys(errors).length > 0) {\n\t\t\tsubmittingRef.current = false\n\t\t\treturn\n\t\t}\n\t\trawDispatch({ type: 'SUBMIT_START' })\n\t\tconst values: SubmissionValue[] = answeredValues()\n\t\tif (honeypotName) {\n\t\t\tconst decoy = honeypotRef.current?.value ?? ''\n\t\t\tif (decoy !== '') {\n\t\t\t\t// Submit the decoy under a reserved key, not its cosmetic DOM name, so a real field sharing\n\t\t\t\t// that name is never stripped or mistaken for the honeypot on the server.\n\t\t\t\tvalues.push({ field: HONEYPOT_VALUE_KEY, value: decoy })\n\t\t\t}\n\t\t}\n\t\tif (captchaToken) {\n\t\t\tvalues.push({ field: CAPTCHA_TOKEN_KEY, value: captchaToken })\n\t\t}\n\t\tconst result: SubmitFormResult = onSubmit\n\t\t\t? await onSubmit({ formId: form.id, values })\n\t\t\t: await submitForm({ formId: form.id, values, apiRoute })\n\t\tsubmittingRef.current = false\n\t\tif (result.ok) {\n\t\t\tsubmittedRef.current = true\n\t\t\trawDispatch({ type: 'SUBMIT_SUCCESS' })\n\t\t\temitFormEvent(sinkRef.current, formIdRef.current, {\n\t\t\t\ttype: 'submission.created',\n\t\t\t\tsubmissionId: result.submissionId,\n\t\t\t})\n\t\t\tonSuccess?.(result.submissionId)\n\t\t\tif (activePresentation.dismissOnSuccess) {\n\t\t\t\thandleClose()\n\t\t\t}\n\t\t\tconst redirectUrl =\n\t\t\t\tform.response?.type === 'redirect' ? form.response.redirect?.url : undefined\n\t\t\t// Part of submit handling, not rendering: fires on the custom-`children` path too.\n\t\t\t// Browser-only: no-op during SSR or in non-DOM test environments.\n\t\t\tif (\n\t\t\t\ttypeof redirectUrl === 'string' &&\n\t\t\t\tredirectUrl.length > 0 &&\n\t\t\t\ttypeof window !== 'undefined'\n\t\t\t) {\n\t\t\t\twindow.location.assign(redirectUrl)\n\t\t\t}\n\t\t} else {\n\t\t\tif (result.fieldErrors) {\n\t\t\t\trawDispatch({ type: 'SET_ALL_ISSUES', errors: result.fieldErrors })\n\t\t\t}\n\t\t\tconst message = result.message ?? translate(keys.formSubmitFailed)\n\t\t\trawDispatch({ type: 'SUBMIT_ERROR', message })\n\t\t\tonError?.(message)\n\t\t}\n\t}\n\n\tconst step: FormStepInfo = flow\n\t\t? {\n\t\t\t\tflow,\n\t\t\t\tcurrentStepId,\n\t\t\t\tstepIndex: flow.steps.findIndex((s) => s.id === currentStepId),\n\t\t\t\tstepCount: flow.steps.length,\n\t\t\t\tisFirst: history.length === 0,\n\t\t\t\tisTerminal: currentStepId ? isTerminalStepId(flow, currentStepId, effectiveValues) : true,\n\t\t\t\tgoNext: () => {\n\t\t\t\t\tvoid goNext()\n\t\t\t\t},\n\t\t\t\tgoBack,\n\t\t\t}\n\t\t: {\n\t\t\t\tstepIndex: 0,\n\t\t\t\tstepCount: 1,\n\t\t\t\tisFirst: true,\n\t\t\t\tisTerminal: true,\n\t\t\t\tgoNext: () => {},\n\t\t\t\tgoBack: () => {},\n\t\t\t}\n\n\tconst renderedFields = (flow ? stepVisible : visible).filter(\n\t\t(field) => field.hidden !== true && field.calcDisplay !== false\n\t)\n\n\tconst contextValue: FormContextValue = {\n\t\tform,\n\t\tstate,\n\t\tdispatch,\n\t\tvalidateField,\n\t\tlocale,\n\t\tstep,\n\t\trendererRegistry,\n\t\tlabels,\n\t\tt: translate,\n\t\teffectiveValues,\n\t\trecall,\n\t\trenderedFields,\n\t}\n\n\tconst PresentationWrapper = activePresentation.Wrapper\n\tconst wrap = (content: ReactNode): ReactNode =>\n\t\tPresentationWrapper ? (\n\t\t\t<PresentationWrapper\n\t\t\t\tpresentation={activePresentation}\n\t\t\t\topen\n\t\t\t\tonClose={handleClose}\n\t\t\t\ttitle={title}\n\t\t\t\tcloseLabel={resolvedCloseLabel}\n\t\t\t>\n\t\t\t\t{content}\n\t\t\t</PresentationWrapper>\n\t\t) : (\n\t\t\tcontent\n\t\t)\n\n\tif (children !== undefined) {\n\t\treturn (\n\t\t\t<FormContext.Provider value={contextValue}>\n\t\t\t\t{wrap(\n\t\t\t\t\t<form\n\t\t\t\t\t\tclassName={cn('fb-form-root', className)}\n\t\t\t\t\t\tnoValidate\n\t\t\t\t\t\tonSubmit={handleSubmit}\n\t\t\t\t\t\tdata-fb-presentation={activePresentation.name}\n\t\t\t\t\t\tdata-fb-density={activePresentation.density}\n\t\t\t\t\t>\n\t\t\t\t\t\t{honeypotName ? <Honeypot name={honeypotName} inputRef={honeypotRef} /> : null}\n\t\t\t\t\t\t{children}\n\t\t\t\t\t</form>\n\t\t\t\t)}\n\t\t\t</FormContext.Provider>\n\t\t)\n\t}\n\n\tif (state.submitted) {\n\t\tconst responseMessage =\n\t\t\tform.response?.type === 'message' || form.response?.type == null\n\t\t\t\t? form.response?.message\n\t\t\t\t: undefined\n\t\tconst responseHtml = responseMessage\n\t\t\t? serializeBody(responseMessage, {\n\t\t\t\t\tvalues: formattedValues(),\n\t\t\t\t\tdescriptors: descriptorsFor(answerableFields()),\n\t\t\t\t})\n\t\t\t: undefined\n\t\treturn (\n\t\t\t<FormContext.Provider value={contextValue}>\n\t\t\t\t{wrap(\n\t\t\t\t\tresponseHtml ? (\n\t\t\t\t\t\t// Safe to inject: serializeBody HTML-escapes all text (recall values included) and sanitizes link URLs.\n\t\t\t\t\t\t<div\n\t\t\t\t\t\t\trole=\"status\"\n\t\t\t\t\t\t\tclassName={cn('fb-form__success', className)}\n\t\t\t\t\t\t\tdata-fb-presentation={activePresentation.name}\n\t\t\t\t\t\t\tdata-fb-density={activePresentation.density}\n\t\t\t\t\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: HTML is produced by our escaping serializer, never raw user input\n\t\t\t\t\t\t\tdangerouslySetInnerHTML={{ __html: responseHtml }}\n\t\t\t\t\t\t/>\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<p\n\t\t\t\t\t\t\trole=\"status\"\n\t\t\t\t\t\t\tclassName={cn('fb-form__success', className)}\n\t\t\t\t\t\t\tdata-fb-presentation={activePresentation.name}\n\t\t\t\t\t\t\tdata-fb-density={activePresentation.density}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{interpolate(resolvedSuccessMessage, recall)}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t)\n\t\t\t\t)}\n\t\t\t</FormContext.Provider>\n\t\t)\n\t}\n\n\treturn (\n\t\t<FormContext.Provider value={contextValue}>\n\t\t\t{wrap(\n\t\t\t\t<form\n\t\t\t\t\tclassName={cn('fb-form-root', className)}\n\t\t\t\t\tnoValidate\n\t\t\t\t\tonSubmit={handleSubmit}\n\t\t\t\t\tdata-fb-presentation={activePresentation.name}\n\t\t\t\t\tdata-fb-density={activePresentation.density}\n\t\t\t\t>\n\t\t\t\t\t{honeypotName ? <Honeypot name={honeypotName} inputRef={honeypotRef} /> : null}\n\t\t\t\t\t{header}\n\t\t\t\t\t<FormFields layout={layout} />\n\t\t\t\t\t{state.submitError ? (\n\t\t\t\t\t\t<p role=\"alert\" className=\"fb-form__submit-error\">\n\t\t\t\t\t\t\t{state.submitError}\n\t\t\t\t\t\t</p>\n\t\t\t\t\t) : null}\n\t\t\t\t\t<FormControls\n\t\t\t\t\t\tbackButtonClassName={backButtonClassName}\n\t\t\t\t\t\tnextButtonClassName={nextButtonClassName}\n\t\t\t\t\t\tsubmitButtonClassName={submitButtonClassName}\n\t\t\t\t\t\trenderBack={renderBack}\n\t\t\t\t\t\trenderNext={renderNext}\n\t\t\t\t\t\trenderSubmit={renderSubmit}\n\t\t\t\t\t/>\n\t\t\t\t</form>\n\t\t\t)}\n\t\t</FormContext.Provider>\n\t)\n}\n"],"mappings":"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"}
@@ -1,7 +1,9 @@
1
+ import { FormFieldInstance } from "../submissions/types.js";
1
2
  import { FormFlow } from "../flow/types.js";
2
3
  import { FormDocument } from "../form/types.js";
3
4
  import { RendererTranslate } from "./contract.js";
4
5
  import { RendererRegistry } from "./registry.js";
6
+ import { RecallResolver } from "../recall/resolver.js";
5
7
  import { FormAction, FormState } from "./state.js";
6
8
  import { Dispatch } from "react";
7
9
 
@@ -43,7 +45,9 @@ type FormContextValue = {
43
45
  rendererRegistry: RendererRegistry; /** Resolved prev/next/submit labels used by `<FormControls>` and available to custom chrome. */
44
46
  labels: FormControlLabels; /** The active translator: the `<Form>` `t` prop, else the bundled English fallback. */
45
47
  t: RendererTranslate; /** Calc-authoritative answers: `state.values` overlaid with derived calc values. Consumers fall back to `state.values` when absent. */
46
- effectiveValues?: Record<string, unknown>;
48
+ effectiveValues?: Record<string, unknown>; /** Recall resolver for token interpolation, exposed so `<FormFields>` and custom layouts can format values. */
49
+ recall?: RecallResolver; /** The exact visible field list the default loop renders (post hidden/calc filter). Consumed by `<FormFields>`. */
50
+ renderedFields?: FormFieldInstance[];
47
51
  };
48
52
  /** Read the form controller context. Throws if used outside `<Form>`. */
49
53
  declare const useFormContext: () => FormContextValue;
@@ -1 +1 @@
1
- {"version":3,"file":"FormContext.js","names":[],"sources":["../../src/react/FormContext.ts"],"sourcesContent":["'use client'\n\nimport type { Dispatch } from 'react'\nimport { createContext, useContext } from 'react'\nimport type { FormFlow } from '../flow/types'\nimport type { FormDocument } from '../form/types'\nimport type { RendererTranslate } from './contract'\nimport type { RendererRegistry } from './registry'\nimport type { FormAction, FormState } from './state'\n\n/** Multi-step navigation state. Defaults to a single terminal step when the form has no flow. */\nexport type FormStepInfo = {\n\tflow?: FormFlow\n\tcurrentStepId?: string\n\tstepIndex: number\n\tstepCount: number\n\tisFirst: boolean\n\tisTerminal: boolean\n\tgoNext: () => void\n\tgoBack: () => void\n}\n\n/** Resolved chrome button labels. Precedence per label: the `<Form>` prop, then the form's `buttons` value, then the translated default. */\nexport type FormControlLabels = {\n\tprev: string\n\tnext: string\n\tsubmit: string\n}\n\n/**\n * The form controller's context, read via `useFormContext` (or the focused `useField`,\n * `useFormState`, and `useFormStep` hooks). Available anywhere under `<Form>`, including\n * custom `children` layouts and custom field renderers.\n */\nexport type FormContextValue = {\n\t/** The document rendered by this `<Form>`. Custom chrome reads host-added `buttons` keys from here. */\n\tform: FormDocument\n\t/** Current form state: values, errors, touched, submitting, submitted, submitError. */\n\tstate: FormState\n\t/**\n\t * Dispatch a `FormAction` (see `./state` for the action union). Custom field layouts\n\t * typically dispatch `TOUCH` or `SET_FIELD_ISSUES`; prefer `useField` for value binding,\n\t * which wires `SET_VALUE` and validation for you.\n\t */\n\tdispatch: Dispatch<FormAction>\n\t/** Validate one field now (client mode) against the supplied value and store its issues. */\n\tvalidateField: (name: string, value: unknown) => void\n\t/** The locale passed to `<Form>` (defaults to `'en'`). */\n\tlocale: string\n\t/** Multi-step navigation state and the `goNext`/`goBack` handlers. */\n\tstep: FormStepInfo\n\t/** The active renderer registry, exposed so nested renderers (e.g. repeater) can look up sub-renderers. */\n\trendererRegistry: RendererRegistry\n\t/** Resolved prev/next/submit labels used by `<FormControls>` and available to custom chrome. */\n\tlabels: FormControlLabels\n\t/** The active translator: the `<Form>` `t` prop, else the bundled English fallback. */\n\tt: RendererTranslate\n\t/** Calc-authoritative answers: `state.values` overlaid with derived calc values. Consumers fall back to `state.values` when absent. */\n\teffectiveValues?: Record<string, unknown>\n}\n\nexport const FormContext = createContext<FormContextValue | null>(null)\n\n/** Read the form controller context. Throws if used outside `<Form>`. */\nexport const useFormContext = (): FormContextValue => {\n\tconst context = useContext(FormContext)\n\tif (!context) {\n\t\tthrow new Error('useFormContext must be used within a <Form>')\n\t}\n\treturn context\n}\n"],"mappings":";;;AA6DA,MAAa,cAAc,cAAuC,IAAI;;AAGtE,MAAa,uBAAyC;CACrD,MAAM,UAAU,WAAW,WAAW;CACtC,IAAI,CAAC,SACJ,MAAM,IAAI,MAAM,6CAA6C;CAE9D,OAAO;AACR"}
1
+ {"version":3,"file":"FormContext.js","names":[],"sources":["../../src/react/FormContext.ts"],"sourcesContent":["'use client'\n\nimport type { Dispatch } from 'react'\nimport { createContext, useContext } from 'react'\nimport type { FormFlow } from '../flow/types'\nimport type { FormDocument } from '../form/types'\nimport type { RecallResolver } from '../recall/resolver'\nimport type { FormFieldInstance } from '../submissions/types'\nimport type { RendererTranslate } from './contract'\nimport type { RendererRegistry } from './registry'\nimport type { FormAction, FormState } from './state'\n\n/** Multi-step navigation state. Defaults to a single terminal step when the form has no flow. */\nexport type FormStepInfo = {\n\tflow?: FormFlow\n\tcurrentStepId?: string\n\tstepIndex: number\n\tstepCount: number\n\tisFirst: boolean\n\tisTerminal: boolean\n\tgoNext: () => void\n\tgoBack: () => void\n}\n\n/** Resolved chrome button labels. Precedence per label: the `<Form>` prop, then the form's `buttons` value, then the translated default. */\nexport type FormControlLabels = {\n\tprev: string\n\tnext: string\n\tsubmit: string\n}\n\n/**\n * The form controller's context, read via `useFormContext` (or the focused `useField`,\n * `useFormState`, and `useFormStep` hooks). Available anywhere under `<Form>`, including\n * custom `children` layouts and custom field renderers.\n */\nexport type FormContextValue = {\n\t/** The document rendered by this `<Form>`. Custom chrome reads host-added `buttons` keys from here. */\n\tform: FormDocument\n\t/** Current form state: values, errors, touched, submitting, submitted, submitError. */\n\tstate: FormState\n\t/**\n\t * Dispatch a `FormAction` (see `./state` for the action union). Custom field layouts\n\t * typically dispatch `TOUCH` or `SET_FIELD_ISSUES`; prefer `useField` for value binding,\n\t * which wires `SET_VALUE` and validation for you.\n\t */\n\tdispatch: Dispatch<FormAction>\n\t/** Validate one field now (client mode) against the supplied value and store its issues. */\n\tvalidateField: (name: string, value: unknown) => void\n\t/** The locale passed to `<Form>` (defaults to `'en'`). */\n\tlocale: string\n\t/** Multi-step navigation state and the `goNext`/`goBack` handlers. */\n\tstep: FormStepInfo\n\t/** The active renderer registry, exposed so nested renderers (e.g. repeater) can look up sub-renderers. */\n\trendererRegistry: RendererRegistry\n\t/** Resolved prev/next/submit labels used by `<FormControls>` and available to custom chrome. */\n\tlabels: FormControlLabels\n\t/** The active translator: the `<Form>` `t` prop, else the bundled English fallback. */\n\tt: RendererTranslate\n\t/** Calc-authoritative answers: `state.values` overlaid with derived calc values. Consumers fall back to `state.values` when absent. */\n\teffectiveValues?: Record<string, unknown>\n\t/** Recall resolver for token interpolation, exposed so `<FormFields>` and custom layouts can format values. */\n\trecall?: RecallResolver\n\t/** The exact visible field list the default loop renders (post hidden/calc filter). Consumed by `<FormFields>`. */\n\trenderedFields?: FormFieldInstance[]\n}\n\nexport const FormContext = createContext<FormContextValue | null>(null)\n\n/** Read the form controller context. Throws if used outside `<Form>`. */\nexport const useFormContext = (): FormContextValue => {\n\tconst context = useContext(FormContext)\n\tif (!context) {\n\t\tthrow new Error('useFormContext must be used within a <Form>')\n\t}\n\treturn context\n}\n"],"mappings":";;;AAmEA,MAAa,cAAc,cAAuC,IAAI;;AAGtE,MAAa,uBAAyC;CACrD,MAAM,UAAU,WAAW,WAAW;CACtC,IAAI,CAAC,SACJ,MAAM,IAAI,MAAM,6CAA6C;CAE9D,OAAO;AACR"}
@@ -0,0 +1,18 @@
1
+ //#region src/react/FormFields.d.ts
2
+ type FormFieldsProps = {
3
+ /** When false, render the fields in document order with no grid class. Defaults to the grid. */layout?: boolean; /** Extra class names merged onto the layout container. */
4
+ className?: string;
5
+ };
6
+ /**
7
+ * The standard field loop, driven entirely by `useFormContext()`. Rendered by `<Form>` in default
8
+ * mode and exported so a custom `children` layout can drop the same loop in without reimplementing
9
+ * visibility, calc, recall, and step filtering. In `children` mode the host owns the success-state
10
+ * swap (via `useFormState`); this component only ever renders the fields.
11
+ */
12
+ declare const FormFields: ({
13
+ layout,
14
+ className
15
+ }: FormFieldsProps) => import("react/jsx-runtime").JSX.Element;
16
+ //#endregion
17
+ export { FormFields, FormFieldsProps };
18
+ //# sourceMappingURL=FormFields.d.ts.map