@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.
- package/CHANGELOG.md +73 -0
- package/dist/actions/buildActionBlocks.js +12 -9
- package/dist/actions/buildActionBlocks.js.map +1 -1
- package/dist/actions/builtin/confirmation.js +13 -68
- package/dist/actions/builtin/confirmation.js.map +1 -1
- package/dist/actions/builtin/emailAction.d.ts +17 -0
- package/dist/actions/builtin/emailAction.js +83 -0
- package/dist/actions/builtin/emailAction.js.map +1 -0
- package/dist/actions/builtin/emailTeam.js +14 -74
- package/dist/actions/builtin/emailTeam.js.map +1 -1
- package/dist/actions/builtin/index.d.ts +9 -12
- package/dist/actions/builtin/index.js +11 -11
- package/dist/actions/builtin/index.js.map +1 -1
- package/dist/actions/defineAction.d.ts +2 -2
- package/dist/actions/defineAction.js.map +1 -1
- package/dist/actions/emailRecipients.js +75 -31
- package/dist/actions/emailRecipients.js.map +1 -1
- package/dist/actions/registry.d.ts +2 -1
- package/dist/actions/registry.js +2 -6
- package/dist/actions/registry.js.map +1 -1
- package/dist/actions/task.js +5 -3
- package/dist/actions/task.js.map +1 -1
- package/dist/calc/evaluate.js +1 -2
- package/dist/calc/evaluate.js.map +1 -1
- package/dist/calc/normalizeCalc.js +1 -2
- package/dist/calc/normalizeCalc.js.map +1 -1
- package/dist/calc/types.js.map +1 -1
- package/dist/collections/buttonFields.js +1 -1
- package/dist/collections/formSubmissions.js +2 -1
- package/dist/collections/formSubmissions.js.map +1 -1
- package/dist/collections/forms.js +30 -99
- package/dist/collections/forms.js.map +1 -1
- package/dist/collections/formsEndpoints.js +101 -0
- package/dist/collections/formsEndpoints.js.map +1 -0
- package/dist/conditions/evaluate.js +1 -1
- package/dist/conditions/evaluate.js.map +1 -1
- package/dist/conditions/fieldTypes.d.ts +3 -1
- package/dist/conditions/fieldTypes.js +4 -1
- package/dist/conditions/fieldTypes.js.map +1 -1
- package/dist/conditions/normalizeConditions.js +18 -5
- package/dist/conditions/normalizeConditions.js.map +1 -1
- package/dist/consent/captureConsent.d.ts +7 -0
- package/dist/consent/captureConsent.js +7 -0
- package/dist/consent/captureConsent.js.map +1 -1
- package/dist/consent/consentSourcesField.d.ts +4 -0
- package/dist/consent/consentSourcesField.js +5 -1
- package/dist/consent/consentSourcesField.js.map +1 -1
- package/dist/consent/resolveConsentEntries.d.ts +1 -1
- package/dist/consent/resolveConsentEntries.js +1 -4
- package/dist/consent/resolveConsentEntries.js.map +1 -1
- package/dist/consent/resolveConsentSourcesRequest.js +0 -1
- package/dist/consent/resolveConsentSourcesRequest.js.map +1 -1
- package/dist/consent/resolveConsentStatements.d.ts +1 -1
- package/dist/consent/resolveConsentStatements.js.map +1 -1
- package/dist/consent/types.d.ts +9 -1
- package/dist/email/departmentsField.js +1 -1
- package/dist/exports/react.d.ts +3 -2
- package/dist/exports/react.js +3 -2
- package/dist/exports/rsc.js +1 -1
- package/dist/exports/types.d.ts +1 -1
- package/dist/fields/builtin/email.js +1 -1
- package/dist/fields/builtin/email.js.map +1 -1
- package/dist/fields/builtin/message.js +1 -1
- package/dist/fields/builtin/repeater.js +1 -1
- package/dist/fields/builtin/select.js +1 -1
- package/dist/fields/registry.d.ts +2 -1
- package/dist/fields/registry.js +2 -6
- package/dist/fields/registry.js.map +1 -1
- package/dist/fields/sharedConfig.js +1 -1
- package/dist/flow/normalizeFlow.js +7 -3
- package/dist/flow/normalizeFlow.js.map +1 -1
- package/dist/index.d.ts +4 -201
- package/dist/index.js +11 -4
- package/dist/index.js.map +1 -1
- package/dist/options.d.ts +228 -0
- package/dist/plugin/applyRegistryConfig.d.ts +11 -0
- package/dist/plugin/applyRegistryConfig.js +20 -0
- package/dist/plugin/applyRegistryConfig.js.map +1 -0
- package/dist/plugin/registerCollections.js +2 -1
- package/dist/plugin/registerCollections.js.map +1 -1
- package/dist/poll/buildPollOptionSourceFields.js +2 -2
- package/dist/poll/buildPollOptionSourceFields.js.map +1 -1
- package/dist/poll/definePollOptionSource.d.ts +2 -2
- package/dist/poll/definePollOptionSource.js.map +1 -1
- package/dist/poll/pollTypeRegistry.js +2 -4
- package/dist/poll/pollTypeRegistry.js.map +1 -1
- package/dist/poll/registry.d.ts +2 -1
- package/dist/poll/registry.js +2 -6
- package/dist/poll/registry.js.map +1 -1
- package/dist/poll/resolvePollOptionsRequest.js +0 -1
- package/dist/poll/resolvePollOptionsRequest.js.map +1 -1
- package/dist/prefill/valuesFromSearchParams.js +2 -7
- package/dist/prefill/valuesFromSearchParams.js.map +1 -1
- package/dist/react/Form.d.ts +3 -1
- package/dist/react/Form.js +93 -155
- package/dist/react/Form.js.map +1 -1
- package/dist/react/FormContext.d.ts +5 -1
- package/dist/react/FormContext.js.map +1 -1
- package/dist/react/FormFields.d.ts +18 -0
- package/dist/react/FormFields.js +114 -0
- package/dist/react/FormFields.js.map +1 -0
- package/dist/react/FormLayout.d.ts +4 -2
- package/dist/react/FormLayout.js +3 -2
- package/dist/react/FormLayout.js.map +1 -1
- package/dist/react/Poll.js +13 -3
- package/dist/react/Poll.js.map +1 -1
- package/dist/react/presentation/DialogSurface.js +1 -4
- package/dist/react/presentation/DialogSurface.js.map +1 -1
- package/dist/react/renderers/calculation.js +1 -3
- package/dist/react/renderers/calculation.js.map +1 -1
- package/dist/react/renderers/checkbox.js +1 -3
- package/dist/react/renderers/checkbox.js.map +1 -1
- package/dist/react/renderers/consent.js +2 -3
- package/dist/react/renderers/consent.js.map +1 -1
- package/dist/react/renderers/date.js +1 -3
- package/dist/react/renderers/date.js.map +1 -1
- package/dist/react/renderers/email.js +1 -3
- package/dist/react/renderers/email.js.map +1 -1
- package/dist/react/renderers/file.js +6 -7
- package/dist/react/renderers/file.js.map +1 -1
- package/dist/react/renderers/number.js +1 -3
- package/dist/react/renderers/number.js.map +1 -1
- package/dist/react/renderers/region.js +1 -3
- package/dist/react/renderers/region.js.map +1 -1
- package/dist/react/renderers/repeater.js +20 -4
- package/dist/react/renderers/repeater.js.map +1 -1
- package/dist/react/renderers/select.js +1 -3
- package/dist/react/renderers/select.js.map +1 -1
- package/dist/react/renderers/text.js +1 -3
- package/dist/react/renderers/text.js.map +1 -1
- package/dist/react/renderers/textarea.js +1 -3
- package/dist/react/renderers/textarea.js.map +1 -1
- package/dist/spam/constants.js +7 -1
- package/dist/spam/constants.js.map +1 -1
- package/dist/spam/resolveSpam.js +2 -1
- package/dist/spam/resolveSpam.js.map +1 -1
- package/dist/spam/spamGuard.js +22 -6
- package/dist/spam/spamGuard.js.map +1 -1
- package/dist/spam/types.d.ts +17 -2
- package/dist/submissions/coerceBoolean.js +14 -0
- package/dist/submissions/coerceBoolean.js.map +1 -0
- package/dist/submissions/runSubmission.js +7 -7
- package/dist/submissions/runSubmission.js.map +1 -1
- package/dist/submissions/validateSubmission.js +3 -2
- package/dist/submissions/validateSubmission.js.map +1 -1
- package/dist/translations/de.js +10 -0
- package/dist/translations/de.js.map +1 -1
- package/dist/translations/en.js +10 -0
- package/dist/translations/en.js.map +1 -1
- package/dist/translations/keys.d.ts +10 -0
- package/dist/translations/keys.js +10 -0
- package/dist/translations/keys.js.map +1 -1
- package/dist/translations/server.js +8 -1
- package/dist/translations/server.js.map +1 -1
- package/dist/uploads/captureFileRef.d.ts +10 -3
- package/dist/uploads/captureFileRef.js +18 -9
- package/dist/uploads/captureFileRef.js.map +1 -1
- package/dist/validation/builtin/email.js +1 -1
- package/dist/validation/builtin/email.js.map +1 -1
- package/dist/validation/builtin/notAlreadySubmitted.js +1 -0
- package/dist/validation/builtin/notAlreadySubmitted.js.map +1 -1
- package/dist/validation/emailPattern.js +10 -0
- package/dist/validation/emailPattern.js.map +1 -0
- package/dist/validation/registry.d.ts +2 -1
- package/dist/validation/registry.js +2 -9
- package/dist/validation/registry.js.map +1 -1
- package/package.json +4 -4
package/dist/react/Form.js
CHANGED
|
@@ -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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
230
|
+
setHistory((prev) => [...prev, currentStepId]);
|
|
231
|
+
setCurrentStepId(next);
|
|
232
|
+
emitFormEvent(sinkRef.current, formIdRef.current, {
|
|
233
|
+
type: "step.viewed",
|
|
234
|
+
stepId: next
|
|
238
235
|
});
|
|
239
|
-
|
|
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
|
-
|
|
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:
|
|
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 ??
|
|
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(
|
|
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
|
-
|
|
476
|
-
|
|
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",
|
package/dist/react/Form.js.map
CHANGED
|
@@ -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":";;;
|
|
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
|