@marktiderman/genesis-forms-web 0.0.0-stage → 0.1.0

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/dist/index.js ADDED
@@ -0,0 +1,1024 @@
1
+ "use client";
2
+ import { createContext, useRef, useContext, useState, useEffect, useLayoutEffect, useId } from 'react';
3
+ import { Button } from '@marktiderman/genesis-ui/button';
4
+ import { Progress } from '@marktiderman/genesis-ui/progress';
5
+ import { cn } from '@marktiderman/genesis-ui/utils';
6
+ import { createRendererRegistry, useForm, FormRuntime, useFormContext, useFormSelector, FieldRenderer } from '@marktiderman/genesis-forms-react';
7
+ import { Checkbox } from '@marktiderman/genesis-ui/checkbox';
8
+ import { FormDescription, FormError, FormLabel } from '@marktiderman/genesis-ui/form-field';
9
+ import { Input } from '@marktiderman/genesis-ui/input';
10
+ import { Label } from '@marktiderman/genesis-ui/label';
11
+ import { RadioGroup, RadioGroupItem } from '@marktiderman/genesis-ui/radio-group';
12
+ import { Textarea } from '@marktiderman/genesis-ui/textarea';
13
+ import { choiceOptions, getFieldType } from '@marktiderman/genesis-forms';
14
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
15
+
16
+ // src/Form.tsx
17
+ var FormPresentationContext = createContext({ presentation: "embedded", layout: "classic" });
18
+ var FormPresentationProvider = FormPresentationContext.Provider;
19
+ function useFormPresentation() {
20
+ return useContext(FormPresentationContext);
21
+ }
22
+ var questionFocus = "rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ring-offset-background";
23
+ function FieldChrome({ props, children, className }) {
24
+ const { binding, inputId } = props;
25
+ const { field, error, label } = binding;
26
+ const { presentation, layout } = useFormPresentation();
27
+ const pageFocus = presentation === "page" && layout === "focus";
28
+ const pageClassic = presentation === "page" && layout === "classic";
29
+ const group = getFieldType(field.type)?.a11y.semantics === "group";
30
+ const description = field.description ? props.engine.text(field.description) : void 0;
31
+ const questionSize = pageFocus ? "text-2xl font-semibold leading-tight tracking-tight text-balance sm:text-3xl" : pageClassic ? "text-lg font-medium leading-snug" : "text-base";
32
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
33
+ description ? /* @__PURE__ */ jsx(FormDescription, { id: `${inputId}-desc`, className: cn(pageFocus && "text-base sm:text-lg text-pretty"), children: description }) : null,
34
+ pageFocus ? /* @__PURE__ */ jsx("div", { className: "pt-6 sm:pt-8", children }) : children,
35
+ pageFocus ? (
36
+ // A calm note under the answer, Typeform style; the question keeps its colour.
37
+ error ? /* @__PURE__ */ jsxs(
38
+ FormError,
39
+ {
40
+ id: `${inputId}-error`,
41
+ className: "mt-4 inline-flex items-center gap-2 rounded-md bg-destructive px-3 py-2 text-sm text-destructive-foreground",
42
+ children: [
43
+ /* @__PURE__ */ jsxs(
44
+ "svg",
45
+ {
46
+ viewBox: "0 0 24 24",
47
+ fill: "none",
48
+ stroke: "currentColor",
49
+ strokeWidth: "2",
50
+ "aria-hidden": "true",
51
+ className: "size-4 shrink-0",
52
+ children: [
53
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10" }),
54
+ /* @__PURE__ */ jsx("path", { d: "M12 8v4M12 16h.01", strokeLinecap: "round" })
55
+ ]
56
+ }
57
+ ),
58
+ error
59
+ ]
60
+ }
61
+ ) : null
62
+ ) : /* @__PURE__ */ jsx(FormError, { id: `${inputId}-error`, children: error })
63
+ ] });
64
+ if (group) {
65
+ return /* @__PURE__ */ jsxs(
66
+ "fieldset",
67
+ {
68
+ id: inputId,
69
+ "data-slot": "form-field",
70
+ "aria-describedby": describedBy(props),
71
+ className: cn("space-y-3 border-0 p-0 m-0 min-w-0", className),
72
+ children: [
73
+ /* @__PURE__ */ jsxs(
74
+ "legend",
75
+ {
76
+ id: `${inputId}-label`,
77
+ tabIndex: -1,
78
+ "data-gf-question": "",
79
+ className: cn(
80
+ "mb-3 font-medium",
81
+ questionSize,
82
+ pageFocus && "mb-0",
83
+ questionFocus,
84
+ error && !pageFocus && "text-destructive"
85
+ ),
86
+ children: [
87
+ label,
88
+ field.required ? /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", "aria-hidden": "true", children: "*" }) : null
89
+ ]
90
+ }
91
+ ),
92
+ body
93
+ ]
94
+ }
95
+ );
96
+ }
97
+ return /* @__PURE__ */ jsxs("div", { "data-slot": "form-field", className: cn("space-y-2", pageFocus && "space-y-3", className), children: [
98
+ /* @__PURE__ */ jsx(
99
+ FormLabel,
100
+ {
101
+ id: `${inputId}-label`,
102
+ htmlFor: inputId,
103
+ tabIndex: -1,
104
+ "data-gf-question": "",
105
+ error: Boolean(error) && !pageFocus,
106
+ required: field.required,
107
+ className: cn(presentation === "page" && "block", questionSize, questionFocus),
108
+ children: label
109
+ }
110
+ ),
111
+ body
112
+ ] });
113
+ }
114
+ function describedBy(props) {
115
+ const ids = [
116
+ props.binding.field.description ? `${props.inputId}-desc` : null,
117
+ props.binding.error ? `${props.inputId}-error` : null
118
+ ].filter(Boolean);
119
+ return ids.length ? ids.join(" ") : void 0;
120
+ }
121
+ function groupAria(props) {
122
+ return {
123
+ "aria-labelledby": `${props.inputId}-label`,
124
+ "aria-required": props.binding.field.required || void 0,
125
+ "aria-invalid": props.binding.error ? true : void 0
126
+ };
127
+ }
128
+ var NAVIGATION_KEYS = /* @__PURE__ */ new Set(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown"]);
129
+ var NAVIGATION_WINDOW_MS = 500;
130
+ function useDeliberatePick(onAdvance) {
131
+ const lastKey = useRef(null);
132
+ return {
133
+ rootProps: {
134
+ onKeyDownCapture: (e) => {
135
+ lastKey.current = { key: e.key, at: Date.now() };
136
+ },
137
+ onPointerDownCapture: () => {
138
+ lastKey.current = null;
139
+ }
140
+ },
141
+ onItemClick: () => {
142
+ const last = lastKey.current;
143
+ const navigating = last && NAVIGATION_KEYS.has(last.key) && Date.now() - last.at < NAVIGATION_WINDOW_MS;
144
+ if (!navigating) onAdvance?.();
145
+ }
146
+ };
147
+ }
148
+ function TextField(props) {
149
+ const { binding, inputId, type = "text", multiline, inputMode, autoComplete } = props;
150
+ const manifest = getFieldType(binding.field.type);
151
+ const { presentation, layout } = useFormPresentation();
152
+ const pageFocus = presentation === "page" && layout === "focus";
153
+ const common = {
154
+ id: inputId,
155
+ value: typeof binding.value === "string" ? binding.value : "",
156
+ placeholder: binding.field.placeholder ?? (pageFocus ? "Type your answer here\u2026" : void 0),
157
+ // Page focus: a large underlined answer line, Typeform style. The underline turns primary on focus.
158
+ className: pageFocus ? "h-auto rounded-none border-0 border-b-2 border-input bg-transparent px-0 py-2 text-xl shadow-none md:text-2xl sm:text-2xl placeholder:text-muted-foreground/70 focus-visible:border-primary focus-visible:ring-0 focus-visible:ring-offset-0 aria-invalid:border-destructive" : presentation === "page" ? "h-11 md:text-base" : void 0,
159
+ // The focus layout focuses the input itself, so a respondent can type straight away.
160
+ "data-gf-focus": "",
161
+ "aria-invalid": binding.error ? true : void 0,
162
+ "aria-required": binding.field.required || void 0,
163
+ "aria-describedby": describedBy(props)
164
+ };
165
+ return /* @__PURE__ */ jsx(FieldChrome, { props, children: multiline ? /* @__PURE__ */ jsx(
166
+ Textarea,
167
+ {
168
+ ...common,
169
+ rows: pageFocus ? 1 : 4,
170
+ className: cn(common.className, pageFocus && "min-h-12 resize-none"),
171
+ onChange: (e) => binding.setValue(e.target.value)
172
+ }
173
+ ) : /* @__PURE__ */ jsx(
174
+ Input,
175
+ {
176
+ ...common,
177
+ type,
178
+ inputMode: manifest?.a11y.inputMode ?? inputMode,
179
+ autoComplete: manifest?.a11y.autocomplete ?? autoComplete,
180
+ onChange: (e) => binding.setValue(e.target.value)
181
+ }
182
+ ) });
183
+ }
184
+ function ButtonRadioGroup({
185
+ props,
186
+ options,
187
+ value,
188
+ onValueChange,
189
+ itemClassName
190
+ }) {
191
+ const pick = useDeliberatePick(props.onAdvance);
192
+ const { presentation, layout } = useFormPresentation();
193
+ const page = presentation === "page";
194
+ const size = layout === "classic" ? "h-12" : options.length <= 6 ? "h-14 sm:h-16" : "h-14";
195
+ return /* @__PURE__ */ jsx(
196
+ RadioGroup,
197
+ {
198
+ ...groupAria(props),
199
+ ...pick.rootProps,
200
+ value,
201
+ onValueChange,
202
+ className: page ? cn("grid gap-2", options.length <= 6 && "sm:gap-3", gridColumns(options.length)) : "flex flex-wrap justify-start gap-2",
203
+ children: options.map((o) => /* @__PURE__ */ jsxs("div", { className: "relative", children: [
204
+ /* @__PURE__ */ jsx(
205
+ RadioGroupItem,
206
+ {
207
+ value: o.value,
208
+ "aria-label": o.label,
209
+ "aria-describedby": o.describedBy,
210
+ onClick: pick.onItemClick,
211
+ className: cn(
212
+ "peer h-11 w-11 aspect-auto rounded-md border-input bg-background shadow-sm transition-colors hover:bg-accent motion-reduce:transition-none",
213
+ page && cn(size, "w-full rounded-lg border-primary/30 bg-primary/5 shadow-none hover:bg-primary/10"),
214
+ "data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:hover:bg-primary",
215
+ "[&_svg]:hidden",
216
+ page ? void 0 : itemClassName
217
+ )
218
+ }
219
+ ),
220
+ /* @__PURE__ */ jsx(
221
+ "span",
222
+ {
223
+ "aria-hidden": "true",
224
+ className: cn(
225
+ "pointer-events-none absolute inset-0 flex items-center justify-center text-base font-medium peer-disabled:opacity-50 peer-data-[state=checked]:text-primary-foreground",
226
+ page && "text-lg font-semibold"
227
+ ),
228
+ children: o.label
229
+ }
230
+ )
231
+ ] }, o.value))
232
+ }
233
+ );
234
+ }
235
+ var PHONE_COLUMNS = {
236
+ 2: "grid-cols-2",
237
+ 3: "grid-cols-3",
238
+ 4: "grid-cols-4",
239
+ 5: "grid-cols-5",
240
+ 6: "grid-cols-6",
241
+ 7: "grid-cols-4",
242
+ 8: "grid-cols-4",
243
+ 9: "grid-cols-5",
244
+ 10: "grid-cols-5",
245
+ 11: "grid-cols-6"
246
+ };
247
+ var WIDE_COLUMNS = {
248
+ 2: "sm:grid-cols-2",
249
+ 3: "sm:grid-cols-3",
250
+ 4: "sm:grid-cols-4",
251
+ 5: "sm:grid-cols-5",
252
+ 6: "sm:grid-cols-6",
253
+ 7: "sm:grid-cols-7",
254
+ 8: "sm:grid-cols-8",
255
+ 9: "sm:grid-cols-9",
256
+ 10: "sm:grid-cols-10",
257
+ 11: "sm:grid-cols-11"
258
+ };
259
+ function gridColumns(count) {
260
+ return cn(PHONE_COLUMNS[count] ?? "grid-cols-6", WIDE_COLUMNS[count] ?? "sm:grid-cols-11");
261
+ }
262
+ function scaleValues(type, max) {
263
+ if (type === "nps") return Array.from({ length: 11 }, (_, i) => i);
264
+ return Array.from({ length: max }, (_, i) => i + 1);
265
+ }
266
+ function ScaleField(props) {
267
+ const { binding, inputId } = props;
268
+ const max = typeof binding.field.props?.max === "number" ? binding.field.props.max : 5;
269
+ const values = scaleValues(binding.field.type, max);
270
+ const nps = binding.field.type === "nps";
271
+ const options = values.map((n, i) => ({
272
+ value: String(n),
273
+ label: String(n),
274
+ describedBy: nps && i === 0 ? `${inputId}-low` : nps && i === values.length - 1 ? `${inputId}-high` : void 0
275
+ }));
276
+ return /* @__PURE__ */ jsxs(FieldChrome, { props, children: [
277
+ /* @__PURE__ */ jsx(
278
+ ButtonRadioGroup,
279
+ {
280
+ props,
281
+ options,
282
+ value: typeof binding.value === "number" ? String(binding.value) : "",
283
+ onValueChange: (v) => binding.setValue(Number(v))
284
+ }
285
+ ),
286
+ nps ? /* @__PURE__ */ jsxs("div", { className: "flex justify-between text-sm text-muted-foreground", children: [
287
+ /* @__PURE__ */ jsx("span", { id: `${inputId}-low`, children: "Not likely" }),
288
+ /* @__PURE__ */ jsx("span", { id: `${inputId}-high`, children: "Very likely" })
289
+ ] }) : null
290
+ ] });
291
+ }
292
+ function YesNoField(props) {
293
+ const { binding } = props;
294
+ const current = binding.value === true ? "yes" : binding.value === false ? "no" : "";
295
+ return /* @__PURE__ */ jsx(FieldChrome, { props, children: /* @__PURE__ */ jsx(
296
+ ButtonRadioGroup,
297
+ {
298
+ props,
299
+ options: [
300
+ { value: "yes", label: "Yes" },
301
+ { value: "no", label: "No" }
302
+ ],
303
+ value: current,
304
+ onValueChange: (v) => binding.setValue(v === "yes"),
305
+ itemClassName: "w-20"
306
+ }
307
+ ) });
308
+ }
309
+ var choiceRow = "flex min-h-11 w-full cursor-pointer items-center gap-3 rounded-md border border-input px-3 py-2 text-base font-normal leading-snug transition-colors hover:bg-accent motion-reduce:transition-none has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50 has-[:disabled]:hover:bg-transparent";
310
+ var pageChoiceRow = "group/choice min-h-13 gap-3 rounded-lg border-primary/30 bg-primary/5 px-3 py-2.5 text-lg hover:bg-primary/10 has-[[data-state=checked]]:border-primary has-[[data-state=checked]]:bg-primary/15 has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-ring has-[:focus-visible]:ring-offset-2 ring-offset-background";
311
+ var pageChoiceKey = "peer size-7 shrink-0 rounded-md border border-primary/50 bg-background shadow-none focus-visible:ring-0 focus-visible:ring-offset-0 data-[state=checked]:border-primary data-[state=checked]:bg-primary [&_svg]:hidden";
312
+ var KEYS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
313
+ function useChoiceLook(inputId, count) {
314
+ const { presentation, layout } = useFormPresentation();
315
+ const pageFocus = presentation === "page" && layout === "focus";
316
+ const page = presentation === "page";
317
+ return {
318
+ pageFocus,
319
+ row: cn(
320
+ choiceRow,
321
+ "has-[[data-state=checked]]:border-primary",
322
+ page && "min-h-12 text-base",
323
+ pageFocus && pageChoiceRow
324
+ ),
325
+ control: pageFocus ? pageChoiceKey : void 0,
326
+ onKeyDown: pageFocus ? (e) => {
327
+ if (e.altKey || e.ctrlKey || e.metaKey || e.key.length !== 1) return;
328
+ const index = KEYS.indexOf(e.key.toUpperCase());
329
+ if (index < 0 || index >= count) return;
330
+ const option = document.getElementById(`${inputId}-${index}`);
331
+ if (!option || option.disabled) return;
332
+ e.preventDefault();
333
+ option.focus();
334
+ option.click();
335
+ } : void 0
336
+ };
337
+ }
338
+ function ChoiceContent({ look, index, label }) {
339
+ if (!look.pageFocus) return /* @__PURE__ */ jsx("span", { children: label });
340
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
341
+ /* @__PURE__ */ jsx(
342
+ "span",
343
+ {
344
+ "aria-hidden": "true",
345
+ className: "pointer-events-none absolute flex size-7 items-center justify-center text-xs font-bold text-primary group-has-[[data-state=checked]]/choice:text-primary-foreground",
346
+ children: KEYS[index] ?? ""
347
+ }
348
+ ),
349
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: label }),
350
+ /* @__PURE__ */ jsx(
351
+ "svg",
352
+ {
353
+ viewBox: "0 0 24 24",
354
+ fill: "none",
355
+ stroke: "currentColor",
356
+ strokeWidth: "2.5",
357
+ strokeLinecap: "round",
358
+ strokeLinejoin: "round",
359
+ "aria-hidden": "true",
360
+ className: "size-5 shrink-0 text-primary opacity-0 transition-opacity group-has-[[data-state=checked]]/choice:opacity-100 motion-reduce:transition-none",
361
+ children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" })
362
+ }
363
+ )
364
+ ] });
365
+ }
366
+ function SingleChoiceField(props) {
367
+ const { binding, inputId, onAdvance } = props;
368
+ const pick = useDeliberatePick(onAdvance);
369
+ const options = choiceOptions(binding.field);
370
+ const look = useChoiceLook(inputId, options.length);
371
+ return /* @__PURE__ */ jsx(FieldChrome, { props, children: /* @__PURE__ */ jsx(
372
+ RadioGroup,
373
+ {
374
+ ...groupAria(props),
375
+ ...pick.rootProps,
376
+ onKeyDown: look.onKeyDown,
377
+ className: look.pageFocus ? "gap-2.5" : void 0,
378
+ value: typeof binding.value === "string" ? binding.value : "",
379
+ onValueChange: (v) => binding.setValue(v),
380
+ children: options.map((o, i) => /* @__PURE__ */ jsxs(Label, { htmlFor: `${inputId}-${i}`, className: look.row, children: [
381
+ /* @__PURE__ */ jsx(
382
+ RadioGroupItem,
383
+ {
384
+ id: `${inputId}-${i}`,
385
+ value: o.value,
386
+ onClick: pick.onItemClick,
387
+ className: look.control
388
+ }
389
+ ),
390
+ /* @__PURE__ */ jsx(ChoiceContent, { look, index: i, label: o.label ?? o.value })
391
+ ] }, o.value))
392
+ }
393
+ ) });
394
+ }
395
+ function MultiChoiceField(props) {
396
+ const { binding, inputId } = props;
397
+ const selected = Array.isArray(binding.value) ? binding.value : [];
398
+ const options = choiceOptions(binding.field);
399
+ const look = useChoiceLook(inputId, options.length);
400
+ const toggle = (value, on) => {
401
+ const next = on ? [...selected, value] : selected.filter((v) => v !== value);
402
+ binding.setValue(next.length ? next : void 0);
403
+ };
404
+ return /* @__PURE__ */ jsx(FieldChrome, { props, children: /* @__PURE__ */ jsxs("div", { className: cn("grid gap-2", look.pageFocus && "gap-2.5"), onKeyDown: look.onKeyDown, children: [
405
+ look.pageFocus ? /* @__PURE__ */ jsx("p", { className: "-mt-2 mb-1 text-sm text-muted-foreground", children: "Choose as many as you like" }) : null,
406
+ options.map((o, i) => /* @__PURE__ */ jsxs(Label, { htmlFor: `${inputId}-${i}`, className: look.row, children: [
407
+ /* @__PURE__ */ jsx(
408
+ Checkbox,
409
+ {
410
+ id: `${inputId}-${i}`,
411
+ checked: selected.includes(o.value),
412
+ "aria-invalid": binding.error ? true : void 0,
413
+ onCheckedChange: (c) => toggle(o.value, c === true),
414
+ className: look.control
415
+ }
416
+ ),
417
+ /* @__PURE__ */ jsx(ChoiceContent, { look, index: i, label: o.label ?? o.value })
418
+ ] }, o.value))
419
+ ] }) });
420
+ }
421
+ var builtInRenderers = [
422
+ { type: "short-text", Component: (p) => /* @__PURE__ */ jsx(TextField, { ...p }) },
423
+ { type: "long-text", Component: (p) => /* @__PURE__ */ jsx(TextField, { ...p, multiline: true }) },
424
+ { type: "email", Component: (p) => /* @__PURE__ */ jsx(TextField, { ...p, type: "email" }) },
425
+ // The engine's `phone` type (string value). Registered by name so it works
426
+ // with any engine that defines it; the tel hints don't depend on its manifest.
427
+ { type: "phone", Component: (p) => /* @__PURE__ */ jsx(TextField, { ...p, type: "tel", inputMode: "tel", autoComplete: "tel" }) },
428
+ { type: "rating", Component: ScaleField },
429
+ { type: "nps", Component: ScaleField },
430
+ { type: "single-choice", Component: SingleChoiceField },
431
+ { type: "multi-choice", Component: MultiChoiceField },
432
+ { type: "yes-no", Component: YesNoField }
433
+ ];
434
+ var defaultRegistry = createRendererRegistry(builtInRenderers);
435
+ function renderSlot(Default, override, props, extra) {
436
+ if (override === null) return null;
437
+ const Component = override ?? Default;
438
+ return /* @__PURE__ */ jsx(Component, { ...props, ...extra });
439
+ }
440
+ var focusRing = "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ring-offset-background";
441
+ function DefaultHeader({ title, description, className }) {
442
+ const { presentation, layout } = useFormPresentation();
443
+ if (presentation === "page" && layout === "focus") {
444
+ return /* @__PURE__ */ jsxs("header", { className: cn("space-y-1", className), children: [
445
+ /* @__PURE__ */ jsx("h1", { className: "text-sm font-medium text-muted-foreground", children: title }),
446
+ description ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: description }) : null
447
+ ] });
448
+ }
449
+ if (presentation === "page") {
450
+ return /* @__PURE__ */ jsxs(
451
+ "header",
452
+ {
453
+ className: cn(
454
+ "rounded-xl border border-t-8 border-t-primary bg-card px-5 py-6 text-card-foreground sm:px-8 sm:py-8",
455
+ className
456
+ ),
457
+ children: [
458
+ /* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold tracking-tight text-balance sm:text-4xl", children: title }),
459
+ description ? /* @__PURE__ */ jsx("p", { className: "mt-3 text-base text-muted-foreground text-pretty", children: description }) : null
460
+ ]
461
+ }
462
+ );
463
+ }
464
+ return /* @__PURE__ */ jsxs("header", { className: cn("space-y-1", className), children: [
465
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold tracking-tight", children: title }),
466
+ description ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: description }) : null
467
+ ] });
468
+ }
469
+ function initials(name) {
470
+ return name.trim().split(/\s+/).filter(Boolean).slice(0, 2).map((word) => word[0].toUpperCase()).join("");
471
+ }
472
+ function DefaultBrand({ name, logo, className }) {
473
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex min-w-0 items-center gap-2.5", className), "data-gf-brand": "", children: [
474
+ typeof logo === "string" ? /* @__PURE__ */ jsx("img", { src: logo, alt: "", className: "size-8 shrink-0 rounded-md object-contain" }) : logo ? /* @__PURE__ */ jsx("span", { className: "flex shrink-0 items-center", "aria-hidden": "true", children: logo }) : /* @__PURE__ */ jsx(
475
+ "span",
476
+ {
477
+ "aria-hidden": "true",
478
+ className: "flex size-8 shrink-0 items-center justify-center rounded-md bg-primary text-xs font-bold tracking-wide text-primary-foreground",
479
+ children: initials(name)
480
+ }
481
+ ),
482
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-semibold tracking-tight", children: name })
483
+ ] });
484
+ }
485
+ function DefaultProgress({ step, total, className }) {
486
+ const { presentation } = useFormPresentation();
487
+ const page = presentation === "page";
488
+ const percent = total ? Math.round((page ? step - 1 : step) / total * 100) : 0;
489
+ const bar = /* @__PURE__ */ jsx(
490
+ Progress,
491
+ {
492
+ value: percent,
493
+ role: "progressbar",
494
+ "aria-valuemin": 0,
495
+ "aria-valuemax": 100,
496
+ "aria-valuenow": percent,
497
+ "aria-valuetext": `Question ${step} of ${total}`,
498
+ "aria-label": "Progress",
499
+ className: cn("motion-reduce:[&>div]:transition-none", page && "h-1 rounded-none bg-primary/15")
500
+ }
501
+ );
502
+ if (page) {
503
+ return /* @__PURE__ */ jsx("div", { className: cn("fixed inset-x-0 top-0 z-10", className), "data-gf-progress": "", children: bar });
504
+ }
505
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-1", className), "data-gf-progress": "", children: [
506
+ bar,
507
+ /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
508
+ "Question ",
509
+ step,
510
+ " of ",
511
+ total
512
+ ] })
513
+ ] });
514
+ }
515
+ function CheckIcon({ className }) {
516
+ return /* @__PURE__ */ jsx(
517
+ "svg",
518
+ {
519
+ viewBox: "0 0 24 24",
520
+ fill: "none",
521
+ stroke: "currentColor",
522
+ strokeWidth: "2.5",
523
+ strokeLinecap: "round",
524
+ strokeLinejoin: "round",
525
+ "aria-hidden": "true",
526
+ className,
527
+ children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" })
528
+ }
529
+ );
530
+ }
531
+ function ChevronLeftIcon() {
532
+ return /* @__PURE__ */ jsx(
533
+ "svg",
534
+ {
535
+ viewBox: "0 0 24 24",
536
+ fill: "none",
537
+ stroke: "currentColor",
538
+ strokeWidth: "2",
539
+ strokeLinecap: "round",
540
+ strokeLinejoin: "round",
541
+ "aria-hidden": "true",
542
+ children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" })
543
+ }
544
+ );
545
+ }
546
+ function DefaultNavigation({
547
+ canGoBack,
548
+ isLast,
549
+ busy,
550
+ failed,
551
+ onBack,
552
+ submitLabel,
553
+ nextLabel,
554
+ retryLabel,
555
+ keyHint,
556
+ className
557
+ }) {
558
+ const { presentation, layout } = useFormPresentation();
559
+ const label = busy ? "Sending\u2026" : failed ? retryLabel : isLast ? submitLabel : nextLabel;
560
+ if (presentation === "page") {
561
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-wrap items-center gap-x-4 gap-y-2", layout === "classic" && "pt-2", className), children: [
562
+ /* @__PURE__ */ jsxs(
563
+ Button,
564
+ {
565
+ type: "submit",
566
+ size: "lg",
567
+ loading: busy,
568
+ className: "h-12 min-w-28 px-6 text-base font-semibold motion-reduce:[&_.animate-spin]:animate-none [&_svg]:size-5",
569
+ children: [
570
+ label,
571
+ !busy && !failed && !isLast ? /* @__PURE__ */ jsx(CheckIcon, {}) : null
572
+ ]
573
+ }
574
+ ),
575
+ keyHint && !busy ? /* @__PURE__ */ jsxs("span", { className: "hidden text-sm text-muted-foreground pointer-fine:inline", "data-gf-key-hint": "", children: [
576
+ "press ",
577
+ /* @__PURE__ */ jsx("kbd", { className: "font-sans font-semibold text-foreground", children: keyHint })
578
+ ] }) : null,
579
+ canGoBack ? (
580
+ // After OK in the tab order, drawn quietly at the page's top-left.
581
+ /* @__PURE__ */ jsx(
582
+ Button,
583
+ {
584
+ type: "button",
585
+ variant: "ghost",
586
+ size: "icon",
587
+ "aria-label": "Back",
588
+ onClick: onBack,
589
+ disabled: busy,
590
+ className: "absolute left-2 top-2.5 size-11 text-muted-foreground sm:left-4 sm:top-4.5 [&_svg]:size-6",
591
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
592
+ }
593
+ )
594
+ ) : null
595
+ ] });
596
+ }
597
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-3", className), children: [
598
+ canGoBack ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "lg", onClick: onBack, disabled: busy, children: "Back" }) : null,
599
+ /* @__PURE__ */ jsx(Button, { type: "submit", size: "lg", loading: busy, className: "motion-reduce:[&_.animate-spin]:animate-none", children: label })
600
+ ] });
601
+ }
602
+ function DefaultEnding({ title, description, className }) {
603
+ const { presentation } = useFormPresentation();
604
+ if (presentation === "page") {
605
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col items-center gap-4 text-center", className), children: [
606
+ /* @__PURE__ */ jsx(
607
+ "span",
608
+ {
609
+ "aria-hidden": "true",
610
+ className: "mb-2 flex size-16 items-center justify-center rounded-full bg-primary/10 text-primary",
611
+ children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-8" })
612
+ }
613
+ ),
614
+ /* @__PURE__ */ jsx(
615
+ "h2",
616
+ {
617
+ tabIndex: -1,
618
+ "data-gf-focus": "",
619
+ className: cn(
620
+ "rounded-sm text-3xl font-semibold leading-tight tracking-tight text-balance sm:text-4xl",
621
+ focusRing
622
+ ),
623
+ children: title
624
+ }
625
+ ),
626
+ description ? /* @__PURE__ */ jsx("p", { className: "max-w-md text-lg text-muted-foreground text-pretty", children: description }) : null
627
+ ] });
628
+ }
629
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-2 py-8 text-center", className), children: [
630
+ /* @__PURE__ */ jsx("h2", { tabIndex: -1, "data-gf-focus": "", className: cn("rounded-sm text-2xl font-semibold", focusRing), children: title }),
631
+ description ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: description }) : null
632
+ ] });
633
+ }
634
+ function focusErrorTarget(event) {
635
+ const id = event.currentTarget.hash.slice(1);
636
+ const target = id ? document.getElementById(id) : null;
637
+ if (!target) return;
638
+ event.preventDefault();
639
+ const control = target.matches("input, textarea, select, button") ? target : target.querySelector('[aria-checked="true"]:not(:disabled)') ?? target.querySelector("input:not(:disabled), textarea:not(:disabled), button:not(:disabled)");
640
+ (control ?? target).focus();
641
+ }
642
+ function DefaultErrorSummary({ count, errors, className }) {
643
+ const titleId = useId();
644
+ return (
645
+ // `<Form>` focuses the element marked data-gf-error-summary after a failed submit.
646
+ /* @__PURE__ */ jsxs(
647
+ "div",
648
+ {
649
+ role: "group",
650
+ "aria-labelledby": titleId,
651
+ tabIndex: -1,
652
+ "data-gf-error-summary": "",
653
+ className: cn("space-y-2 rounded-md border border-destructive p-4", focusRing, className),
654
+ children: [
655
+ /* @__PURE__ */ jsx("h2", { id: titleId, className: "text-base font-semibold text-destructive", children: count === 1 ? "One answer needs attention." : `${count} answers need attention.` }),
656
+ /* @__PURE__ */ jsx("ul", { className: "list-disc space-y-1 pl-5 text-sm", children: errors.map((e) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("a", { href: e.href, onClick: focusErrorTarget, className: "font-medium text-destructive underline underline-offset-4", children: [
657
+ e.label,
658
+ ": ",
659
+ e.message
660
+ ] }) }, e.id)) })
661
+ ]
662
+ }
663
+ )
664
+ );
665
+ }
666
+ var ADVANCE_DELAY_MS = 250;
667
+ var engineKeys = /* @__PURE__ */ new WeakMap();
668
+ var nextEngineKey = 0;
669
+ function keyOf(engine) {
670
+ let key = engineKeys.get(engine);
671
+ if (key === void 0) {
672
+ key = nextEngineKey++;
673
+ engineKeys.set(engine, key);
674
+ }
675
+ return key;
676
+ }
677
+ function Form(props) {
678
+ return props.engine ? /* @__PURE__ */ jsx(FormWithEngine, { ...props, engine: props.engine }) : /* @__PURE__ */ jsx(OwnedForm, { ...props });
679
+ }
680
+ function OwnedForm(props) {
681
+ const engine = useForm(props.form, props.options);
682
+ return /* @__PURE__ */ jsx(FormWithEngine, { ...props, engine });
683
+ }
684
+ function FormWithEngine(props) {
685
+ return /* @__PURE__ */ jsx(FormRuntime, { engine: props.engine, registry: props.registry ?? defaultRegistry, children: /* @__PURE__ */ jsx(FormBody, { ...props }, keyOf(props.engine)) });
686
+ }
687
+ function focusWithin(root) {
688
+ if (!root) return;
689
+ const target = root.querySelector("[data-gf-focus]") ?? root.querySelector("[data-gf-question]") ?? root.querySelector("h1, h2, h3");
690
+ if (target && target.tabIndex < 0 && !target.hasAttribute("tabindex")) target.tabIndex = -1;
691
+ (target ?? root).focus();
692
+ }
693
+ function FormBody({
694
+ engine,
695
+ layout: layoutProp,
696
+ presentation = "embedded",
697
+ brand,
698
+ onSubmit,
699
+ onChange,
700
+ autoAdvance = true,
701
+ submitLabel = "Submit",
702
+ nextLabel = "OK",
703
+ retryLabel = "Try again",
704
+ slots,
705
+ slotProps,
706
+ className,
707
+ testID
708
+ }) {
709
+ const { idPrefix } = useFormContext();
710
+ const def = engine.definition;
711
+ const layout = layoutProp ?? def.layout?.mode ?? "classic";
712
+ const status = useFormSelector(engine, (s) => s.status);
713
+ const current = useFormSelector(engine, (s) => s.current);
714
+ const pathKey = useFormSelector(engine, (s) => JSON.stringify(s.path));
715
+ const errorsKey = useFormSelector(engine, (s) => JSON.stringify(s.errors));
716
+ const ending = useFormSelector(engine, (s) => s.ending);
717
+ const historyLength = useFormSelector(engine, (s) => s.history.length);
718
+ const path = JSON.parse(pathKey);
719
+ const errors = JSON.parse(errorsKey);
720
+ const formRef = useRef(null);
721
+ const stepRef = useRef(null);
722
+ const endingRef = useRef(null);
723
+ const advanceTimer = useRef(void 0);
724
+ const previousCurrent = useRef(current);
725
+ const [phase, setPhase] = useState("idle");
726
+ const [announcement, setAnnouncement] = useState("");
727
+ const [summaryRequest, setSummaryRequest] = useState(0);
728
+ const sending = useRef(false);
729
+ const direction = useRef("forward");
730
+ const [modKey, setModKey] = useState("Ctrl");
731
+ const page = presentation === "page";
732
+ useEffect(() => {
733
+ if (/Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent)) setModKey("\u2318");
734
+ }, []);
735
+ const showEnding = status === "completed" && (phase === "sent" || !onSubmit);
736
+ const locked = status === "completed";
737
+ useEffect(() => {
738
+ if (!onChange) return;
739
+ return engine.subscribe(() => onChange(engine.getState(), engine));
740
+ }, [engine, onChange]);
741
+ useLayoutEffect(() => {
742
+ if (previousCurrent.current === current) return;
743
+ previousCurrent.current = current;
744
+ clearTimeout(advanceTimer.current);
745
+ if (layout === "focus") focusWithin(stepRef.current);
746
+ }, [current, layout]);
747
+ useLayoutEffect(() => {
748
+ if (showEnding) focusWithin(endingRef.current);
749
+ }, [showEnding]);
750
+ useLayoutEffect(() => {
751
+ if (!summaryRequest) return;
752
+ const form = formRef.current;
753
+ const summary = form?.querySelector("[data-gf-error-summary]");
754
+ if (summary) summary.focus();
755
+ else form?.querySelector('[aria-invalid="true"]')?.focus();
756
+ }, [summaryRequest]);
757
+ useLayoutEffect(() => {
758
+ if (phase !== "failed") return;
759
+ const form = formRef.current;
760
+ if (form && !form.contains(document.activeElement)) form.querySelector('[type="submit"]')?.focus();
761
+ }, [phase]);
762
+ useEffect(() => () => clearTimeout(advanceTimer.current), []);
763
+ const announce = (message) => setAnnouncement((previous) => previous === message ? `${message}\xA0` : message);
764
+ const announceStepError = () => {
765
+ const state = engine.getState();
766
+ const message = state.current ? state.errors[state.current] : void 0;
767
+ if (message) announce(message);
768
+ };
769
+ const finish = async () => {
770
+ if (sending.current) return;
771
+ if (engine.getState().status !== "completed" && !engine.submit()) {
772
+ if (layout === "classic") setSummaryRequest((n) => n + 1);
773
+ else announceStepError();
774
+ return;
775
+ }
776
+ if (!onSubmit) {
777
+ setPhase("sent");
778
+ return;
779
+ }
780
+ sending.current = true;
781
+ setPhase("sending");
782
+ try {
783
+ await onSubmit(engine);
784
+ setPhase("sent");
785
+ } catch {
786
+ setPhase("failed");
787
+ } finally {
788
+ sending.current = false;
789
+ }
790
+ };
791
+ const isLastStep = (state) => state.current === state.path[state.path.length - 1];
792
+ const advance = () => {
793
+ direction.current = "forward";
794
+ if (isLastStep(engine.getState())) void finish();
795
+ else if (!engine.next()) announceStepError();
796
+ };
797
+ const submit = () => {
798
+ clearTimeout(advanceTimer.current);
799
+ if (layout === "focus") advance();
800
+ else void finish();
801
+ };
802
+ const handleSubmit = (e) => {
803
+ e.preventDefault();
804
+ submit();
805
+ };
806
+ const handleKeyDown = (e) => {
807
+ if (e.key !== "Enter" || e.nativeEvent.isComposing) return;
808
+ const target = e.target;
809
+ if (target instanceof HTMLTextAreaElement) {
810
+ if (e.metaKey || e.ctrlKey) {
811
+ e.preventDefault();
812
+ submit();
813
+ }
814
+ return;
815
+ }
816
+ if (layout === "focus" && target.matches('[role="radio"], [role="checkbox"]')) {
817
+ e.preventDefault();
818
+ submit();
819
+ }
820
+ };
821
+ const scheduleAdvance = layout === "focus" && autoAdvance ? () => {
822
+ clearTimeout(advanceTimer.current);
823
+ advanceTimer.current = setTimeout(() => {
824
+ const state = engine.getState();
825
+ if (state.status === "completed" || isLastStep(state)) return;
826
+ direction.current = "forward";
827
+ if (!engine.next()) announceStepError();
828
+ }, ADVANCE_DELAY_MS);
829
+ } : void 0;
830
+ const goBack = () => {
831
+ clearTimeout(advanceTimer.current);
832
+ direction.current = "back";
833
+ engine.back();
834
+ };
835
+ const liveRegion = /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", "aria-atomic": "true", className: "sr-only", "data-gf-live": "", children: announcement });
836
+ const presentationValue = { presentation, layout };
837
+ const brandNode = brand ? renderSlot(DefaultBrand, slots?.brand, { name: brand.name, logo: brand.logo }, slotProps?.brand) : null;
838
+ if (showEnding) {
839
+ const end = def.endings?.find((x) => x.id === ending) ?? def.endings?.[0];
840
+ const endingNode = renderSlot(
841
+ DefaultEnding,
842
+ slots?.ending,
843
+ {
844
+ title: engine.text(end?.title ?? "Thank you."),
845
+ description: end?.description ? engine.text(end.description) : void 0
846
+ },
847
+ slotProps?.ending
848
+ );
849
+ return /* @__PURE__ */ jsxs(FormPresentationProvider, { value: presentationValue, children: [
850
+ liveRegion,
851
+ page ? /* @__PURE__ */ jsxs(
852
+ "div",
853
+ {
854
+ ref: endingRef,
855
+ "data-presentation": "page",
856
+ "data-testid": testID,
857
+ className: cn("flex min-h-dvh w-full flex-col bg-background text-foreground", className),
858
+ children: [
859
+ brandNode ? /* @__PURE__ */ jsx("div", { className: "flex h-16 shrink-0 items-center justify-center px-16 sm:h-20", children: brandNode }) : null,
860
+ /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center px-4 pb-24 pt-8 sm:px-6", children: /* @__PURE__ */ jsx("div", { className: "w-full max-w-xl", children: endingNode }) })
861
+ ]
862
+ }
863
+ ) : /* @__PURE__ */ jsxs("div", { ref: endingRef, className: cn("mx-auto w-full max-w-xl space-y-6", className), "data-testid": testID, children: [
864
+ brandNode,
865
+ endingNode
866
+ ] })
867
+ ] });
868
+ }
869
+ const pageFocus = page && layout === "focus";
870
+ const pageClassic = page && layout === "classic";
871
+ const renderBlock = (id, focus) => {
872
+ const block = engine.block(id);
873
+ if (!block || block.kind === "page-break") return null;
874
+ if (block.kind === "content") {
875
+ const focusable = focus ? { tabIndex: -1, "data-gf-question": "" } : {};
876
+ const content = block.type === "heading" ? /* @__PURE__ */ jsx(
877
+ "h2",
878
+ {
879
+ className: cn(
880
+ "rounded-sm text-xl font-semibold",
881
+ pageFocus && "text-2xl leading-tight tracking-tight text-balance sm:text-3xl",
882
+ focusRing
883
+ ),
884
+ ...focusable,
885
+ children: engine.text(block.text)
886
+ },
887
+ id
888
+ ) : /* @__PURE__ */ jsx(
889
+ "p",
890
+ {
891
+ className: cn("rounded-sm text-muted-foreground", pageFocus && "text-lg sm:text-xl text-pretty", focusRing),
892
+ ...focusable,
893
+ children: engine.text(block.text)
894
+ },
895
+ id
896
+ );
897
+ return pageClassic ? /* @__PURE__ */ jsx("section", { className: classicSection, children: content }, id) : content;
898
+ }
899
+ const field = /* @__PURE__ */ jsx(FieldRenderer, { id, onAdvance: focus ? scheduleAdvance : void 0 });
900
+ return /* @__PURE__ */ jsx("div", { "data-block": id, className: pageClassic ? classicSection : void 0, children: field }, id);
901
+ };
902
+ const visiblePath = path.filter((id) => engine.isVisible(id) || engine.block(id)?.kind === "content");
903
+ const step = current ? Math.max(1, visiblePath.indexOf(current) + 1) : 1;
904
+ const isLast = layout === "classic" || current === path[path.length - 1];
905
+ const errorItems = path.filter((id) => errors[id]).map((id) => ({
906
+ id,
907
+ label: engine.text(engine.field(id)?.label ?? id),
908
+ message: errors[id],
909
+ href: `#${idPrefix}-${id}`
910
+ }));
911
+ const currentField = pageFocus && current ? engine.field(current) : void 0;
912
+ const keyHint = currentField ? currentField.type === "long-text" ? `${modKey} + Enter` : "Enter \u21B5" : void 0;
913
+ const header = renderSlot(
914
+ DefaultHeader,
915
+ slots?.header,
916
+ { title: def.title, description: def.description },
917
+ slotProps?.header
918
+ );
919
+ const errorSummary = errorItems.length > 0 && layout === "classic" ? renderSlot(
920
+ DefaultErrorSummary,
921
+ slots?.errorSummary,
922
+ {
923
+ count: errorItems.length,
924
+ errors: errorItems,
925
+ className: page ? "bg-card" : void 0
926
+ },
927
+ slotProps?.errorSummary
928
+ ) : null;
929
+ const progress = layout === "focus" ? renderSlot(DefaultProgress, slots?.progress, { step, total: visiblePath.length }, slotProps?.progress) : null;
930
+ const fields = (
931
+ // Disabled once the response is completed (sending, or after a failed send): read-only, not dead.
932
+ /* @__PURE__ */ jsx("fieldset", { disabled: locked, className: "min-w-0 border-0 p-0", children: layout === "focus" ? /* @__PURE__ */ jsx(
933
+ "div",
934
+ {
935
+ ref: stepRef,
936
+ tabIndex: -1,
937
+ className: cn(
938
+ "outline-none",
939
+ pageFocus && "transition-[opacity,translate] duration-300 ease-out starting:opacity-0 motion-reduce:transition-none",
940
+ pageFocus && (direction.current === "back" ? "starting:-translate-y-6" : "starting:translate-y-6")
941
+ ),
942
+ children: current ? renderBlock(current, true) : null
943
+ },
944
+ pageFocus ? current ?? "" : void 0
945
+ ) : /* @__PURE__ */ jsx("div", { className: pageClassic ? "space-y-4" : "space-y-8", children: visiblePath.map((id) => renderBlock(id, false)) }) })
946
+ );
947
+ const failure = phase === "failed" ? /* @__PURE__ */ jsxs("p", { role: "alert", className: cn("text-sm font-medium text-destructive", page && "text-base"), children: [
948
+ "We couldn't send your answers. They're kept here: press ",
949
+ retryLabel,
950
+ " to send them again."
951
+ ] }) : null;
952
+ const navigation = renderSlot(
953
+ DefaultNavigation,
954
+ slots?.navigation,
955
+ {
956
+ canGoBack: layout === "focus" && historyLength > 0 && !locked,
957
+ isLast: isLast || status === "completed",
958
+ busy: phase === "sending",
959
+ failed: phase === "failed",
960
+ onBack: goBack,
961
+ submitLabel,
962
+ nextLabel,
963
+ retryLabel,
964
+ keyHint
965
+ },
966
+ slotProps?.navigation
967
+ );
968
+ const formProps = {
969
+ ref: formRef,
970
+ noValidate: true,
971
+ onSubmit: handleSubmit,
972
+ onKeyDown: handleKeyDown,
973
+ "data-layout": layout,
974
+ "data-presentation": presentation,
975
+ "data-testid": testID
976
+ };
977
+ return /* @__PURE__ */ jsxs(FormPresentationProvider, { value: presentationValue, children: [
978
+ liveRegion,
979
+ pageFocus ? /* @__PURE__ */ jsxs(
980
+ "form",
981
+ {
982
+ ...formProps,
983
+ className: cn("relative flex min-h-dvh w-full flex-col bg-background text-foreground", className),
984
+ children: [
985
+ progress,
986
+ /* @__PURE__ */ jsx("div", { className: "flex h-16 shrink-0 items-center justify-center px-16 sm:h-20", children: brandNode }),
987
+ /* @__PURE__ */ jsx("div", { className: "flex flex-1 flex-col justify-center px-4 pb-20 pt-4 sm:px-6 sm:pb-28", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-xl", children: [
988
+ /* @__PURE__ */ jsx("div", { className: historyLength === 0 ? "mb-5" : "sr-only", children: header }),
989
+ fields,
990
+ failure ? /* @__PURE__ */ jsx("div", { className: "mt-6", children: failure }) : null,
991
+ /* @__PURE__ */ jsx("div", { className: "mt-10", children: navigation })
992
+ ] }) })
993
+ ]
994
+ }
995
+ ) : pageClassic ? /* @__PURE__ */ jsx(
996
+ "form",
997
+ {
998
+ ...formProps,
999
+ className: cn("min-h-dvh w-full bg-muted/40 px-4 py-8 text-foreground sm:py-12", className),
1000
+ children: /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-2xl space-y-4", children: [
1001
+ brandNode ? /* @__PURE__ */ jsx("div", { className: "flex justify-center pb-2", children: brandNode }) : null,
1002
+ header,
1003
+ errorSummary,
1004
+ fields,
1005
+ failure,
1006
+ navigation
1007
+ ] })
1008
+ }
1009
+ ) : /* @__PURE__ */ jsxs("form", { ...formProps, className: cn("mx-auto w-full max-w-xl space-y-8", className), children: [
1010
+ brandNode,
1011
+ header,
1012
+ errorSummary,
1013
+ progress,
1014
+ fields,
1015
+ failure,
1016
+ navigation
1017
+ ] })
1018
+ ] });
1019
+ }
1020
+ var classicSection = "rounded-xl border bg-card px-5 py-6 text-card-foreground sm:px-8 has-[[aria-invalid=true]]:border-destructive";
1021
+
1022
+ export { FieldChrome, Form, builtInRenderers, defaultRegistry, describedBy, useFormPresentation };
1023
+ //# sourceMappingURL=index.js.map
1024
+ //# sourceMappingURL=index.js.map