@formancy/react 0.1.0 → 0.2.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.d.mts +306 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1558 -40
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -5
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/context.tsx","../src/use-field.ts","../src/use-repeater.ts","../src/use-wizard.ts","../src/use-submit.ts","../src/form.tsx","../src/error-summary.tsx"],"sourcesContent":["import { createContext, useContext } from 'react'\nimport type { ReactNode } from 'react'\nimport type { FormEngine } from '@formancy/core'\n\n/**\n * The engine reaches components through context, never through props drilling:\n * a form is one engine instance, and every hook resolves against it. Kept in\n * its own module so tree-shaking can drop the provider for consumers that only\n * use prop getters.\n */\nconst FormancyContext = createContext<FormEngine | null>(null)\n\nexport function FormancyProvider({ engine, children }: { engine: FormEngine; children?: ReactNode }) {\n return <FormancyContext.Provider value={engine}>{children}</FormancyContext.Provider>\n}\n\nexport function useFormEngine(): FormEngine {\n const engine = useContext(FormancyContext)\n if (!engine) {\n throw new Error('formancy hooks need a <FormancyProvider engine={...}> above them in the tree')\n }\n return engine\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react'\nimport { formatPath, parsePath } from '@formancy/core'\nimport type { ControlProps, FieldSnapshot, Path } from '@formancy/core'\nimport { useFormEngine } from './context.js'\n\nexport interface FieldBinding extends FieldSnapshot {\n setValue(value: unknown): void\n touch(): void\n /** Spread onto the input/select/textarea element. */\n controlProps: ControlProps\n /** Spread onto the label element. */\n labelProps: { id: string; htmlFor: string }\n /** Spread onto the error text element. */\n errorProps: { id: string }\n}\n\n/**\n * One field's live state, re-rendering exactly when that field's snapshot\n * changes and never for its siblings.\n *\n * useSyncExternalStore is the honest way to bind an external store in React:\n * the engine's snapshots are identity-stable, so getSnapshot needs no\n * memoisation and concurrent rendering gets a consistent tearing guard for\n * free. The third argument makes SSR render from the same snapshot the client\n * will hydrate with — ids are deterministic, so markup matches.\n */\nexport function useField(path: string | Path): FieldBinding {\n const engine = useFormEngine()\n\n const wire = typeof path === 'string' ? path : formatPath(path)\n const parsed = useMemo(() => parsePath(wire), [wire])\n\n const subscribe = useCallback(\n (onChange: () => void) => engine.subscribeField(parsed, onChange),\n [engine, parsed],\n )\n const getSnapshot = useCallback(() => engine.getFieldSnapshot(parsed), [engine, parsed])\n\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n const setValue = useCallback((value: unknown) => engine.setValue(parsed, value), [engine, parsed])\n const touch = useCallback(() => engine.touch(parsed), [engine, parsed])\n\n return useMemo(\n () => ({\n ...snapshot,\n setValue,\n touch,\n controlProps: snapshot.props.control,\n labelProps: { id: snapshot.props.label.id, htmlFor: snapshot.props.label.for },\n errorProps: snapshot.props.error,\n }),\n [snapshot, setValue, touch],\n )\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react'\nimport { formatPath, parsePath } from '@formancy/core'\nimport type { Path } from '@formancy/core'\nimport { useFormEngine } from './context.js'\n\nexport interface RepeaterBinding {\n rowCount: number\n /** Stable per-row keys, in row order. Never key a row by its index. */\n rowIds: readonly string[]\n addRow(): void\n removeRow(index: number): void\n}\n\n/**\n * A repeater's live row list. Renders exactly when rows are added, removed or\n * edited — the engine treats the repeater path as subscribable in its own\n * right, so this hook needs no polling and no array identity tricks.\n */\nexport function useRepeater(path: string | Path): RepeaterBinding {\n const engine = useFormEngine()\n\n const wire = typeof path === 'string' ? path : formatPath(path)\n const parsed = useMemo(() => parsePath(wire), [wire])\n\n const subscribe = useCallback(\n (onChange: () => void) => engine.subscribeField(parsed, onChange),\n [engine, parsed],\n )\n const getRowCount = useCallback(() => engine.rowCount(parsed), [engine, parsed])\n const rowCount = useSyncExternalStore(subscribe, getRowCount, getRowCount)\n\n const addRow = useCallback(() => engine.addRow(parsed), [engine, parsed])\n const removeRow = useCallback((index: number) => engine.removeRow(parsed, index), [engine, parsed])\n\n // Derived from rowCount rather than subscribed separately: the engine mints\n // an id when a row is created, so the count changing is exactly when the ids\n // change.\n const rowIds = useMemo(\n () => Array.from({ length: rowCount }, (_, index) => engine.rowId(parsed, index)),\n [engine, parsed, rowCount],\n )\n\n return useMemo(\n () => ({ rowCount, rowIds, addRow, removeRow }),\n [rowCount, rowIds, addRow, removeRow],\n )\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react'\nimport { useFormEngine } from './context.js'\n\nexport interface WizardBinding {\n page: number\n pageCount: number\n /** Resolves true iff the page advanced; a failed validation keeps the page. */\n next(): Promise<boolean>\n back(): void\n /** Unvalidated jump — error navigation, never a way past a gate. */\n goTo(pageIndex: number): void\n}\n\n/**\n * The wizard's live position. Throws on an unpaged form rather than degrading\n * to a one-page pseudo-wizard: a stepper rendered over a flat form is a bug in\n * the caller, and a loud error at mount beats a mute component.\n */\nexport function useWizard(): WizardBinding {\n const engine = useFormEngine()\n const wizard = engine.wizard()\n if (wizard === undefined) {\n throw new Error('useWizard needs a schema with pages; this form has none')\n }\n\n const subscribe = useCallback((onChange: () => void) => wizard.subscribe(onChange), [wizard])\n const getPage = useCallback(() => wizard.page(), [wizard])\n const page = useSyncExternalStore(subscribe, getPage, getPage)\n\n return useMemo(\n () => ({\n page,\n pageCount: wizard.pageCount,\n next: () => wizard.next(),\n back: () => wizard.back(),\n goTo: (pageIndex: number) => wizard.goTo(pageIndex),\n }),\n [page, wizard],\n )\n}\n","import { useCallback } from 'react'\nimport { parsePath } from '@formancy/core'\nimport { useFormEngine } from './context.js'\n\n/**\n * Submit the form: touch everything, validate everything, and on failure move\n * focus to the first invalid control — the WCAG-required behaviour that every\n * consumer would otherwise have to hand-roll, and most would forget.\n *\n * Focus lives in the binding, not the engine, because it needs the DOM after\n * the commit; the engine only names the field (firstInvalid) and mints the id.\n */\nexport function useSubmit(): () => { ok: boolean; errors: Record<string, string[]> } {\n const engine = useFormEngine()\n\n return useCallback(() => {\n const outcome = engine.submit()\n if (!outcome.ok && typeof document !== 'undefined') {\n // An ErrorSummary takes precedence: it lists every problem, and focusing\n // it announces the situation once. It focuses ITSELF when errors appear,\n // so nothing to do here. Without one, fall back to the first control.\n const summary = document.querySelector('[data-formancy-part=\"error-summary\"]')\n if (summary === null) {\n const firstInvalid = engine.firstInvalid()\n if (firstInvalid !== null) {\n const ids = engine.getFieldSnapshot(parsePath(firstInvalid)).ids\n document.getElementById(ids.control)?.focus()\n }\n }\n }\n return outcome\n }, [engine])\n}\n","import type { ComponentType, ReactNode } from 'react'\nimport { parsePath } from '@formancy/core'\nimport type { FieldDef, FieldType } from '@formancy/spec'\nimport { useFormEngine } from './context.js'\nimport { useField } from './use-field.js'\nimport type { FieldBinding } from './use-field.js'\nimport { useRepeater } from './use-repeater.js'\nimport { useSubmit } from './use-submit.js'\nimport { useWizard } from './use-wizard.js'\n\n/**\n * The component registry — theming mechanism number two. The schema decides\n * WHAT a field is; the registry decides what RENDERS it, with per-path entries\n * beating per-type entries beating the built-in defaults. A design system\n * replaces the entire visual layer by handing in a registry, without forking\n * anything.\n */\nexport interface FieldComponentProps {\n path: string\n label: string\n}\n\nexport type FieldComponent = ComponentType<FieldComponentProps>\n\nexport interface Registry {\n byType?: Partial<Record<FieldType, FieldComponent>>\n byPath?: Record<string, FieldComponent>\n}\n\nexport interface SubmitOutcome {\n ok: boolean\n errors: Record<string, string[]>\n /** The canonical value, present when accepted. */\n data?: unknown\n}\n\nexport interface FormancyFormProps {\n /**\n * Display text per wire path (row fields by their template wire,\n * `items[].name`). A `label` on the model definition wins — that is how\n * fixture schemas carry text until the spec's i18n section lands; a missing\n * entry falls back to the wire path, which is at least honest.\n */\n labels?: Record<string, string>\n registry?: Registry\n submitLabel?: string\n onSubmit?: (outcome: SubmitOutcome) => void\n}\n\n/**\n * Renders the whole form from the engine: one slot per field, resolved through\n * the registry. Slots subscribe individually, so a keystroke re-renders one\n * field and a visibility flip mounts or unmounts exactly the fields it hit.\n * A paged schema renders a stepper, one page at a time, navigation, and the\n * submit control on the last page — a failed submit navigates to the first\n * page with a problem instead of leaving the user on a clean review page\n * staring at a rejection.\n */\nexport function FormancyForm(props: FormancyFormProps) {\n const engine = useFormEngine()\n return engine.wizard() === undefined ? <FlatForm {...props} /> : <PagedForm {...props} />\n}\n\nfunction SubmitButton({ submitLabel, onSubmit, onFailedNavigate }: FormancyFormProps & { onFailedNavigate?: (page: number) => void }) {\n const engine = useFormEngine()\n const submit = useSubmit()\n return (\n <button\n type=\"button\"\n data-formancy-part=\"submit\"\n onClick={() => {\n const outcome = submit()\n if (!outcome.ok && onFailedNavigate !== undefined) {\n const firstInvalid = engine.firstInvalid()\n if (firstInvalid !== null) onFailedNavigate(engine.pageOf(parsePath(firstInvalid)))\n }\n onSubmit?.(\n outcome.ok\n ? { ok: true, errors: outcome.errors, data: engine.value() }\n : { ok: false, errors: outcome.errors },\n )\n }}\n >\n {submitLabel ?? 'Submit'}\n </button>\n )\n}\n\nfunction FlatForm(props: FormancyFormProps) {\n return (\n <>\n <FieldList {...props} />\n <SubmitButton {...props} />\n </>\n )\n}\n\nfunction PagedForm(props: FormancyFormProps) {\n const engine = useFormEngine()\n const wizard = useWizard()\n const pages = engine.pages()\n const lastPage = wizard.pageCount - 1\n\n return (\n <>\n <nav data-formancy-part=\"stepper\" aria-label=\"Progress\">\n <ol>\n {pages.map((page, index) => (\n <li key={page.key} aria-current={index === wizard.page ? 'step' : undefined}>\n {engine.text(page.def.label) ?? page.key}\n </li>\n ))}\n </ol>\n </nav>\n <FieldList {...props} page={wizard.page} />\n <div data-formancy-part=\"wizard-nav\">\n {wizard.page > 0 ? (\n <button type=\"button\" onClick={() => wizard.back()}>\n Back\n </button>\n ) : null}\n {wizard.page < lastPage ? (\n <button type=\"button\" onClick={() => void wizard.next()}>\n Next\n </button>\n ) : (\n <SubmitButton {...props} onFailedNavigate={(page) => wizard.goTo(page)} />\n )}\n </div>\n </>\n )\n}\n\nfunction FieldList({ labels, registry, page }: FormancyFormProps & { page?: number }) {\n const engine = useFormEngine()\n const repeaterWires = engine.repeaterPaths()\n\n const inPage = (wire: string): boolean =>\n page === undefined || engine.pageOf(parsePath(wire)) === page\n\n // Row fields render inside their repeater's own section, never in the flat\n // list — a row needs its remove button and its position context.\n const staticWires = engine\n .fieldPaths()\n .filter((wire) => !repeaterWires.some((repeater) => wire.startsWith(`${repeater}[`)))\n\n return (\n <>\n {staticWires.filter(inPage).map((wire) => (\n <FieldSlot key={wire} path={wire} fallbackLabel={labels?.[wire]} registry={registry} />\n ))}\n {repeaterWires.filter(inPage).map((wire) => (\n <RepeaterSection key={wire} wire={wire} labels={labels} registry={registry} />\n ))}\n </>\n )\n}\n\nfunction FieldSlot({\n path,\n fallbackLabel,\n registry,\n}: {\n path: string\n fallbackLabel?: string | undefined\n registry?: Registry | undefined\n}) {\n const field = useField(path)\n\n // A hidden field leaves the DOM entirely: display:none would still ship the\n // markup, keep it in the accessibility tree's shadow, and leak its labels\n // to screen-reader \"read all\" passes.\n if (!field.visible) return null\n\n const Component =\n registry?.byPath?.[path] ?? registry?.byType?.[field.type] ?? DEFAULT_COMPONENTS[field.type]\n if (Component === null) return null\n\n const label = field.label ?? fallbackLabel ?? path\n return <Component path={path} label={label} />\n}\n\nfunction RepeaterSection({\n wire,\n labels,\n registry,\n}: {\n wire: string\n labels?: Record<string, string> | undefined\n registry?: Registry | undefined\n}) {\n const engine = useFormEngine()\n const repeater = useRepeater(wire)\n const def = engine.repeaters().find((candidate) => candidate.wire === wire)?.def\n const label = engine.text(def?.label) ?? labels?.[wire] ?? wire\n const addLabel = def?.addLabel ?? `Add ${label}`\n const removeLabel = def?.removeLabel ?? `Remove ${label}`\n\n const fallbackFor = (instanceWire: string): string | undefined => {\n const template = instanceWire.replace(/\\[\\d+\\]/, '[]')\n return labels?.[template] ?? labels?.[instanceWire]\n }\n\n return (\n <fieldset data-formancy-part=\"repeater\">\n <legend data-formancy-part=\"repeater-legend\">{label}</legend>\n {repeater.rowIds.map((rowId, index) => (\n // Keyed by identity, not position: removing a row renumbers every row\n // after it, and an index key would make React reuse the wrong DOM\n // nodes — moving focus and animating the wrong element.\n <div data-formancy-part=\"row\" key={rowId}>\n {engine\n .fieldPaths()\n .filter((candidate) => candidate.startsWith(`${wire}[${index}]`))\n .map((instanceWire) => (\n <FieldSlot\n key={instanceWire}\n path={instanceWire}\n fallbackLabel={fallbackFor(instanceWire)}\n registry={registry}\n />\n ))}\n {/* Position context in the NAME, so a screen-reader user knows which\n row this button kills without walking the tree. */}\n <button type=\"button\" onClick={() => repeater.removeRow(index)}>\n {`${removeLabel} ${index + 1} of ${repeater.rowCount}`}\n </button>\n </div>\n ))}\n <button type=\"button\" onClick={() => repeater.addRow()}>\n {addLabel}\n </button>\n </fieldset>\n )\n}\n\n/** Shared unstyled shell: real label, control, error text as the describedby\n * target. Zero CSS; `data-formancy-part` is the styling hook. */\nfunction FieldShell({\n field,\n label,\n children,\n}: {\n field: FieldBinding\n label: string\n children: ReactNode\n}) {\n return (\n <div\n data-formancy-part=\"field\"\n data-state={field.touched && field.errors.length > 0 ? 'invalid' : 'valid'}\n >\n <label data-formancy-part=\"label\" {...field.labelProps}>\n {label}\n </label>\n {children}\n {field.touched && field.errors.length > 0 ? (\n <p data-formancy-part=\"error\" {...field.errorProps}>\n {field.errors.join(', ')}\n </p>\n ) : null}\n </div>\n )\n}\n\nfunction TextField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n return (\n <FieldShell field={field} label={label}>\n <input\n type=\"text\"\n {...field.controlProps}\n value={typeof field.value === 'string' ? field.value : ''}\n onChange={(event) => field.setValue(event.target.value)}\n onBlur={() => field.touch()}\n />\n </FieldShell>\n )\n}\n\nfunction TextareaField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n return (\n <FieldShell field={field} label={label}>\n <textarea\n {...field.controlProps}\n value={typeof field.value === 'string' ? field.value : ''}\n onChange={(event) => field.setValue(event.target.value)}\n onBlur={() => field.touch()}\n />\n </FieldShell>\n )\n}\n\nfunction NumberField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n return (\n <FieldShell field={field} label={label}>\n <input\n type=\"number\"\n {...field.controlProps}\n value={typeof field.value === 'number' ? field.value : ''}\n onChange={(event) =>\n field.setValue(event.target.value === '' ? null : event.target.valueAsNumber)\n }\n onBlur={() => field.touch()}\n />\n </FieldShell>\n )\n}\n\nfunction CheckboxField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n return (\n <FieldShell field={field} label={label}>\n <input\n type=\"checkbox\"\n {...field.controlProps}\n checked={field.value === true}\n onChange={(event) => field.setValue(event.target.checked)}\n onBlur={() => field.touch()}\n />\n </FieldShell>\n )\n}\n\nfunction DateField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n return (\n <FieldShell field={field} label={label}>\n <input\n type=\"date\"\n {...field.controlProps}\n value={typeof field.value === 'string' ? field.value : ''}\n onChange={(event) => field.setValue(event.target.value === '' ? null : event.target.value)}\n onBlur={() => field.touch()}\n />\n </FieldShell>\n )\n}\n\n/**\n * Option labels resolved to strings, since a label may be a message reference.\n * Falling back to the stored value keeps an untranslated option selectable\n * rather than blank.\n */\nfunction useResolvedOptions(field: { def: FieldDef }): Array<{ value: string; label: string }> {\n const engine = useFormEngine()\n return (field.def.options ?? []).map((option) => ({\n value: option.value,\n label: engine.text(option.label) ?? option.value,\n }))\n}\n\nfunction SelectField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n const options = useResolvedOptions(field)\n return (\n <FieldShell field={field} label={label}>\n {/* Setting `value` on the select works only because React applies it\n AFTER the option children render; a select's value property is\n settable once its options exist. Angular binds [selected] per option\n for the same reason — the explicit form of the same contract. */}\n <select\n {...field.controlProps}\n value={typeof field.value === 'string' ? field.value : ''}\n onChange={(event) => field.setValue(event.target.value === '' ? null : event.target.value)}\n onBlur={() => field.touch()}\n >\n {/* The empty option is the unanswered state; without it the browser\n silently pre-selects the first real option, which the engine never\n heard about. */}\n <option value=\"\" />\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </FieldShell>\n )\n}\n\nfunction RadioGroupField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n const options = useResolvedOptions(field)\n const showError = field.touched && field.errors.length > 0\n return (\n <fieldset\n data-formancy-part=\"field\"\n data-state={showError ? 'invalid' : 'valid'}\n aria-describedby={field.controlProps['aria-describedby']}\n >\n <legend data-formancy-part=\"label\">{label}</legend>\n {options.map((option) => {\n const optionId = `${field.ids.control}:${option.value}`\n return (\n <span key={option.value} data-formancy-part=\"radio-option\">\n <input\n type=\"radio\"\n id={optionId}\n name={field.controlProps.name}\n value={option.value}\n checked={field.value === option.value}\n onChange={() => field.setValue(option.value)}\n onBlur={() => field.touch()}\n />\n <label htmlFor={optionId}>{option.label}</label>\n </span>\n )\n })}\n {showError ? (\n <p data-formancy-part=\"error\" {...field.errorProps}>\n {field.errors.join(', ')}\n </p>\n ) : null}\n </fieldset>\n )\n}\n\n/**\n * The built-in unstyled components. `null` means the type renders nothing here:\n * hidden and static are non-inputs, and the container types are laid out by\n * their own machinery, not by a leaf slot.\n */\nconst DEFAULT_COMPONENTS: Record<FieldType, FieldComponent | null> = {\n text: TextField,\n textarea: TextareaField,\n number: NumberField,\n checkbox: CheckboxField,\n date: DateField,\n select: SelectField,\n radio: RadioGroupField,\n hidden: null,\n static: null,\n group: null,\n page: null,\n repeater: null,\n}\n","import { useCallback, useEffect, useRef, useSyncExternalStore } from 'react'\nimport { parsePath } from '@formancy/core'\nimport { useFormEngine } from './context.js'\n\nexport interface ErrorSummaryProps {\n labels?: Record<string, string>\n}\n\n/**\n * The error summary a failed submit focuses.\n *\n * The semantics are deliberate and easy to get wrong: the container takes\n * focus via tabindex=\"-1\" — focusing it already makes screen readers announce\n * it, so role=\"alert\" would announce it TWICE. Each entry is a real in-page\n * link to the offending control, because links are what \"take me to the\n * problem\" already means to assistive tech.\n */\nexport function ErrorSummary({ labels }: ErrorSummaryProps) {\n const engine = useFormEngine()\n const subscribe = useCallback((onChange: () => void) => engine.subscribe(onChange), [engine])\n const getErrors = useCallback(() => engine.visibleErrors(), [engine])\n const errors = useSyncExternalStore(subscribe, getErrors, getErrors)\n const region = useRef<HTMLDivElement | null>(null)\n const previousCount = useRef(0)\n\n // Focus when errors APPEAR (a submit surfaced them), not on every keystroke\n // that edits an already-broken field.\n useEffect(() => {\n if (errors.length > 0 && previousCount.current === 0) region.current?.focus()\n previousCount.current = errors.length\n }, [errors])\n\n if (errors.length === 0) return null\n\n const labelFor = (path: string): string =>\n labels?.[path] ?? labels?.[path.replace(/\\[\\d+\\]/, '[]')] ?? path\n\n return (\n <div data-formancy-part=\"error-summary\" tabIndex={-1} ref={region}>\n <h2 data-formancy-part=\"error-summary-heading\">\n {errors.length === 1 ? 'There is 1 problem to fix' : `There are ${errors.length} problems to fix`}\n </h2>\n <ul>\n {errors.map(({ path, codes }) => {\n const controlId = engine.getFieldSnapshot(parsePath(path)).ids.control\n return (\n <li key={path}>\n <a\n href={`#${controlId}`}\n onClick={(event) => {\n // The hash alone scrolls but does not focus; do both.\n event.preventDefault()\n document.getElementById(controlId)?.focus()\n }}\n >\n {`${labelFor(path)}: ${codes.join(', ')}`}\n </a>\n </li>\n )\n })}\n </ul>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/context.tsx","../src/use-field.ts","../src/use-repeater.ts","../src/use-wizard.ts","../src/focus-control.ts","../src/use-submit.ts","../src/layout.tsx","../src/rich-text.tsx","../src/rich-text-editor.ts","../src/scanning.ts","../src/options-source.ts","../src/use-sourced-options.ts","../src/uploads.ts","../src/form.tsx","../src/error-summary.tsx","../src/resume-notice.tsx"],"sourcesContent":["import { createContext, useContext } from 'react'\nimport type { ReactNode } from 'react'\nimport type { FormEngine } from '@formancy/core'\n\n/**\n * The engine reaches components through context, never through props drilling:\n * a form is one engine instance, and every hook resolves against it. Kept in\n * its own module so tree-shaking can drop the provider for consumers that only\n * use prop getters.\n */\nconst FormancyContext = createContext<FormEngine | null>(null)\n\nexport function FormancyProvider({ engine, children }: { engine: FormEngine; children?: ReactNode }) {\n return <FormancyContext.Provider value={engine}>{children}</FormancyContext.Provider>\n}\n\nexport function useFormEngine(): FormEngine {\n const engine = useContext(FormancyContext)\n if (!engine) {\n throw new Error('formancy hooks need a <FormancyProvider engine={...}> above them in the tree')\n }\n return engine\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react'\nimport { formatPath, parsePath } from '@formancy/core'\nimport type { ControlProps, FieldSnapshot, Path } from '@formancy/core'\nimport { useFormEngine } from './context.js'\n\nexport interface FieldBinding extends FieldSnapshot {\n setValue(value: unknown): void\n touch(): void\n /** Spread onto the input/select/textarea element. */\n controlProps: ControlProps\n /** Spread onto the label element. */\n labelProps: { id: string; htmlFor: string }\n /** Spread onto the error text element. */\n errorProps: { id: string }\n}\n\n/**\n * One field's live state, re-rendering exactly when that field's snapshot\n * changes and never for its siblings.\n *\n * useSyncExternalStore is the honest way to bind an external store in React:\n * the engine's snapshots are identity-stable, so getSnapshot needs no\n * memoisation and concurrent rendering gets a consistent tearing guard for\n * free. The third argument makes SSR render from the same snapshot the client\n * will hydrate with — ids are deterministic, so markup matches.\n */\nexport function useField(path: string | Path): FieldBinding {\n const engine = useFormEngine()\n\n const wire = typeof path === 'string' ? path : formatPath(path)\n const parsed = useMemo(() => parsePath(wire), [wire])\n\n const subscribe = useCallback(\n (onChange: () => void) => engine.subscribeField(parsed, onChange),\n [engine, parsed],\n )\n const getSnapshot = useCallback(() => engine.getFieldSnapshot(parsed), [engine, parsed])\n\n const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)\n\n const setValue = useCallback((value: unknown) => engine.setValue(parsed, value), [engine, parsed])\n const touch = useCallback(() => engine.touch(parsed), [engine, parsed])\n\n return useMemo(\n () => ({\n ...snapshot,\n setValue,\n touch,\n controlProps: snapshot.props.control,\n labelProps: { id: snapshot.props.label.id, htmlFor: snapshot.props.label.for },\n errorProps: snapshot.props.error,\n }),\n [snapshot, setValue, touch],\n )\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react'\nimport { formatPath, parsePath } from '@formancy/core'\nimport type { Path } from '@formancy/core'\nimport { useFormEngine } from './context.js'\n\nexport interface RepeaterBinding {\n rowCount: number\n /** Stable per-row keys, in row order. Never key a row by its index. */\n rowIds: readonly string[]\n addRow(): void\n removeRow(index: number): void\n /** Move a row. The keyboard route to reordering; a drag is a second route to this. */\n moveRow(from: number, to: number): void\n}\n\n/**\n * A repeater's live row list. Renders exactly when rows are added, removed or\n * edited — the engine treats the repeater path as subscribable in its own\n * right, so this hook needs no polling and no array identity tricks.\n */\nexport function useRepeater(path: string | Path): RepeaterBinding {\n const engine = useFormEngine()\n\n const wire = typeof path === 'string' ? path : formatPath(path)\n const parsed = useMemo(() => parsePath(wire), [wire])\n\n const subscribe = useCallback(\n (onChange: () => void) => engine.subscribeField(parsed, onChange),\n [engine, parsed],\n )\n const getRowCount = useCallback(() => engine.rowCount(parsed), [engine, parsed])\n const rowCount = useSyncExternalStore(subscribe, getRowCount, getRowCount)\n\n const addRow = useCallback(() => engine.addRow(parsed), [engine, parsed])\n const removeRow = useCallback((index: number) => engine.removeRow(parsed, index), [engine, parsed])\n const moveRow = useCallback(\n (from: number, to: number) => engine.moveRow(parsed, from, to),\n [engine, parsed],\n )\n\n // Subscribed, not derived from rowCount.\n //\n // This read `useMemo(..., [rowCount])`, on the reasoning that \"the engine mints an id\n // when a row is created, so the count changing is exactly when the ids change\". That\n // was true until rows could be REORDERED: a move leaves the count alone and changes\n // the order, so the memo held stale ids and React would key rows by them -- reusing\n // the wrong DOM nodes, which is the exact failure keying by identity exists to\n // prevent, and worse than keying by index because it looks correct.\n //\n // Joined into one string for the snapshot because `useSyncExternalStore` compares by\n // identity and a fresh array every read would loop for ever.\n const getRowIdList = useCallback(\n () => Array.from({ length: engine.rowCount(parsed) }, (_, index) => engine.rowId(parsed, index)).join(' '),\n [engine, parsed],\n )\n const rowIdList = useSyncExternalStore(subscribe, getRowIdList, getRowIdList)\n const rowIds = useMemo(() => (rowIdList === '' ? [] : rowIdList.split(' ')), [rowIdList])\n\n return useMemo(\n () => ({ rowCount, rowIds, addRow, removeRow, moveRow }),\n [rowCount, rowIds, addRow, removeRow, moveRow],\n )\n}\n","import { useCallback, useMemo, useSyncExternalStore } from 'react'\nimport { useFormEngine } from './context.js'\n\nexport interface WizardBinding {\n page: number\n pageCount: number\n /** Resolves true iff the page advanced; a failed validation keeps the page. */\n next(): Promise<boolean>\n back(): void\n /** Unvalidated jump — error navigation, never a way past a gate. */\n goTo(pageIndex: number): void\n}\n\n/**\n * The wizard's live position. Throws on an unpaged form rather than degrading\n * to a one-page pseudo-wizard: a stepper rendered over a flat form is a bug in\n * the caller, and a loud error at mount beats a mute component.\n */\nexport function useWizard(): WizardBinding {\n const engine = useFormEngine()\n const wizard = engine.wizard()\n if (wizard === undefined) {\n throw new Error('useWizard needs a schema with pages; this form has none')\n }\n\n const subscribe = useCallback((onChange: () => void) => wizard.subscribe(onChange), [wizard])\n const getPage = useCallback(() => wizard.page(), [wizard])\n const page = useSyncExternalStore(subscribe, getPage, getPage)\n\n return useMemo(\n () => ({\n page,\n pageCount: wizard.pageCount,\n next: () => wizard.next(),\n back: () => wizard.back(),\n goTo: (pageIndex: number) => wizard.goTo(pageIndex),\n }),\n [page, wizard],\n )\n}\n","/** Reveal enclosing tabs before focusing: hidden controls cannot emit focusin. */\nexport function focusControl(control: HTMLElement | null): void {\n if (control === null) return\n control.dispatchEvent(new Event('formancy-reveal', { bubbles: true }))\n control.focus()\n}\n","import { focusControl } from './focus-control.js'\nimport { useCallback } from 'react'\nimport { parsePath } from '@formancy/core'\nimport { useFormEngine } from './context.js'\n\n/**\n * Submit the form: touch everything, validate everything, and on failure move\n * focus to the first invalid control — the WCAG-required behaviour that every\n * consumer would otherwise have to hand-roll, and most would forget.\n *\n * Focus lives in the binding, not the engine, because it needs the DOM after\n * the commit; the engine only names the field (firstInvalid) and mints the id.\n */\nexport function useSubmit(): () => { ok: boolean; errors: Record<string, string[]> } {\n const engine = useFormEngine()\n\n return useCallback(() => {\n const outcome = engine.submit()\n if (!outcome.ok && typeof document !== 'undefined') {\n // An ErrorSummary takes precedence: it lists every problem, and focusing\n // it announces the situation once. It focuses ITSELF when errors appear,\n // so nothing to do here. Without one, fall back to the first control.\n const summary = document.querySelector('[data-formancy-part=\"error-summary\"]')\n if (summary === null) {\n const firstInvalid = engine.firstInvalid()\n if (firstInvalid !== null) {\n const ids = engine.getFieldSnapshot(parsePath(firstInvalid)).ids\n focusControl(document.getElementById(ids.control))\n }\n }\n }\n return outcome\n }, [engine])\n}\n","\nimport { flushSync } from 'react-dom'\nimport { useEffect, useId, useRef, useState } from 'react'\nimport type { CSSProperties, ReactElement } from 'react'\nimport type { FormSchema, LayoutNode } from '@formancy/spec'\nimport { resolveText, LAYOUT_LEAF_KINDS, layoutChildren } from '@formancy/spec'\nimport { encode } from 'uqr'\nimport { useField } from './use-field.js'\n\n/**\n * Rendering a named `layouts` entry: fields side by side, in sections, in the\n * arrangement the document asks for rather than model order.\n *\n * Four WCAG criteria decide how this is built, and all four point the same way\n * — the DOM is the arrangement, and CSS only decides how wide things are.\n *\n * **1.3.2 Meaningful Sequence** and **2.4.3 Focus Order.** Children are emitted\n * in the order the layout declares, and the stylesheet places them by source\n * order alone. No `order`, no explicit `grid-column`, nothing that lets the\n * visual order and the DOM order disagree — because when they do, a screen\n * reader and a keyboard meet the form in one order and an eye meets it in\n * another.\n *\n * **1.4.10 Reflow.** A row must become a single column when there is no space\n * for two, with no horizontal scrolling at 320 CSS pixels. That is a media\n * query in the stylesheet, not a measurement here: a layout that reflows only\n * when JavaScript has run is a layout that does not reflow.\n *\n * **1.3.1 Info and Relationships.** A row is presentation and gets no\n * semantics. A *section* with a heading is visibly grouping fields, so it says\n * so programmatically too — `role=\"group\"` with `aria-labelledby`. Marking up\n * the row instead would invent a relationship the author did not describe, and\n * leaving the section bare would hide one they did.\n *\n * Every container also carries `data-formancy-layout-path`, the index path of\n * the node that produced it. It is inert here — nothing in this package reads\n * it — and exists so a tool outside the renderer, such as the builder's\n * arrange-on-the-preview surface, can say which node an element came from\n * without the renderer knowing anything about editing.\n */\n\nexport interface LayoutTreeProps {\n schema: FormSchema\n nodes: readonly LayoutNode[]\n locale: string\n /** Renders one field, given its data path. */\n renderField: (path: string) => ReactElement | null\n /** Index path of the container these nodes are the children of. */\n at?: readonly number[]\n}\n\nexport function LayoutTree({\n schema,\n nodes,\n locale,\n renderField,\n at = [],\n}: LayoutTreeProps): ReactElement {\n return (\n <>\n {nodes.map((node, index) => {\n const view = (\n <LayoutNodeView\n // Position is the only identity a layout node has, and a layout does\n // not reorder while a form is being filled in.\n key={index}\n schema={schema}\n node={node}\n locale={locale}\n renderField={renderField}\n at={[...at, index]}\n />\n )\n\n // A node that spans gets a cell to span WITH, and one that does not is left\n // exactly as it was -- a direct child of the container, so nothing about the\n // markup of a form that uses no span changes at all.\n //\n // The cell is the grid item rather than the node itself, because the node's own\n // element is produced further down (a field's control, a nested table, a code)\n // and a renderer cannot reach into a component a consumer registered.\n //\n // Two channels for one fact, and the reason is arithmetic: `data-span` is the\n // authored value, which a selector can match; `--fm-span` is the same number\n // where CSS can COUNT with it, because `grid-column: span attr(data-span)` is not\n // a thing. `all` needs no number -- it is `1 / -1`, whatever the column count --\n // so it carries no property, and the theme's fallback covers the rest.\n if (node.span === undefined) return view\n return (\n <div\n key={index}\n data-formancy-part=\"layout-cell\"\n data-span={String(node.span)}\n {...(node.span === 'all'\n ? {}\n : { style: { '--fm-span': String(node.span) } as CSSProperties })}\n >\n {view}\n </div>\n )\n })}\n </>\n )\n}\n\nfunction LayoutNodeView({\n schema,\n node,\n locale,\n renderField,\n at,\n}: {\n schema: FormSchema\n node: LayoutNode\n locale: string\n renderField: (path: string) => ReactElement | null\n at: readonly number[]\n}): ReactElement | null {\n const headingId = useId()\n\n if (node.kind === 'field') return renderField(node.path)\n if (node.kind === 'qrcode') {\n return <CodeNode schema={schema} node={node} locale={locale} />\n }\n\n const label = resolveText(schema, node.label, locale)\n const here = at.join('.')\n const children = (\n <LayoutTree\n schema={schema}\n nodes={layoutChildren(node)}\n locale={locale}\n renderField={renderField}\n at={at}\n />\n )\n\n if (node.kind === 'row') {\n // Presentation only. A row carries no meaning a screen reader needs, and\n // announcing \"group\" around every pair of fields is noise.\n return (\n <div\n data-formancy-part=\"layout-row\"\n data-formancy-layout-path={here}\n data-columns={String(node.children.length)}\n >\n {children}\n </div>\n )\n }\n\n if (node.kind === 'column') {\n return (\n <div data-formancy-part=\"layout-column\" data-formancy-layout-path={here}>\n {children}\n </div>\n )\n }\n\n if (node.kind === 'tabs') {\n return (\n <Tabs\n schema={schema}\n node={node}\n locale={locale}\n renderField={renderField}\n at={at}\n here={here}\n />\n )\n }\n\n if (node.kind === 'table') {\n return (\n <div\n data-formancy-part=\"layout-table\"\n data-formancy-layout-path={here}\n data-columns={String(node.columns)}\n {...(label === undefined ? {} : { role: 'group', 'aria-labelledby': headingId })}\n >\n {label === undefined ? null : (\n <p id={headingId} data-formancy-part=\"layout-section-heading\">\n {label}\n </p>\n )}\n {children}\n </div>\n )\n }\n\n // A section. Labelled, it is a real grouping and says so; unlabelled, it is\n // a box and stays one.\n if (label === undefined) {\n return (\n <div data-formancy-part=\"layout-section\" data-formancy-layout-path={here}>\n {children}\n </div>\n )\n }\n\n return (\n <div\n data-formancy-part=\"layout-section\"\n data-formancy-layout-path={here}\n role=\"group\"\n aria-labelledby={headingId}\n >\n <p id={headingId} data-formancy-part=\"layout-section-heading\">\n {label}\n </p>\n {children}\n </div>\n )\n}\n\n/**\n * One panel at a time, behind a row of tabs.\n *\n * The ARIA tabs pattern, and the reason it is written out rather than reached\n * for from a library: the keyboard behaviour is the specification. Arrows move\n * between tabs, Home and End reach the ends, and the tab strip is ONE tab stop\n * — a roving tabindex — so a form with twelve tabs does not cost twelve\n * presses to get past.\n *\n * **Every panel stays in the DOM.** A closed tab is hidden, not unmounted,\n * which is the decision the rest of this component follows from. Tabs are\n * presentation, unlike pages: a field in a closed tab is still validated and\n * still submitted, so it has to exist to be validated, and the browser's own\n * find-in-page finds it. Unmounting would also throw away what somebody had\n * typed the moment they looked at another tab.\n *\n * **A tab opens when an error is in it.** An error summary focuses the first\n * invalid control, and focusing something inside a hidden panel does nothing\n * at all — the reader is told the form has an error and sent nowhere. So the\n * strip handles an explicit reveal request before the control receives focus.\n */\nfunction Tabs({\n schema,\n node,\n locale,\n renderField,\n at,\n here,\n}: {\n schema: FormSchema\n node: Extract<LayoutNode, { kind: 'tabs' }>\n locale: string\n renderField: (path: string) => ReactElement | null\n at: readonly number[]\n here: string\n}): ReactElement {\n const base = useId()\n const [open, setOpen] = useState(0)\n const strip = useRef<HTMLDivElement | null>(null)\n const panels = useRef<Array<HTMLDivElement | null>>([])\n const tabs = useRef<Array<HTMLButtonElement | null>>([])\n /** Set only by a key press, so focus is never taken from elsewhere. */\n const moveFocus = useRef(false)\n\n const names = node.children.map((child) =>\n child.kind === 'field' ? undefined : resolveText(schema, child.label, locale),\n )\n\n useEffect(() => {\n if (!moveFocus.current) return\n moveFocus.current = false\n tabs.current[open]?.focus()\n }, [open])\n\n // Commit the tab change before focusControl calls focus(). The reveal event\n // bubbles through all enclosing panels, including nested tab strips.\n useEffect(() => {\n const listeners = panels.current.map((panel, index) => {\n if (panel === null) return undefined\n const reveal = (): void => flushSync(() => setOpen(index))\n panel.addEventListener('formancy-reveal', reveal)\n return () => panel.removeEventListener('formancy-reveal', reveal)\n })\n return () => {\n for (const off of listeners) off?.()\n }\n }, [node.children.length])\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {\n const last = node.children.length - 1\n const next =\n event.key === 'ArrowRight'\n ? Math.min(open + 1, last)\n : event.key === 'ArrowLeft'\n ? Math.max(open - 1, 0)\n : event.key === 'Home'\n ? 0\n : event.key === 'End'\n ? last\n : undefined\n if (next === undefined) return\n event.preventDefault()\n moveFocus.current = true\n setOpen(next)\n }\n\n const stripName = resolveText(schema, node.label, locale)\n\n return (\n <div data-formancy-part=\"layout-tabs\" data-formancy-layout-path={here}>\n <div\n ref={strip}\n role=\"tablist\"\n {...(stripName === undefined ? {} : { 'aria-label': stripName })}\n data-formancy-part=\"tablist\"\n onKeyDown={onKeyDown}\n >\n {node.children.map((child, index) => (\n <button\n key={index}\n ref={(element) => {\n tabs.current[index] = element\n }}\n type=\"button\"\n role=\"tab\"\n id={`${base}-tab-${String(index)}`}\n aria-controls={`${base}-panel-${String(index)}`}\n aria-selected={index === open}\n // Roving tabindex: the strip is one stop, not one per tab.\n tabIndex={index === open ? 0 : -1}\n data-formancy-part=\"tab\"\n onClick={() => setOpen(index)}\n >\n {names[index] ?? `Tab ${String(index + 1)}`}\n </button>\n ))}\n </div>\n\n {node.children.map((child, index) => (\n <div\n key={index}\n ref={(element) => {\n panels.current[index] = element\n }}\n role=\"tabpanel\"\n id={`${base}-panel-${String(index)}`}\n aria-labelledby={`${base}-tab-${String(index)}`}\n data-formancy-part=\"tabpanel\"\n // `hidden` rather than unmounting, and rather than CSS: a panel\n // hidden with display:none by a stylesheet that failed to load is a\n // panel that is not hidden at all.\n hidden={index !== open}\n // Not focusable itself. The pattern allows it when a panel has no\n // focusable content; every panel here holds form fields, and a\n // panel that also takes focus makes a person tab through it twice.\n >\n <LayoutTree\n schema={schema}\n nodes={LAYOUT_LEAF_KINDS.has(child.kind) ? [child] : layoutChildren(child)}\n locale={locale}\n renderField={renderField}\n at={[...at, index]}\n />\n </div>\n ))}\n </div>\n )\n}\n\n/**\n * A machine-readable code drawn from an answer the form already holds.\n *\n * **The accessible content is the value, not the picture.** The value is real text in\n * the document; a drawing, when a consumer registers one, is decorative — a screen\n * reader needs the value it encodes, which somebody can read, copy or dictate.\n *\n * **Out of the box a `qrcode` node shows the value as text and no code.** Encoding one\n * is roughly 10 kB and a SOUP row for every consumer, in a package budgeted at 4 kB, to\n * draw something a design system may want to draw its own way. A consumer who wants the\n * picture registers a component for it\n * ([0070](../../../docs/decisions/0070-a-code-is-an-arrangement-not-a-field.md)).\n */\nfunction CodeNode({\n schema,\n node,\n locale,\n}: {\n schema: FormSchema\n node: Extract<LayoutNode, { kind: 'qrcode' }>\n locale: string\n}): ReactElement {\n const label = resolveText(schema, node.label, locale)\n const labelId = useId()\n // `useField`, not `engine.getFieldSnapshot`. Reading the snapshot directly renders the\n // value once and never again: measured, the code stayed `data-state=\"empty\"` after the\n // answer was typed. A code is a live view of an answer, so it subscribes like any other\n // reader of one.\n const field = useField(node.path)\n const value = field.value\n const text = typeof value === 'string' ? value : value === null || value === undefined ? '' : String(value)\n\n return (\n <div data-formancy-part=\"code\" data-state={text === '' ? 'empty' : 'ready'}>\n {/* Named, and the name is ATTACHED. It was a loose span beside the value, and\n measured, the value's accessible name was the empty string: a screen reader\n announced a booking reference with nothing to say what it was. A label that\n only looks like a label is the failure this whole repository's `describedby`\n composition exists to prevent, in the one place a layout node does its own\n wiring. */}\n {label === undefined ? null : (\n <span id={labelId} data-formancy-part=\"code-label\">\n {label}\n </span>\n )}\n {/* The drawing. Decorative: `aria-hidden`, because a picture of a code says nothing\n to a screen reader and an `alt` of \"QR code\" says nothing either — the value\n below is the content.\n\n Built here rather than with `uqr.renderSVG`, which emits `fill=\"white\"` and\n `fill=\"black\"`. A renderer shipping colours is the thing this project exists to\n avoid, so the modules are drawn with `currentColor` and the light ones are simply\n absent: a theme sets `color` and whatever is behind shows through.\n\n Nothing is drawn for an empty answer. An empty string encodes to a perfectly\n valid code, and a scannable picture of nothing is worse than no picture because\n somebody would scan it. */}\n {text === '' ? null : <CodeDrawing value={text} />}\n {/* The value, as text, always. A reader who cannot see the code reads this; a\n reader who can see one still has something to copy.\n\n `<output>` is a live region, so its text is announced when the answer changes\n — which is exactly right for a second view of an answer — and that is also why\n it must be named: an unnamed live region reads a string out of nowhere. */}\n <output\n data-formancy-part=\"code-value\"\n {...(label === undefined ? {} : { 'aria-labelledby': labelId })}\n >\n {text}\n </output>\n </div>\n )\n}\n\n/**\n * The modules of a QR code as one SVG.\n *\n * `viewBox` in module units with a one-module quiet zone — four is the specification's\n * recommendation and is drawn by the theme's padding instead, because a quiet zone baked\n * into the picture is whitespace a design system cannot remove.\n *\n * One `<rect>` per dark module rather than one path: a rect carries its own `fill`, so a\n * theme can address them, and the node count is bounded by the version (a version 1 code is\n * 23×23).\n */\nfunction CodeDrawing({ value }: { value: string }): ReactElement {\n const { size, data } = encode(value)\n const modules: ReactElement[] = []\n for (const [row, cells] of data.entries()) {\n for (const [column, dark] of cells.entries()) {\n if (!dark) continue\n modules.push(\n <rect key={`${String(row)}.${String(column)}`} x={column} y={row} width={1} height={1} fill=\"currentColor\" />,\n )\n }\n }\n\n return (\n <svg\n data-formancy-part=\"code-drawing\"\n viewBox={`0 0 ${String(size)} ${String(size)}`}\n // Decoration. The value beside it is the content.\n aria-hidden=\"true\"\n focusable=\"false\"\n // `shape-rendering` because a code scaled to a non-integer size gets antialiased\n // seams between modules, and a scanner reads those as noise.\n shapeRendering=\"crispEdges\"\n >\n {modules}\n </svg>\n )\n}\n\n/** Every data path a layout places, in the order it places them. */\nexport function placedPaths(nodes: readonly LayoutNode[], into: string[] = []): string[] {\n for (const node of nodes) {\n // A `qrcode` node is deliberately NOT a placement: it draws a second view of an\n // answer that a field node places elsewhere, so counting it here would report a\n // field as placed when no control for it exists.\n if (node.kind === 'field') into.push(node.path)\n else placedPaths(layoutChildren(node), into)\n }\n return into\n}\n","import { Fragment } from 'react'\nimport type { ReactElement } from 'react'\nimport { parseRichText } from '@formancy/spec'\nimport type { RichBlock, RichInline } from '@formancy/spec'\n\n/**\n * Showing a `richtext` answer.\n *\n * Every element here is created by React from a typed tree. The stored answer\n * never reaches `dangerouslySetInnerHTML`, and there is no sanitiser, because\n * there is nothing to sanitise: the parser in `@formancy/spec` has already\n * turned the characters somebody typed into `{ kind: 'text' }` nodes, and a\n * text node cannot be an element however it is spelled.\n *\n * That is the whole reason the grammar exists rather than storing HTML. A form\n * answer is written by anyone who can reach the form and read later by an\n * administrator, which is the exact shape of a stored cross-site scripting\n * bug ([0052](../../../docs/decisions/0052-richtext-is-not-html.md)).\n *\n * The Angular renderer builds the same tree into the same elements, from the\n * same parser, so the two cannot disagree about what an answer says.\n */\nexport function RichText({ source }: { source: string }): ReactElement | null {\n const blocks = parseRichText(source)\n if (blocks.length === 0) return null\n\n return (\n <div data-formancy-part=\"richtext\">\n {blocks.map((block, index) => (\n <Block key={index} block={block} />\n ))}\n </div>\n )\n}\n\nfunction Block({ block }: { block: RichBlock }): ReactElement {\n if (block.kind === 'paragraph') {\n return (\n <p>\n <Inlines nodes={block.children} />\n </p>\n )\n }\n\n const items = block.items.map((item, index) => (\n <li key={index}>\n <Inlines nodes={item} />\n </li>\n ))\n\n return block.ordered ? <ol>{items}</ol> : <ul>{items}</ul>\n}\n\nfunction Inlines({ nodes }: { nodes: readonly RichInline[] }): ReactElement {\n return (\n <>\n {nodes.map((node, index) => (\n <Fragment key={index}>\n <Inline node={node} />\n </Fragment>\n ))}\n </>\n )\n}\n\nfunction Inline({ node }: { node: RichInline }): ReactElement {\n switch (node.kind) {\n case 'text':\n return <>{node.text}</>\n case 'strong':\n return (\n <strong>\n <Inlines nodes={node.children} />\n </strong>\n )\n case 'emphasis':\n return (\n <em>\n <Inlines nodes={node.children} />\n </em>\n )\n case 'link':\n // `rel` because the destination was written by whoever filled the form\n // in, and the page showing it is usually an administrator's. The parser\n // has already refused anything but http, https and mailto.\n return (\n <a href={node.href} rel=\"noreferrer noopener nofollow ugc\">\n <Inlines nodes={node.children} />\n </a>\n )\n }\n}\n","import { createContext, useContext } from 'react'\nimport type { RichCommand } from '@formancy/spec'\n\n/**\n * A rich-text editing surface, supplied by the host.\n *\n * The same split as the uploader, for the same reason. A contenteditable editor\n * means ProseMirror, and ProseMirror is larger than this entire package — so a\n * form with no rich-text field, which is most of them, must not pay for one.\n * The host passes a factory; without one the field is a textarea with a toolbar,\n * which is a working editor and not a degraded mode\n * ([0061](../../../docs/decisions/0061-tiptap-over-the-closed-grammar.md)).\n *\n * What crosses this boundary is **the stored grammar in both directions, never\n * markup**. That is the whole reason an editor is admissible: nothing on either\n * side of this interface holds a string of HTML, so no consumer of an answer\n * becomes a sanitiser ([0052](../../../docs/decisions/0052-richtext-is-not-html.md)).\n * A factory that returned HTML here would be the bug this design exists to\n * prevent, which is why the type says `string` and the documentation says which\n * string.\n */\nexport interface RichTextEditorHandle {\n /** The stored answer, in the grammar. */\n value: () => string\n /** Replace the content when the form's value changes underneath the editor. */\n setValue: (value: string) => void\n /**\n * Run a formatting command, so the field's own toolbar keeps working.\n *\n * Needed because an editor library brings keyboard shortcuts and no toolbar\n * UI. Leaving the field's toolbar out made Bold reachable by Ctrl+B and by no\n * visible control, which is a regression against the `<textarea>` it replaced\n * and unusable for anybody who does not know the shortcut.\n *\n * The commands are the grammar's whole surface — the same `RichCommand`\n * values the textarea's toolbar uses — so one toolbar drives either surface\n * and the two cannot offer different things.\n */\n run: (command: RichCommand, href?: string) => void\n /** Whether the command is on at the caret, for the toolbar's pressed state. */\n isActive: (command: RichCommand) => boolean\n destroy: () => void\n}\n\nexport interface RichTextEditorMount {\n /** Where to mount. The host's factory owns what it puts inside. */\n readonly element: Element\n /** The stored answer to open with, in the grammar. */\n readonly value: string\n /** Called with the new stored answer, in the grammar. */\n readonly onChange: (value: string) => void\n readonly editable: boolean\n /**\n * Attributes for the editing surface itself, so the ENGINE keeps owning the\n * accessibility wiring.\n *\n * The ids in here are minted by the engine and composed centrally, which is\n * what makes `aria-describedby` correct in both renderers rather than in\n * three separate implementations. An editor package that invented its own\n * labelling would be the fourth implementation, and the one nobody tests.\n */\n readonly attributes: Readonly<Record<string, string>>\n}\n\nexport type RichTextEditorFactory = (mount: RichTextEditorMount) => RichTextEditorHandle\n\nconst RichTextEditorContext = createContext<RichTextEditorFactory | undefined>(undefined)\n\nexport const RichTextEditorProvider = RichTextEditorContext.Provider\n\n/**\n * The host's editor factory, or undefined when there is none.\n *\n * Undefined is a supported state and the default one. Unlike the uploader — where\n * its absence makes a file field read-only, because there is nowhere to put the\n * bytes — its absence here costs nothing but the WYSIWYG surface: the answer is\n * still editable, still valid, and still the same grammar.\n */\nexport function useRichTextEditorFactory(): RichTextEditorFactory | undefined {\n return useContext(RichTextEditorContext)\n}\n","import { createContext, useContext } from 'react'\n\n/**\n * What a scanner is asked to read.\n *\n * The field's resolved label and its data path, and nothing else. A host's camera sheet\n * needs to say what it is looking for, and the path lets a host meter one field's scans.\n *\n * **Nothing about the format is here.** `pattern` is the field's and the engine checks\n * it; a scanner told to pre-filter would be a second validator drifting from the first.\n */\nexport interface ScanRequest {\n /** The field's label, resolved through the message catalogue. */\n label: string\n /** The field's data path, e.g. `serial` or `items[1].serial`. */\n path: string\n}\n\n/**\n * Reads a code and reports the text on it.\n *\n * The camera, the permission prompt, the viewfinder and the decoding all belong to\n * whoever mounted the form ([0071](../../../docs/decisions/0071-a-scanner-is-supplied-not-built.md)).\n *\n * **Resolving with `null` means nobody scanned anything** — the sheet was closed. That\n * is not a failure and the field says nothing about it.\n *\n * **Rejecting means the device did not work**: permission refused, no camera, a stream\n * that died. The field says so in its status region rather than its error region,\n * because a hardware problem is not a wrong answer.\n *\n * What comes back is stored exactly as typing it would be. **A scanner may not\n * pre-validate**: a value the field's `pattern` refuses is still what the camera read,\n * and dropping it would leave the field looking empty with no record of why.\n */\nexport type Scanner = (request: ScanRequest) => Promise<string | null>\n\nconst ScannerContext = createContext<Scanner | undefined>(undefined)\n\nexport const ScannerProvider = ScannerContext.Provider\n\n/**\n * The host's scanner, or undefined when there is none.\n *\n * Undefined is a supported state. A `scanner` widget with no scanner behind it renders\n * the ordinary text input and no button — typing was always the field's primary route,\n * so there is nothing to disable and a Scan button that opened nothing would be worse.\n */\nexport function useScanner(): Scanner | undefined {\n return useContext(ScannerContext)\n}\n","import { createContext, useContext } from 'react'\nimport type { RemoteOption } from '@formancy/spec'\n\n/**\n * What a source is asked for.\n *\n * `kind` is a flat discriminator rather than a union of two interfaces, on the same\n * reasoning the scanner uses: a host written in plain JavaScript must be harmless, and\n * a shape it can read with one `if` is a shape it gets right.\n */\nexport interface OptionsRequest {\n /**\n * `search` — somebody is typing and wants matching rows.\n * `labels` — the form holds these values already and needs their names.\n */\n kind: 'search' | 'labels'\n /** The name the document gave, which the deployment resolves. */\n source: string\n /** The field's data path, e.g. `canton` or `people[1].canton`. */\n path: string\n /** What was typed. Empty on a `labels` request. */\n query: string\n /** The stored values to name. Empty on a `search`. */\n values: readonly string[]\n /**\n * The locale the form is being resolved in — the engine's, which is the host's own\n * `locale` when it passed one and the document's default otherwise.\n *\n * A remote label is a plain string, never a `{$t}` reference: nothing can check a\n * reference that arrives at runtime, and an unchecked one resolves to nothing and\n * shows an opaque identifier. So the source answers in the right language instead.\n */\n locale: string\n /** How many rows the control can show. A hint: the control caps what arrives anyway. */\n limit: number\n /** Aborted when the answer stops being wanted — a newer keystroke, or an unmount. */\n signal: AbortSignal\n}\n\n/**\n * Where one named list of options comes from.\n *\n * The third instance of the inversion the uploader and the scanner already are: the\n * document names a list, the deployment says what that name means, and nothing in\n * formancy ever makes a request of its own. A URL in a form document would be a\n * deployment detail in a portable format, unfixable once published, and an SSRF\n * surface on a self-hosted instance.\n */\nexport interface OptionsSource {\n resolve(request: OptionsRequest): Promise<readonly RemoteOption[]>\n /** How long to wait after a keystroke before asking. The control has a default. */\n debounceMs?: number\n /** Below this many characters, do not ask at all. The control says so on screen. */\n minQueryLength?: number\n /** How many rows to show at once. */\n maxRows?: number\n}\n\n/**\n * Every source this deployment has, by the name a document would use.\n *\n * A MAP rather than one resolver function, and that is the one deliberate difference\n * from `Scanner`. The control has to know **synchronously** whether a name resolves,\n * because absence here is the *file field's* branch and not the scanner's: a text\n * field with no scanner still collects the answer by typing, but a select whose\n * options come only from a source collects nothing at all, so it must say so instead\n * of rendering an empty chooser. With a single resolver, absence would only be\n * discoverable by calling it and failing.\n */\nexport type OptionsSources = Readonly<Record<string, OptionsSource>>\n\nconst OptionsSourcesContext = createContext<OptionsSources | undefined>(undefined)\n\nexport const OptionsSourcesProvider = OptionsSourcesContext.Provider\n\n/**\n * The host's sources, or undefined when there are none.\n *\n * Undefined is a supported state and not a misconfiguration — a form with no sourced\n * field never needs one. What it costs, when a document DOES name a source, is the\n * whole field: it renders a message where the chooser would be, exactly as the file\n * field does without an uploader.\n */\nexport function useOptionsSources(): OptionsSources | undefined {\n return useContext(OptionsSourcesContext)\n}\n","import { useEffect, useRef, useState } from 'react'\nimport { acceptRemoteOptions, capRemoteOptions } from '@formancy/spec'\nimport type { FieldDef, RemoteOption } from '@formancy/spec'\nimport { useFormEngine } from './context.js'\nimport { useOptionsSources } from './options-source.js'\n\n/**\n * A field's options, from the document or from the deployment.\n *\n * One hook for both controls — the plain `<select>` and the `typeahead` — because\n * `optionsSource` decides which answers EXIST rather than how they are shown, so a\n * select that ignored it would render an empty chooser over a field that collects\n * something.\n *\n * `unavailable` is the file field's branch, not the scanner's. A text field with no\n * scanner still collects by typing; a select whose options come only from a source\n * collects nothing at all, so the control says so instead of rendering an empty\n * chooser somebody would stare at.\n *\n * `labels` is not a nicety. With no document options there is no list on mount, so a\n * resumed draft, a wizard page change or a datagrid row move — which remounts every\n * control in the row by design — would render an empty box over a stored answer.\n * Until the names arrive the control shows the raw value, which is what it already\n * does for an authored option whose label is missing.\n *\n * A failure is reported and never thrown: a source being down is not a wrong answer,\n * so it belongs in the field's own status region rather than in the error region that\n * carries the engine's verdict.\n */\n\nexport interface SourcedOptions {\n /** What the control should offer right now. */\n options: ReadonlyArray<{ value: string; label: string }>\n /** Null when the document carries its own options — the ordinary case. */\n remote: RemoteState | null\n}\n\nexport interface RemoteState {\n /** The document names a source this deployment does not have. */\n unavailable: boolean\n /** A request is in flight, and the control says `aria-busy` rather than disabling. */\n busy: boolean\n /** What to announce, or the empty string when there is nothing to say. */\n status: string\n /** How many characters are still needed before anything is asked. */\n needs: number\n}\n\nconst DEBOUNCE_MS = 250\nconst MIN_QUERY = 0\nconst MAX_ROWS = 50\n\nexport function useSourcedOptions(\n field: { def: FieldDef; value: unknown; path: string },\n query: string,\n /**\n * Whether this instance is the one that will render.\n *\n * `SelectField` calls this hook and then hands over to the typeahead when the\n * field carries that widget — and React will not let it skip a hook, because the\n * builder can set a widget on a live document and a changed hook count is an\n * error. So both instances exist and only one asks: measured, the parent's was\n * sending a second request for the empty query that nobody ever read.\n */\n enabled = true,\n): SourcedOptions {\n const engine = useFormEngine()\n const sources = useOptionsSources()\n const name = field.def.optionsSource\n const source = name === undefined ? undefined : sources?.[name]\n\n const [rows, setRows] = useState<readonly RemoteOption[]>([])\n const [named, setNamed] = useState<ReadonlyMap<string, string>>(new Map())\n const [busy, setBusy] = useState(false)\n const [failed, setFailed] = useState(false)\n const [capped, setCapped] = useState<{ shown: number; total: number } | null>(null)\n\n const debounceMs = source?.debounceMs ?? DEBOUNCE_MS\n const minQueryLength = source?.minQueryLength ?? MIN_QUERY\n const maxRows = source?.maxRows ?? MAX_ROWS\n const stored = typeof field.value === 'string' && field.value !== '' ? field.value : undefined\n // The field's DATA PATH, which is what `OptionsRequest.path` promises — `canton`, or\n // `people[1].canton` inside a repeater. It sent `def.key` before, so a resolver could\n // not tell two same-named sourced fields apart and never saw which row it was\n // answering for. The contract said one thing and the code did another.\n const path = field.path\n\n /*\n * The search. Debounced, and superseded by aborting rather than by ignoring: a\n * request nobody wants any more is a request a host should be able to cancel, and\n * the signal is the only way to tell it so.\n */\n const asked = useRef(0)\n useEffect(() => {\n if (!enabled || source === undefined) return\n\n // Bumped BEFORE the early return, not after it. Deleting characters back under the\n // minimum aborts whatever is in flight, and with the generation unchanged that\n // abort arrived at a `.catch` that still believed it was current — so backspacing\n // reported \"The options could not be loaded\" for a source that was working. It\n // also left `busy` set, because the `.finally` that clears it is guarded the same\n // way and the superseding request never started.\n const generation = (asked.current += 1)\n\n if (query.trim().length < minQueryLength) {\n setRows([])\n setCapped(null)\n setBusy(false)\n return\n }\n\n const controller = new AbortController()\n const timer = setTimeout(() => {\n setBusy(true)\n setFailed(false)\n void source\n .resolve({\n kind: 'search',\n source: name ?? '',\n path,\n query,\n values: [],\n locale: engine.locale(),\n limit: maxRows,\n signal: controller.signal,\n })\n .then((answer) => {\n if (generation !== asked.current) return\n // Refused whole rather than filtered: a partial list silently lacks the row\n // somebody came for, and they cannot tell that from a source that has none.\n const accepted = acceptRemoteOptions(answer)\n if (accepted === undefined) {\n setFailed(true)\n setRows([])\n setCapped(null)\n return\n }\n const { shown, total, capped: wasCapped } = capRemoteOptions(accepted, maxRows)\n setRows(shown)\n setCapped(wasCapped ? { shown: shown.length, total } : null)\n })\n .catch(() => {\n if (generation !== asked.current) return\n setFailed(true)\n setRows([])\n })\n .finally(() => {\n if (generation === asked.current) setBusy(false)\n })\n }, debounceMs)\n\n return () => {\n clearTimeout(timer)\n controller.abort()\n }\n }, [enabled, source, name, path, query, minQueryLength, maxRows, debounceMs, engine])\n\n /*\n * The names of what is already stored. Asked at most ONCE per value, and remembered\n * as \"asked\" rather than inferred from the answer.\n *\n * That distinction is the whole of it. The first version asked again unless the\n * answer contained the value — and a source is entitled not to know it: a resumed\n * form may hold a value the list no longer offers, and a host may implement only\n * `kind: 'search'`. The answer still replaced the map, a new map is a new dependency\n * identity, the effect re-ran, and it asked again. Measured: **602 requests in 300\n * milliseconds**, with no \"maximum update depth\" to notice it by, because every turn\n * went through a promise.\n */\n const askedFor = useRef<Set<string>>(new Set())\n useEffect(() => {\n if (!enabled || source === undefined || stored === undefined) return\n if (named.has(stored) || askedFor.current.has(stored)) return\n askedFor.current.add(stored)\n\n const controller = new AbortController()\n void source\n .resolve({\n kind: 'labels',\n source: name ?? '',\n path,\n query: '',\n values: [stored],\n locale: engine.locale(),\n limit: 1,\n signal: controller.signal,\n })\n .then((answer) => {\n const accepted = acceptRemoteOptions(answer)\n if (accepted === undefined) return\n setNamed((was) => {\n const next = new Map(was)\n for (const row of accepted) next.set(row.value, row.label)\n return next\n })\n })\n // A name that does not arrive is not an error anybody can act on: the control\n // shows the raw value, exactly as it does for an authored option with no label.\n .catch(() => undefined)\n\n return () => controller.abort()\n }, [enabled, source, name, path, stored, named, engine])\n\n if (name === undefined) {\n return {\n options: (field.def.options ?? []).map((option) => ({\n value: option.value,\n label: engine.text(option.label) ?? option.value,\n })),\n remote: null,\n }\n }\n\n // The stored answer is always offerable, even when the current query does not match\n // it: a control that dropped it would show an empty box over an answer the form\n // holds, and the next blur would look like the person cleared it.\n const offered = [...rows]\n if (stored !== undefined && !offered.some((row) => row.value === stored)) {\n offered.unshift({ value: stored, label: named.get(stored) ?? stored })\n }\n\n return {\n options: offered,\n remote: {\n unavailable: source === undefined,\n busy,\n needs: minQueryLength,\n status: statusFor({\n failed,\n busy,\n needs: minQueryLength,\n query,\n capped,\n }),\n },\n }\n}\n\n/**\n * The one thing the field says out loud, and never more than one.\n *\n * Success announces nothing: the listbox and `aria-activedescendant` are the\n * feedback, and a region that spoke on every successful keystroke would talk over\n * them. A failure is a sentence somebody can act on rather than a code.\n */\nfunction statusFor({\n failed,\n busy,\n needs,\n query,\n capped,\n}: {\n failed: boolean\n busy: boolean\n needs: number\n query: string\n capped: { shown: number; total: number } | null\n}): string {\n if (failed) return 'The options could not be loaded. Type to try again.'\n if (query.trim().length < needs) {\n return `Type at least ${String(needs)} characters to search.`\n }\n // Set only once a request is actually on its way — after the debounce — so nothing\n // announces per keystroke.\n if (busy) return 'Searching…'\n if (capped !== null) {\n return `Showing the first ${String(capped.shown)} of ${String(capped.total)} — keep typing to narrow.`\n }\n return ''\n}\n","import { createContext, useContext } from 'react'\n\n/**\n * Where a file goes, and what the submission remembers about it.\n *\n * This package has no opinion about the destination: the same field works against local\n * disk, S3 or a customer's own service, and none of them is a dependency of a renderer.\n *\n * **The bytes never pass through the submission.** What is stored is what the file is\n * and where it went, so a submission read back years later is small, readable on its\n * own, and says what was attached even if the object store has since been emptied.\n */\nexport interface StoredFile {\n /** Stable within the submission; how a row is keyed and removed. */\n id: string\n name: string\n size: number\n contentType: string\n /** Where the bytes are, in whatever the host's storage calls a location. */\n storageKey: string\n}\n\n/**\n * Uploads one file and reports what was stored.\n *\n * **Rejecting is a real answer**: the field says so out loud rather than dropping the\n * file, because a submission somebody believes carries their evidence and does not is\n * the worst outcome available here.\n */\nexport type Uploader = (file: File) => Promise<StoredFile>\n\nconst UploaderContext = createContext<Uploader | undefined>(undefined)\n\nexport const UploaderProvider = UploaderContext.Provider\n\n/**\n * The host's uploader, or undefined when there is none.\n *\n * Undefined is a supported state, not a misconfiguration: a form with no file\n * fields needs no uploader, and a file field without one renders read-only and\n * says why. The alternative — throwing — would turn a form that mostly works\n * into a blank page.\n */\nexport function useUploader(): Uploader | undefined {\n return useContext(UploaderContext)\n}\n","import { useEffect, useRef, useState } from 'react'\nimport type { ComponentType, CSSProperties, KeyboardEvent, ReactNode } from 'react'\nimport { parsePath } from '@formancy/core'\nimport { applyRichCommand, datagridColumns, narrowOptionsByLabel } from '@formancy/spec'\nimport type { DataGridColumn, FieldDef, FieldType, RichCommand } from '@formancy/spec'\nimport { LayoutTree, placedPaths } from './layout.js'\nimport { useFormEngine } from './context.js'\nimport { useField } from './use-field.js'\nimport type { FieldBinding } from './use-field.js'\nimport { useRepeater } from './use-repeater.js'\nimport { useSubmit } from './use-submit.js'\nimport { useWizard } from './use-wizard.js'\nimport { RichText } from './rich-text.js'\nimport {\n useRichTextEditorFactory,\n} from './rich-text-editor.js'\nimport type { RichTextEditorFactory, RichTextEditorHandle } from './rich-text-editor.js'\nimport { useScanner } from './scanning.js'\nimport { useSourcedOptions } from './use-sourced-options.js'\nimport { useUploader } from './uploads.js'\nimport type { StoredFile } from './uploads.js'\n\n/**\n * The component registry — theming mechanism number two. The schema decides\n * WHAT a field is; the registry decides what RENDERS it, with per-path entries\n * beating per-type entries beating the built-in defaults. A design system\n * replaces the entire visual layer by handing in a registry, without forking\n * anything.\n */\nexport interface FieldComponentProps {\n path: string\n label: string\n}\n\nexport type FieldComponent = ComponentType<FieldComponentProps>\n\nexport interface Registry {\n byType?: Partial<Record<FieldType, FieldComponent>>\n byPath?: Record<string, FieldComponent>\n}\n\nexport interface SubmitOutcome {\n ok: boolean\n errors: Record<string, string[]>\n /** The canonical value, present when accepted. */\n data?: unknown\n}\n\nexport interface FormancyFormProps {\n /**\n * Display text per wire path (row fields by their template wire,\n * `items[].name`). A `label` on the model definition wins — that is how\n * fixture schemas carry text until the spec's i18n section lands; a missing\n * entry falls back to the wire path, which is at least honest.\n */\n labels?: Record<string, string>\n registry?: Registry\n /**\n * Render a named entry from the schema's `layouts` instead of model order —\n * `\"web\"`, `\"print\"`, whatever the document defines. Unknown or absent, the\n * form falls back to model order, because a mistyped layout name should not\n * produce an empty form.\n */\n layout?: string\n submitLabel?: string\n onSubmit?: (outcome: SubmitOutcome) => void\n}\n\n/**\n * Renders the whole form from the engine: one slot per field, resolved through\n * the registry. Slots subscribe individually, so a keystroke re-renders one\n * field and a visibility flip mounts or unmounts exactly the fields it hit.\n * A paged schema renders a stepper, one page at a time, navigation, and the\n * submit control on the last page — a failed submit navigates to the first\n * page with a problem instead of leaving the user on a clean review page\n * staring at a rejection.\n */\nexport function FormancyForm(props: FormancyFormProps) {\n const engine = useFormEngine()\n return engine.wizard() === undefined ? <FlatForm {...props} /> : <PagedForm {...props} />\n}\n\nfunction SubmitButton({ submitLabel, onSubmit, onFailedNavigate }: FormancyFormProps & { onFailedNavigate?: (page: number) => void }) {\n const engine = useFormEngine()\n const submit = useSubmit()\n return (\n <button\n type=\"button\"\n data-formancy-part=\"submit\"\n onClick={() => {\n const outcome = submit()\n if (!outcome.ok && onFailedNavigate !== undefined) {\n const firstInvalid = engine.firstInvalid()\n if (firstInvalid !== null) onFailedNavigate(engine.pageOf(parsePath(firstInvalid)))\n }\n onSubmit?.(\n outcome.ok\n ? { ok: true, errors: outcome.errors, data: engine.value() }\n : { ok: false, errors: outcome.errors },\n )\n }}\n >\n {submitLabel ?? 'Submit'}\n </button>\n )\n}\n\nfunction FlatForm(props: FormancyFormProps) {\n return (\n <>\n <FieldList {...props} />\n <SubmitButton {...props} />\n </>\n )\n}\n\nfunction PagedForm(props: FormancyFormProps) {\n const engine = useFormEngine()\n const wizard = useWizard()\n const pages = engine.pages()\n const lastPage = wizard.pageCount - 1\n\n return (\n <>\n <nav data-formancy-part=\"stepper\" aria-label=\"Progress\">\n <ol>\n {pages.map((page, index) => (\n <li key={page.key} aria-current={index === wizard.page ? 'step' : undefined}>\n {engine.text(page.def.label) ?? page.key}\n </li>\n ))}\n </ol>\n </nav>\n <FieldList {...props} page={wizard.page} />\n <div data-formancy-part=\"wizard-nav\">\n {wizard.page > 0 ? (\n <button type=\"button\" onClick={() => wizard.back()}>\n Back\n </button>\n ) : null}\n {wizard.page < lastPage ? (\n <button type=\"button\" onClick={() => void wizard.next()}>\n Next\n </button>\n ) : (\n <SubmitButton {...props} onFailedNavigate={(page) => wizard.goTo(page)} />\n )}\n </div>\n </>\n )\n}\n\nfunction FieldList({ labels, registry, page, layout }: FormancyFormProps & { page?: number }) {\n const engine = useFormEngine()\n const repeaterWires = engine.repeaterPaths()\n const schema = engine.schema()\n\n const arrangement = schema.layouts?.find((candidate) => candidate.name === layout)\n if (layout !== undefined && arrangement !== undefined) {\n const placed = new Set(placedPaths(arrangement.nodes))\n\n return (\n <LayoutTree\n schema={schema}\n nodes={arrangement.nodes}\n locale={schema.i18n?.defaultLocale ?? ''}\n renderField={(path) => {\n if (repeaterWires.includes(path)) {\n return <RepeaterSection key={path} wire={path} labels={labels} registry={registry} />\n }\n return <FieldSlot key={path} path={path} fallbackLabel={labels?.[path]} registry={registry} />\n }}\n />\n )\n // A field the layout leaves out is not rendered. That is deliberate — a\n // print layout without the consent checkbox is doing its job — and it is\n // why `unreferencedPaths` exists in the spec for a builder to warn with.\n void placed\n }\n\n const inPage = (wire: string): boolean =>\n page === undefined || engine.pageOf(parsePath(wire)) === page\n\n // Row fields render inside their repeater's own section, never in the flat\n // list — a row needs its remove button and its position context.\n const staticWires = engine\n .fieldPaths()\n .filter((wire) => !repeaterWires.some((repeater) => wire.startsWith(`${repeater}[`)))\n\n return (\n <>\n {staticWires.filter(inPage).map((wire) => (\n <FieldSlot key={wire} path={wire} fallbackLabel={labels?.[wire]} registry={registry} />\n ))}\n {repeaterWires.filter(inPage).map((wire) => (\n <RepeaterSection key={wire} wire={wire} labels={labels} registry={registry} />\n ))}\n </>\n )\n}\n\nfunction FieldSlot({\n path,\n fallbackLabel,\n registry,\n}: {\n path: string\n fallbackLabel?: string | undefined\n registry?: Registry | undefined\n}) {\n const field = useField(path)\n\n // A hidden field leaves the DOM entirely: display:none would still ship the\n // markup, keep it in the accessibility tree's shadow, and leak its labels\n // to screen-reader \"read all\" passes.\n if (!field.visible) return null\n\n const Component =\n registry?.byPath?.[path] ?? registry?.byType?.[field.type] ?? DEFAULT_COMPONENTS[field.type]\n if (Component === null) return null\n\n const label = field.label ?? fallbackLabel ?? path\n return <Component path={path} label={label} />\n}\n\nfunction RepeaterSection({\n wire,\n labels,\n registry,\n}: {\n wire: string\n labels?: Record<string, string> | undefined\n registry?: Registry | undefined\n}) {\n const engine = useFormEngine()\n const repeater = useRepeater(wire)\n const def = engine.repeaters().find((candidate) => candidate.wire === wire)?.def\n const label = engine.text(def?.label) ?? labels?.[wire] ?? wire\n const addLabel = def?.addLabel ?? `Add ${label}`\n const removeLabel = def?.removeLabel ?? `Remove ${label}`\n\n const fallbackFor = (instanceWire: string): string | undefined => {\n const template = instanceWire.replace(/\\[\\d+\\]/, '[]')\n return labels?.[template] ?? labels?.[instanceWire]\n }\n\n // The grid, computed once per render and empty unless the widget asked for one.\n // `columns` without the widget stays inert on purpose: 0066 lets an author write the\n // arrangement before a renderer honours it, and a repeater that silently became a grid\n // because somebody sized its columns would be the opposite of that.\n const grid = def?.widget === 'datagrid'\n const declaredColumns = def?.columns ?? []\n const plan = grid ? datagridColumns(def, declaredColumns) : []\n\n // The ratios, as ONE custom property on the container rather than as\n // `grid-template-columns`. A property lays nothing out by itself, so a theme's\n // narrow-screen media query replaces its own declaration and wins rather than losing to\n // an inline one it cannot outrank. It also carries a value space no attribute can\n // enumerate: `width` is a `number` with `exclusiveMinimum: 0`, so 1.5 is legal and\n // nothing bounds it from above.\n //\n // Emitted only when an author sized something, so the theme's fallback is live code\n // rather than a branch nothing takes.\n const tracks = declaredColumns.some((column) => column.width !== undefined)\n ? plan\n .map(({ column }) => (column?.width === undefined ? '1fr' : `${String(column.width)}fr`))\n .join(' ')\n : undefined\n\n /** The control in one cell: the leaf the column's child field collects into.\n *\n * One leaf, because a grid's rows are FLAT -- a child holding fields of its own is\n * refused when the document is saved\n * ([0078](../../../docs/decisions/0078-a-grid-row-is-flat.md)). This walked the whole\n * subtree under the child while a group could be a column, and every clause that made\n * that walk safe is gone with the arrangement it served.\n *\n * Still a filter over the paths that EXIST rather than the path the column implies, so\n * a document nobody validated renders an empty cell instead of asking the engine about\n * a field it does not have. */\n const leavesOf = (index: number, key: string): string[] => {\n const wanted = `${wire}[${String(index)}].${key}`\n return engine.fieldPaths().filter((leaf) => leaf === wanted)\n }\n\n /** A column's visible heading: the author's shortening, else the child's own label,\n * else the labels prop, else the key.\n *\n * Visible text and NOTHING else -- never an `id` target, never an `aria-label`.\n * Measured against the accessible-name implementation this repository installs: a\n * column heading contributes nothing to the name of a control in its column, whether\n * it is a `<th scope=\"col\">` or a `headers=` target. So the heading cannot replace a\n * cell's label, and pointing a control at it would only ADD the heading to the name --\n * which is the failure 0066 separates `header` from `label` to prevent. */\n const headingOf = (entry: (typeof plan)[number]): string =>\n engine.text(entry.column?.header) ??\n engine.text(entry.child?.label) ??\n fallbackFor(`${wire}[0].${entry.key}`) ??\n entry.key\n\n /** A row's own controls, identical in both arrangements down to the names, because\n * 0068 put the row's position in those names and a grid does not change where a person\n * is.\n *\n * The text sits in its own element so a THEME can clip it and draw a mark instead,\n * which is what a grid wants -- \"Remove recipient 1 of 1\" on three wrapped lines took\n * more room than the answers beside it, measured in the playground. Clipped and never\n * removed: `display: none` and `visibility: hidden` both compute the button's name to\n * the empty string, and a button called nothing is worse than a wide one.\n *\n * The renderer draws no mark of its own. An icon is appearance, appearance belongs to\n * the consumer ([0004](../../../docs/decisions/0004-headless-core.md)), and a renderer\n * that shipped a glyph would be choosing one for every design system at once. */\n const rowButtons = (index: number): ReactNode => (\n <>\n {/* Position context in the NAME, so a screen-reader user knows which\n row this button kills without walking the tree. */}\n <button type=\"button\" data-formancy-part=\"row-remove\" onClick={() => repeater.removeRow(index)}>\n <span data-formancy-part=\"row-action-text\">\n {`${removeLabel} ${index + 1} of ${repeater.rowCount}`}\n </span>\n </button>\n {/* Reordering by button, which is the KEYBOARD route and therefore the\n primary one: WCAG 2.5.7 requires a non-drag equivalent for any drag, so\n a drag affordance can only ever be a second route to these.\n\n Position is in the name here too, and the name says where the row goes\n rather than which direction it travels.\n\n Absent at the ends rather than disabled. A disabled button is still in\n the tab order in some browsers and announces a control that does\n nothing; a row that cannot move up simply has no such button. */}\n {index > 0 ? (\n <button type=\"button\" data-formancy-part=\"row-up\" onClick={() => repeater.moveRow(index, index - 1)}>\n <span data-formancy-part=\"row-action-text\">\n {`Move ${label} ${index + 1} of ${repeater.rowCount} up`}\n </span>\n </button>\n ) : null}\n {index < repeater.rowCount - 1 ? (\n <button type=\"button\" data-formancy-part=\"row-down\" onClick={() => repeater.moveRow(index, index + 1)}>\n <span data-formancy-part=\"row-action-text\">\n {`Move ${label} ${index + 1} of ${repeater.rowCount} down`}\n </span>\n </button>\n ) : null}\n </>\n )\n\n /** One control in a row.\n *\n * Keyed by the POSITIONAL WIRE, so every control in a row is remounted when the row\n * moves and focus is lost on a reorder. Keying by the field's key within the row fixes\n * that here, and was reverted to keep the two renderers identical: the same change in\n * Angular lets it REUSE a component whose path is read once in ngOnInit, so after a\n * removal it binds to the old wire and shows the wrong row's answer -- caught by a\n * conformance fixture. One renderer keeping focus and the other not is the\n * framework-specific divergence this architecture exists to prevent, so both wait for\n * reactive path binding in Angular. */\n const slot = (instanceWire: string): ReactNode => (\n <FieldSlot\n key={instanceWire}\n path={instanceWire}\n fallbackLabel={fallbackFor(instanceWire)}\n registry={registry}\n />\n )\n\n const rows = repeater.rowIds.map((rowId, index) =>\n grid ? (\n // Keyed by identity, not position: removing a row renumbers every row after it, and\n // an index key would make React reuse the wrong DOM nodes.\n <div data-formancy-part=\"datagrid-row\" key={rowId}>\n {plan.map((entry) => (\n // Always emitted, even when every field in it renders nothing. A rule that\n // hides one answer must not shift that row's remaining columns out of line\n // with the heading strip and with every other row -- which is the whole\n // reason this arrangement exists.\n <div\n data-formancy-part=\"datagrid-cell\"\n key={entry.key}\n {...(entry.column?.align === undefined ? {} : { 'data-align': entry.column.align })}\n >\n {leavesOf(index, entry.key).map(slot)}\n </div>\n ))}\n {/* One cell for all of a row's buttons, because there are two on the first and\n last rows and three in between. A track whose cell count varied per row is\n exactly what a table cannot express without a cell that announces a blank. */}\n <div data-formancy-part=\"datagrid-actions\">{rowButtons(index)}</div>\n </div>\n ) : (\n <div data-formancy-part=\"row\" key={rowId}>\n {engine\n .fieldPaths()\n .filter((candidate) => candidate.startsWith(`${wire}[${String(index)}]`))\n .map(slot)}\n {rowButtons(index)}\n </div>\n ),\n )\n\n return (\n <fieldset data-formancy-part=\"repeater\">\n <legend data-formancy-part=\"repeater-legend\">{label}</legend>\n {grid ? (\n // A container inside the fieldset rather than the fieldset itself, so the legend\n // and the Add button do not become grid items.\n //\n // No role anywhere in here, and that is the decision rather than an omission.\n // `role=\"grid\"` would take the arrow keys, which the controls in the cells\n // already own -- a `select` with `widget: \"typeahead\"` is legal in a row and\n // claims Up, Down, Home, End, Enter and Escape -- and it would replace twenty tab\n // stops with one. A role is not paint, which is the line 0065 draws.\n <div\n data-formancy-part=\"datagrid\"\n data-columns={String(plan.length)}\n {...(tracks === undefined ? {} : { style: { '--fm-datagrid-columns': tracks } as CSSProperties })}\n >\n {/* Only once there is a row to head, and plain static text. A heading here\n names nothing, so a theme may delete it at phone width without changing what\n any control announces -- which is the property that makes the narrow-screen\n reflow possible at all. */}\n {repeater.rowCount > 0 ? (\n <div data-formancy-part=\"datagrid-head\">\n {plan.map((entry) => (\n <span\n data-formancy-part=\"datagrid-heading\"\n key={entry.key}\n {...(entry.column?.align === undefined\n ? {}\n : { 'data-align': entry.column.align })}\n >\n {headingOf(entry)}\n </span>\n ))}\n </div>\n ) : null}\n {rows}\n </div>\n ) : (\n rows\n )}\n <button type=\"button\" onClick={() => repeater.addRow()}>\n {addLabel}\n </button>\n </fieldset>\n )\n}\n\n/** Shared unstyled shell: real label, control, error text as the describedby\n * target. Zero CSS; `data-formancy-part` is the styling hook. */\nfunction FieldShell({\n path,\n field,\n label,\n children,\n}: {\n path: string\n field: FieldBinding\n label: string\n children: ReactNode\n}) {\n return (\n <div\n data-formancy-part=\"field\"\n // Inert here, and read by tools outside the renderer — the builder's\n // arrange surface needs to know which field an element on screen is.\n data-formancy-field-path={path}\n data-state={field.touched && field.errors.length > 0 ? 'invalid' : 'valid'}\n >\n <label data-formancy-part=\"label\" {...field.labelProps}>\n {label}\n </label>\n {children}\n {field.touched && field.errors.length > 0 ? (\n <p data-formancy-part=\"error\" {...field.errorProps}>\n {field.errors.join(', ')}\n </p>\n ) : null}\n </div>\n )\n}\n\n/**\n * A single-line answer, and — with `widget: \"scanner\"` — a camera route to the same\n * string.\n *\n * The input is the control in both cases, never a second one beside it: typing is\n * the accessibility floor and the fallback at once, so it is what is always there\n * and the scan button is what is sometimes added. With no scanner supplied the\n * markup is the default control exactly, because a Scan button that opens nothing is\n * worse than no button ([0071](../../../docs/decisions/0071-a-scanner-is-supplied-not-built.md)).\n */\nfunction TextField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n const scan = useScanner()\n const [scanning, setScanning] = useState(false)\n /** A device failure, held here rather than in the field's errors. See below. */\n const [trouble, setTrouble] = useState<string | undefined>(undefined)\n\n /**\n * The ONE place a text field's answer is written, typed or scanned.\n *\n * Structural rather than careful: the parameter is a `string`, so there is no path\n * from the camera to `setValue` that could store something typing could not. That\n * is the line a widget may never cross — it changes how a field looks, never what\n * it collects ([0065](../../../docs/decisions/0065-a-widget-is-authored-not-registered.md)).\n */\n const commit = (text: string): void =>\n // `<input type=\"text\">` runs the platform's value sanitiser on everything that reaches\n // it -- \"strip newlines from the value\", in HTML's value sanitization algorithm -- so a\n // typed or pasted answer can never hold CR or LF. A scanned code CAN: a Wi-Fi or vCard\n // payload is several lines. Applying the control's own rule here is what makes a scanned\n // answer byte-identical to a typed one rather than merely similar, which is the line a\n // widget may not cross\n // ([0065](../../../docs/decisions/0065-a-widget-is-authored-not-registered.md)).\n //\n // Not a second validator -- `pattern` stays the engine's -- and not taste: jsdom does\n // not implement the sanitiser either, so nothing here notices unless it is asserted.\n field.setValue(text.replace(/[\\r\\n]/g, ''))\n\n const read = async (): Promise<void> => {\n // Re-entrancy, which `disabled` used to prevent: a second press while a scan is in\n // flight is ignored rather than opening a second camera session.\n if (scan === undefined || scanning) return\n setScanning(true)\n setTrouble(undefined)\n try {\n const text = await scan({ label, path })\n // Nobody scanned anything: the sheet was closed, or they changed their mind.\n // Not a failure, and an apology in a live region for a decision somebody made\n // on purpose is noise.\n if (text === null) return\n if (typeof text !== 'string') {\n // A host written in plain JavaScript can resolve with anything. Reported as\n // the device failure it is, rather than stored — an object in a text field\n // is exactly what `commit` exists to make impossible.\n setTrouble(\n 'Scanning did not work: the scanner did not return text. Type the value instead.',\n )\n return\n }\n // Stored as typed, THEN touched — so a value the field's `pattern` refuses\n // shows the engine's own error rather than being dropped. Dropping it would\n // discard the only record of what the camera read and leave the field looking\n // empty, which is the worse of the two failures by some distance.\n commit(text)\n field.touch()\n } catch (error) {\n setTrouble(\n `Scanning did not work: ${\n error instanceof Error ? error.message : String(error)\n }. Type the value instead.`,\n )\n } finally {\n setScanning(false)\n }\n }\n\n return (\n <FieldShell path={path} field={field} label={label}>\n <input\n type=\"text\"\n {...field.controlProps}\n value={typeof field.value === 'string' ? field.value : ''}\n onChange={(event) => commit(event.target.value)}\n onBlur={() => field.touch()}\n />\n {field.def.widget === 'scanner' && scan !== undefined ? (\n <>\n <button\n type=\"button\"\n data-formancy-part=\"scanner-button\"\n // Disabled only when the FIELD is. Disabling the button somebody just pressed\n // blurs it, and the browser then resets focus to `<body>` — so a keyboard user\n // who presses Scan is returned to the top of the document, and the status line\n // tells them to type the value instead into a field they now have to find again.\n //\n // Busy is said with `aria-busy`, which changes nothing about focus, and the guard\n // in `read` does what `disabled` was doing. Not testable here: jsdom does not\n // blur a focused element that becomes disabled, which is exactly why this was\n // written the other way round first.\n disabled={field.disabled}\n aria-busy={scanning ? true : undefined}\n onClick={() => {\n void read()\n }}\n >\n {/* The word is the visible label and the field's name completes the\n accessible one, so three scannable fields on a page do not offer\n three buttons called \"Scan\" — and the visible text is still\n contained in the accessible name (WCAG 2.5.3). */}\n Scan <span data-formancy-part=\"visually-hidden\">{label}</span>\n </button>\n {/* The camera's own progress and its failures, in this field's polite\n region. NOT the error region: that one is the control's describedby\n target, it holds the engine's verdicts, and a refused permission put\n there would describe a hardware problem as a wrong answer. Same shape\n as the file field's status line, for the same reason. */}\n <p role=\"status\" data-formancy-part=\"scanner-status\">\n {scanning ? 'Scanning…' : (trouble ?? '')}\n </p>\n </>\n ) : null}\n </FieldShell>\n )\n}\n\nfunction TextareaField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n return (\n <FieldShell path={path} field={field} label={label}>\n <textarea\n {...field.controlProps}\n value={typeof field.value === 'string' ? field.value : ''}\n onChange={(event) => field.setValue(event.target.value)}\n onBlur={() => field.touch()}\n />\n </FieldShell>\n )\n}\n\nfunction NumberField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n return (\n <FieldShell path={path} field={field} label={label}>\n <input\n type=\"number\"\n {...field.controlProps}\n value={typeof field.value === 'number' ? field.value : ''}\n onChange={(event) =>\n field.setValue(event.target.value === '' ? null : event.target.valueAsNumber)\n }\n onBlur={() => field.touch()}\n />\n </FieldShell>\n )\n}\n\nfunction CheckboxField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n return (\n <FieldShell path={path} field={field} label={label}>\n <input\n type=\"checkbox\"\n {...field.controlProps}\n /* `widget: \"toggle\"` is a part name and NOT `role=\"switch\"`. ARIA's switch\n means a control that takes effect when you operate it, and a form field\n sets a value submitted later or never — so announcing \"switch\" describes\n it incorrectly to the people who rely on the description. A role is also\n not paint: changing it would make this the first widget to change what a\n control claims to be, which is the line the widget mechanism exists to\n hold. The switch is CSS, and conformance keeps finding this by role\n `checkbox` either way. */\n {...(field.def.widget === 'toggle' ? { 'data-formancy-part': 'toggle' } : {})}\n checked={field.value === true}\n onChange={(event) => field.setValue(event.target.checked)}\n onBlur={() => field.touch()}\n />\n </FieldShell>\n )\n}\n\nfunction DateField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n return (\n <FieldShell path={path} field={field} label={label}>\n <input\n type=\"date\"\n {...field.controlProps}\n value={typeof field.value === 'string' ? field.value : ''}\n onChange={(event) => field.setValue(event.target.value === '' ? null : event.target.value)}\n onBlur={() => field.touch()}\n />\n </FieldShell>\n )\n}\n\n/**\n * A time of day, as `HH:MM`.\n *\n * `<input type=\"time\">` gives the platform's own picker, its own keyboard handling\n * and its own locale display — a 12-hour clock where the reader expects one — while\n * its `value` is always `HH:MM` on a 24-hour clock. That is exactly the split the\n * format wants: the reader sees their convention, the answer records one canonical\n * shape.\n *\n * `step` is not set, so the browser offers no seconds. A time answer has none, and a\n * control offering a precision the format discards is a control that loses what\n * somebody typed.\n */\nfunction TimeField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n return (\n <FieldShell path={path} field={field} label={label}>\n <input\n type=\"time\"\n {...field.controlProps}\n /* Bounds are handed to the browser as well as checked by the engine. The\n engine's check is the truth — it runs again on the server — and this is\n what lets the platform grey out what it will not accept, which is a better\n experience than a message after the fact. */\n {...(typeof field.def.earliest === 'string' ? { min: field.def.earliest } : {})}\n {...(typeof field.def.latest === 'string' ? { max: field.def.latest } : {})}\n value={typeof field.value === 'string' ? field.value : ''}\n onChange={(event) => field.setValue(event.target.value === '' ? null : event.target.value)}\n onBlur={() => field.touch()}\n />\n </FieldShell>\n )\n}\n\n/**\n * An instant, stored as `YYYY-MM-DDTHH:MM:SSZ`.\n *\n * `<input type=\"datetime-local\">` because there is no zoned datetime input in any\n * browser — so the control shows the reader a local wall clock and this converts.\n * The conversion is the whole reason this component exists rather than reusing\n * `DateField`:\n *\n * - **In:** the control gives `YYYY-MM-DDTHH:MM` in the reader's own zone. `new\n * Date(local).toISOString()` interprets a zoneless string as local time, which is\n * what is wanted here and is exactly what `bindTimestamp` refuses for a stored\n * value — the difference is that the reader's zone is known at the moment they\n * type, and is not knowable later.\n * - **Out:** the stored instant is rendered back into local time for the control,\n * sliced to minutes because the input rejects a seconds component it was not asked\n * for.\n *\n * Seconds are therefore always `00` in an answer a person typed. The format keeps\n * them because a machine-supplied answer has them and a fixed width is what makes\n * ordering work.\n */\nfunction DateTimeField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n const stored = typeof field.value === 'string' ? field.value : ''\n const asLocal = (): string => {\n if (stored === '') return ''\n const instant = new Date(stored)\n if (Number.isNaN(instant.getTime())) return ''\n // Local parts, not `toISOString()`: that would show UTC in a control the\n // browser labels as local, so the reader would see an hour they did not type.\n const pad = (part: number): string => String(part).padStart(2, '0')\n return (\n `${String(instant.getFullYear())}-${pad(instant.getMonth() + 1)}-${pad(instant.getDate())}` +\n `T${pad(instant.getHours())}:${pad(instant.getMinutes())}`\n )\n }\n\n return (\n <FieldShell path={path} field={field} label={label}>\n <input\n type=\"datetime-local\"\n {...field.controlProps}\n value={asLocal()}\n onChange={(event) => {\n const local = event.target.value\n if (local === '') {\n field.setValue(null)\n return\n }\n const instant = new Date(local)\n // A control can hand back something unparseable mid-edit. Writing null\n // rather than a malformed string keeps the stored answer always either\n // empty or canonical, which is what the engine's shape check assumes.\n field.setValue(\n Number.isNaN(instant.getTime())\n ? null\n : `${instant.toISOString().slice(0, 19)}Z`,\n )\n }}\n onBlur={() => field.touch()}\n />\n </FieldShell>\n )\n}\n\n/**\n * Option labels resolved to strings, since a label may be a message reference.\n * Falling back to the stored value keeps an untranslated option selectable\n * rather than blank.\n */\nfunction useResolvedOptions(field: { def: FieldDef }): Array<{ value: string; label: string }> {\n const engine = useFormEngine()\n return (field.def.options ?? []).map((option) => ({\n value: option.value,\n label: engine.text(option.label) ?? option.value,\n }))\n}\n\nfunction SelectField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n // The query the plain control searches with is the empty one: a native select has\n // nothing to type into, so it offers whatever the source returns for \"everything\",\n // capped. A source with more rows than that is a source whose field wants the\n // typeahead widget, and the status region says how many were left out.\n // One hook, unconditionally: it hands back the document's own options when the\n // field names no source, so there is no branch above a hook to reorder React's\n // list the moment the builder sets one on a live document.\n const sourced = useSourcedOptions({ ...field, path }, '', field.def.widget !== 'typeahead')\n const options = sourced.options\n // Every hook runs before the branch on purpose: the builder can set a widget on\n // a live document, and a branch above a hook would reorder React's hook list\n // the moment it did.\n\n // The document names a source this deployment does not have. Unlike a missing\n // scanner this costs the whole field -- a select with no options collects nothing\n // -- so it says so where the chooser would be, exactly as the file field does.\n //\n // BEFORE the widget, and the order is the fix: dispatching to the typeahead first\n // made this message unreachable for the very widget the feature was built for. What\n // somebody got instead was a working-looking combobox that returned nothing and\n // announced \"No options match\" -- which says the list has no such row, when the\n // truth is that there is no list.\n if (sourced.remote?.unavailable === true) {\n return (\n <FieldShell path={path} field={field} label={label}>\n <p data-formancy-part=\"options-unavailable\">\n {`This field's answers come from \"${field.def.optionsSource ?? ''}\", which this application has not provided.`}\n </p>\n </FieldShell>\n )\n }\n\n if (field.def.widget === 'typeahead') {\n return <TypeaheadSelectField path={path} label={label} field={field} />\n }\n\n return (\n <FieldShell path={path} field={field} label={label}>\n {/* Setting `value` on the select works only because React applies it\n AFTER the option children render; a select's value property is\n settable once its options exist. Angular binds [selected] per option\n for the same reason — the explicit form of the same contract. */}\n <select\n {...field.controlProps}\n value={typeof field.value === 'string' ? field.value : ''}\n onChange={(event) => field.setValue(event.target.value === '' ? null : event.target.value)}\n onBlur={() => field.touch()}\n >\n {/* The empty option is the unanswered state; without it the browser\n silently pre-selects the first real option, which the engine never\n heard about. */}\n <option value=\"\" />\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n {/* Only a sourced select has anything to say: how many rows were left out, or\n that the source could not be reached. Never the error region, which carries\n the engine's verdict — a source being down is not a wrong answer. */}\n {sourced.remote === null ? null : (\n <p role=\"status\" data-formancy-part=\"select-status\">\n {sourced.remote.status}\n </p>\n )}\n </FieldShell>\n )\n}\n\n/**\n * `widget: \"typeahead\"` - the same select, narrowed by typing.\n *\n * An ARIA 1.2 editable combobox over a listbox popup: a text box with\n * `role=\"combobox\"` and a `<ul role=\"listbox\">` of `<li role=\"option\">`. DOM focus\n * never leaves the text box, so the arrowed-over option is named by\n * `aria-activedescendant` - which is the only thing that says where somebody is\n * when what they are moving through does not hold focus.\n *\n * **The list element exists while the popup is collapsed.** `aria-expanded` and\n * `aria-controls` are required properties of the role, and an `aria-controls`\n * pointing at an element that is not there is an unresolvable IDREF - so the\n * listbox is rendered and `hidden` rather than mounted when it opens.\n *\n * `aria-autocomplete=\"list\"`, never `\"both\"`: nothing is ever written into the box\n * on the person's behalf, and `\"both\"` announces an inline completion that does\n * not exist. No `aria-haspopup`: `listbox` is the role's implicit popup, so the\n * attribute either repeats it or lies.\n *\n * `aria-selected` is on the CHOSEN option and on nothing else. Following the arrow\n * keys with it - the commonest defect in this pattern - tells a screen reader the\n * answer changed every time somebody pressed Down to read the next row.\n *\n * **It cannot store what somebody typed.** `setValue` is reached from exactly two\n * places here, with an option's own value or with `null`, and the text goes nowhere\n * but the filter. That is what makes\n * [0065](../../../docs/decisions/0065-a-widget-is-authored-not-registered.md)\n * structural rather than remembered: there is no path from this input to a stored\n * string a plain select could not have produced. So leaving the field with a query\n * that matches nothing stores nothing, and the box goes back to showing the answer.\n *\n * `null` is the other half of that: a select's empty first option means\n * un-answering is always available, and a widget may not take it away. Clearing the\n * box and leaving clears the answer.\n */\n/**\n * What the status region is saying, as a word a theme can select on.\n *\n * Not the text: a theme that keyed off English prose would break in every other\n * language the form is offered in.\n */\nfunction statusState({\n sourced,\n open,\n matches,\n}: {\n sourced: { remote: { busy: boolean; status: string } | null }\n open: boolean\n matches: number\n}): string | undefined {\n if (sourced.remote === null) return open && matches === 0 ? 'empty' : undefined\n if (sourced.remote.busy) return 'busy'\n if (sourced.remote.status.startsWith('The options could not')) return 'failed'\n if (sourced.remote.status !== '') return 'hint'\n return open && matches === 0 ? 'empty' : undefined\n}\n\nfunction TypeaheadSelectField({\n path,\n label,\n field,\n}: FieldComponentProps & {\n field: FieldBinding\n}) {\n /**\n * What is in the box while somebody types, or null when the box is simply\n * showing the answer.\n *\n * Two states rather than one string, because \"empty because they cleared it\"\n * and \"empty because there is no answer\" are different facts, and only the\n * first clears the answer on the way out.\n */\n const [query, setQuery] = useState<string | null>(null)\n const [open, setOpen] = useState(false)\n /** The arrowed-over option by VALUE, not by index: the filtered list changes on\n * every keystroke and an index would point at a different row after one. */\n const [activeValue, setActiveValue] = useState<string | null>(null)\n\n // The options, from the document or from the deployment. The query goes in so a\n // source is asked what somebody is looking for rather than for everything.\n const sourced = useSourcedOptions({ ...field, path }, query ?? '')\n const options = sourced.options\n\n const chosen = options.find((option) => option.value === field.value)\n // A source is the authority on what matches: it was handed the query, and\n // re-folding its rows here would drop ones it matched on data the person cannot\n // see. A host that wants fetch-once-filter-locally composes `narrowOptionsByLabel`\n // in its own resolver, which is why that function lives in `@formancy/spec`.\n const matches =\n sourced.remote === null ? narrowOptionsByLabel(options, query ?? '') : options\n /** Collapsed whenever there is nothing on the screen, so `aria-expanded` never\n * claims a popup a person cannot see. */\n const expanded = open && matches.length > 0\n const activeIndex = matches.findIndex((option) => option.value === activeValue)\n const activeId =\n expanded && activeIndex !== -1 ? optionDomId(field, matches[activeIndex]!.value) : undefined\n const listboxId = `${field.ids.control}:listbox`\n\n const choose = (value: string): void => {\n field.setValue(value)\n setQuery(null)\n setOpen(false)\n setActiveValue(null)\n }\n\n const moveActive = (delta: number): void => {\n if (matches.length === 0) return\n const from = activeIndex === -1 ? (delta > 0 ? -1 : matches.length) : activeIndex\n // Clamped, not wrapped: Down means further down the list, and a list that\n // jumps back to the top moves somebody past the end without saying so.\n const next = Math.min(Math.max(from + delta, 0), matches.length - 1)\n setActiveValue(matches[next]!.value)\n }\n\n const onKeyDown = (event: KeyboardEvent<HTMLInputElement>): void => {\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault()\n if (!open) {\n setOpen(true)\n // Opens ON the answer when there is one, so Down then Enter cannot\n // quietly change an answer somebody only wanted to look at.\n const fallback = event.key === 'ArrowDown' ? matches[0] : matches[matches.length - 1]\n setActiveValue(chosen?.value ?? fallback?.value ?? null)\n return\n }\n moveActive(event.key === 'ArrowDown' ? 1 : -1)\n return\n }\n if ((event.key === 'Home' || event.key === 'End') && expanded) {\n // The popup's keys while it is open. Left to the caret, a long list is\n // reachable only by holding Down.\n event.preventDefault()\n setActiveValue((event.key === 'Home' ? matches[0]! : matches[matches.length - 1]!).value)\n return\n }\n if (event.key === 'Enter') {\n // Only while the list is showing. Otherwise Enter belongs to the form, and\n // a control that swallowed it would break submitting from the keyboard.\n if (!expanded) return\n event.preventDefault()\n if (activeIndex === -1) {\n setOpen(false)\n return\n }\n choose(matches[activeIndex]!.value)\n return\n }\n if (event.key === 'Escape') {\n if (!open && query === null) return\n event.preventDefault()\n // \"Never mind about this list\", not \"delete what I chose earlier\": the\n // query is abandoned and the ANSWER is untouched.\n setOpen(false)\n setActiveValue(null)\n setQuery(null)\n }\n }\n\n const onBlur = (): void => {\n setOpen(false)\n setActiveValue(null)\n if (query !== null) {\n // An emptied box is the empty option, and the only route to null. Anything\n // else typed is abandoned - it was never an answer.\n if (query.trim() === '') field.setValue(null)\n setQuery(null)\n }\n field.touch()\n }\n\n return (\n <FieldShell path={path} field={field} label={label}>\n {/* The popup's containing block, and the reason it is an element rather than\n nothing at all.\n\n The popup was absolutely positioned with `top: auto`, on the reasoning that it\n would then land at its STATIC position -- where it would have sat in the flow,\n directly under the box. That holds inside a block container and NOT inside a\n grid or flex one, and every theme lays a field out with `display: grid`. For an\n absolutely positioned child of a grid container the static position is the\n container's own content-box origin, so the list opened over its own label and\n box rather than under them. Measured in the playground before the fix: the\n field's top edge was 457px, an in-flow child would have sat at 537px, and the\n popup sat at 459px.\n\n So the popup is given a containing block that wraps the control and nothing\n else, and every theme positions it against that explicitly. The status region\n stays OUTSIDE it, because it is a row of the field's grid exactly as the error\n region is. */}\n <div data-formancy-part=\"typeahead-anchor\">\n <input\n type=\"text\"\n role=\"combobox\"\n {...field.controlProps}\n data-formancy-part=\"typeahead\"\n // The browser's own suggestion list would sit over this one. This is not\n // the HTML autofill token WCAG 1.3.5 asks for: that is a separate thing\n // the spec has deliberately not spent the word on yet.\n autoComplete=\"off\"\n aria-expanded={expanded}\n aria-controls={listboxId}\n aria-autocomplete=\"list\"\n {...(sourced.remote?.busy === true\n ? // Busy, never disabled: disabling the element somebody just typed into\n // blurs it and the browser resets focus to the document body -- the same\n // reason the scanner's button stays enabled while a scan is in flight.\n { 'aria-busy': true }\n : {})}\n {...(activeId === undefined ? {} : { 'aria-activedescendant': activeId })}\n value={query ?? chosen?.label ?? ''}\n onChange={(event) => {\n setQuery(event.target.value)\n setOpen(true)\n // Nothing is active on a keystroke: aria-activedescendant is ABSENT\n // rather than pointing at a row the person has not moved to.\n setActiveValue(null)\n }}\n onClick={() => setOpen(true)}\n onKeyDown={onKeyDown}\n onBlur={onBlur}\n />\n {/* Named, because `listbox` is one of the roles whose accessible name is\n required -- `aria-query`'s `listboxRole.accessibleNameRequired` is true.\n axe in jsdom does NOT report its absence, measured by removing this line\n and watching the audit below stay green, so the case that holds it in\n place is a query by role AND name rather than the auditor.\n\n Not the field's own name: two elements answering to the same accessible\n name make \"the control called X\" ambiguous for every query that uses it,\n starting with the conformance driver's own. */}\n <ul\n id={listboxId}\n role=\"listbox\"\n aria-label={`${label} suggestions`}\n data-formancy-part=\"typeahead-listbox\"\n hidden={!expanded}\n >\n {matches.map((option) => (\n <li\n key={option.value}\n id={optionDomId(field, option.value)}\n role=\"option\"\n data-formancy-part=\"typeahead-option\"\n data-active={option.value === activeValue ? 'true' : undefined}\n {...(chosen?.value === option.value ? { 'aria-selected': true } : {})}\n // Keeps DOM focus in the text box, which is the pattern's whole\n // premise; without it the blur handler runs before the click and the\n // click lands on a list that has already gone.\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => choose(option.value)}\n >\n {option.label}\n </li>\n ))}\n </ul>\n </div>\n {/* Present from the start and empty until there is something to say: a live\n region created at the moment it gets its text is one several screen\n readers never announce.\n\n ONE region, and never the error region. A source being down is not a wrong\n answer, and the error region is the control's `aria-describedby` target\n carrying the engine's verdict — the same line the scanner draws.\n\n A source's own words win over \"no options match\": while a request is in\n flight, \"nothing matched\" is not yet true. */}\n <p\n role=\"status\"\n data-formancy-part=\"typeahead-status\"\n {...(statusState({ sourced, open, matches: matches.length }) === undefined\n ? {}\n : { 'data-state': statusState({ sourced, open, matches: matches.length }) })}\n >\n {sourced.remote !== null && sourced.remote.status !== ''\n ? sourced.remote.status\n : open && matches.length === 0 && sourced.remote?.busy !== true\n ? 'No options match'\n : ''}\n </p>\n </FieldShell>\n )\n}\n\n/** One option's element id, in the shape the radio group already uses, so a\n * reader of the DOM meets one convention rather than two. */\nfunction optionDomId(field: FieldBinding, value: string): string {\n return `${field.ids.control}:option:${value}`\n}\n\n/**\n * How a grouped field says it is required.\n *\n * A real element rather than an attribute, because the attribute that would\n * mean this — `aria-required` — is not supported on `role=\"group\"`. The\n * engine puts this element's id into the group's `aria-describedby`, so it is\n * announced after the legend; rendering it is all a renderer has to do.\n *\n * Visible as well as announced: WCAG 1.4.1 means a requirement carried only by\n * a red asterisk is a requirement some people cannot perceive.\n */\nfunction RequiredHint({ field }: { field: ReturnType<typeof useField> }) {\n if (!field.required) return null\n return (\n <span data-formancy-part=\"required-hint\" id={field.props.hint.id}>\n required\n </span>\n )\n}\n\nfunction RadioGroupField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n const options = useResolvedOptions(field)\n const showError = field.touched && field.errors.length > 0\n return (\n <fieldset\n data-formancy-part=\"field\"\n data-formancy-field-path={path}\n data-state={showError ? 'invalid' : 'valid'}\n aria-describedby={field.controlProps['aria-describedby']}\n >\n <legend data-formancy-part=\"label\">{label}</legend>\n <RequiredHint field={field} />\n {options.map((option) => {\n const optionId = `${field.ids.control}:${option.value}`\n return (\n <span key={option.value} data-formancy-part=\"radio-option\">\n <input\n type=\"radio\"\n id={optionId}\n name={field.controlProps.name}\n value={option.value}\n checked={field.value === option.value}\n onChange={() => field.setValue(option.value)}\n onBlur={() => field.touch()}\n />\n <label htmlFor={optionId}>{option.label}</label>\n </span>\n )\n })}\n {showError ? (\n <p data-formancy-part=\"error\" {...field.errorProps}>\n {field.errors.join(', ')}\n </p>\n ) : null}\n </fieldset>\n )\n}\n\n/**\n * Text the reader sees that collects nothing — a heading, an explanation, a\n * notice.\n *\n * Not a `<label>`, because there is no control for one to label, and a label\n * pointing at nothing is a label a screen reader announces as an orphan. Not a\n * heading element either: the spec does not say what level it would be, and\n * guessing produces a document outline that skips levels.\n */\nfunction StaticField({ label }: FieldComponentProps) {\n return <p data-formancy-part=\"static\">{label}</p>\n}\n\n/**\n * Several answers from a list, every option visible at once.\n *\n * A `fieldset` with a `legend`, exactly like the radio group, because the\n * relationship is the same one: several controls that answer a single\n * question. What differs is only that more than one may be chosen, which is\n * `type=\"checkbox\"` and an array — not a different structure and not a\n * different way of being announced.\n *\n * Requiring \"at least one\" is a property of the QUESTION, not of any one box,\n * so it belongs to the group — but not as `aria-required`, which\n * `role=\"group\"` does not support and assistive technology therefore ignores.\n * It is announced through the group's description instead, which the engine\n * composes. Putting it on every box would announce each one as required,\n * which is the opposite of what it means.\n */\nfunction SelectBoxesField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n const options = useResolvedOptions(field)\n const showError = field.touched && field.errors.length > 0\n const chosen = Array.isArray(field.value) ? (field.value as unknown[]) : []\n\n const toggle = (value: string, on: boolean): void => {\n // Rebuilt in the options' own order rather than in the order they were\n // ticked, so two people choosing the same answers store the same array and\n // a diff of two submissions means something.\n const next = options\n .map((option) => option.value)\n .filter((candidate) => (candidate === value ? on : chosen.includes(candidate)))\n field.setValue(next)\n }\n\n return (\n <fieldset\n data-formancy-part=\"field\"\n data-formancy-field-path={path}\n data-state={showError ? 'invalid' : 'valid'}\n aria-describedby={field.controlProps['aria-describedby']}\n >\n <legend data-formancy-part=\"label\">{label}</legend>\n <RequiredHint field={field} />\n {options.map((option) => {\n const optionId = `${field.ids.control}:${option.value}`\n return (\n <span key={option.value} data-formancy-part=\"checkbox-option\">\n <input\n type=\"checkbox\"\n id={optionId}\n name={field.controlProps.name}\n value={option.value}\n checked={chosen.includes(option.value)}\n disabled={field.disabled}\n onChange={(event) => toggle(option.value, event.target.checked)}\n onBlur={() => field.touch()}\n />\n <label htmlFor={optionId}>{option.label}</label>\n </span>\n )\n })}\n {showError ? (\n <p data-formancy-part=\"error\" {...field.errorProps}>\n {field.errors.join(', ')}\n </p>\n ) : null}\n </fieldset>\n )\n}\n\n/**\n * Formatted text, written as the restricted markup the spec defines.\n *\n * A textarea, not a contenteditable surface. That is a deliberate v1 cut and\n * not laziness: a WYSIWYG editor is a large accessibility surface of its own\n * — keyboard shortcuts, an announced selection model, focus management\n * inside a rich region — and shipping a half-built one is worse than\n * shipping a textarea that works with every assistive technology already.\n *\n * What the reader types is never treated as markup by anything. It is parsed\n * into a typed tree and rendered as elements, so there is no path from an\n * answer to `innerHTML` and no sanitiser to keep correct forever.\n */\n/**\n * The rich text toolbar.\n *\n * A row of buttons over a textarea, not a contenteditable surface. That is the\n * deliberate choice — see `@formancy/spec`'s `richtext-edit` for why — and\n * it is what makes this editor cheap to make correct: the control is a plain\n * `<textarea>` that every assistive technology already knows, and the buttons\n * are ordinary buttons that do string arithmetic.\n *\n * The ARIA toolbar pattern, which means ONE tab stop for the whole row and\n * arrow keys within it. Five buttons that each take a tab press would put five\n * stops between a keyboard user and the box they came to type in.\n */\nfunction RichTextToolbar({\n disabled,\n label,\n onCommand,\n}: {\n disabled: boolean\n label: ReactNode\n onCommand: (command: RichCommand, href?: string) => void\n}) {\n const buttons = useRef<Array<HTMLButtonElement | null>>([])\n const [active, setActive] = useState(0)\n\n const commands: ReadonlyArray<{ command: RichCommand; name: string; glyph: string }> = [\n { command: 'strong', name: 'Bold', glyph: 'B' },\n { command: 'emphasis', name: 'Italic', glyph: 'I' },\n { command: 'link', name: 'Link', glyph: '↗' },\n { command: 'bulletList', name: 'Bulleted list', glyph: '•' },\n { command: 'orderedList', name: 'Numbered list', glyph: '1.' },\n ]\n\n const move = (to: number): void => {\n const index = (to + commands.length) % commands.length\n setActive(index)\n buttons.current[index]?.focus()\n }\n\n return (\n <div\n role=\"toolbar\"\n // Named with the field, because a form may have several of these and\n // \"toolbar\" five times tells a screen-reader user nothing about which\n // question they are formatting the answer to.\n aria-label={typeof label === 'string' ? `Formatting for ${label}` : 'Formatting'}\n data-formancy-part=\"richtext-toolbar\"\n onKeyDown={(event) => {\n if (event.key === 'ArrowRight') {\n event.preventDefault()\n move(active + 1)\n } else if (event.key === 'ArrowLeft') {\n event.preventDefault()\n move(active - 1)\n } else if (event.key === 'Home') {\n event.preventDefault()\n move(0)\n } else if (event.key === 'End') {\n event.preventDefault()\n move(commands.length - 1)\n }\n }}\n >\n {commands.map((entry, index) => (\n <button\n key={entry.command}\n ref={(element) => {\n buttons.current[index] = element\n }}\n type=\"button\"\n disabled={disabled}\n // The roving tabindex: one stop for the row, arrows within it.\n tabIndex={index === active ? 0 : -1}\n data-formancy-part=\"richtext-button\"\n onFocus={() => setActive(index)}\n onClick={() => {\n // A link needs an address, and a prompt is the honest version of\n // asking for one without building a dialog this package would\n // then own the accessibility of. A host wanting its own can\n // replace the whole field through the component registry.\n if (entry.command === 'link') {\n const href = window.prompt('Address for the link')\n if (href === null || href === '') return\n onCommand('link', href)\n return\n }\n onCommand(entry.command)\n }}\n >\n {/* The glyph is decoration; the button's name is the word. */}\n <span aria-hidden=\"true\">{entry.glyph}</span>\n <span data-formancy-part=\"visually-hidden\">{entry.name}</span>\n </button>\n ))}\n </div>\n )\n}\n\n/**\n * The host's editor, mounted over the same value the textarea would have edited.\n *\n * Mounted once and fed afterwards, rather than re-created when the value\n * changes: a contenteditable rebuilt on every keystroke loses the caret, the\n * selection and the undo stack, which is the difference between an editor and a\n * box that fights you.\n *\n * What crosses the boundary is the stored grammar in both directions, never\n * markup ([0061](../../../docs/decisions/0061-tiptap-over-the-closed-grammar.md)).\n */\nfunction MountedRichText({\n field,\n value,\n make,\n onReady,\n}: {\n field: ReturnType<typeof useField>\n value: string\n make: RichTextEditorFactory\n onReady: (handle: RichTextEditorHandle | undefined) => void\n}) {\n const host = useRef<HTMLDivElement | null>(null)\n const handle = useRef<RichTextEditorHandle | undefined>(undefined)\n // The value at mount time, read through a ref so the mount effect does not\n // depend on it and therefore does not re-run when it changes.\n const opening = useRef(value)\n const commit = useRef(field.setValue)\n commit.current = field.setValue\n\n useEffect(() => {\n const element = host.current\n if (element === null) return undefined\n\n const editor = make({\n element,\n value: opening.current,\n onChange: (next) => commit.current(next),\n editable: field.disabled !== true,\n // The engine owns the ids and the describedby composition, so they are\n // passed in rather than invented here. `aria-labelledby` rather than a\n // `<label for>`: the surface is a div, and `for` does not reach one.\n attributes: {\n ...(field.controlProps['aria-describedby'] === undefined\n ? {}\n : { 'aria-describedby': field.controlProps['aria-describedby'] }),\n ...(field.controlProps['aria-invalid'] === undefined\n ? {}\n : { 'aria-invalid': 'true' }),\n ...(field.controlProps['aria-required'] === undefined\n ? {}\n : { 'aria-required': 'true' }),\n 'aria-labelledby': field.labelProps.id,\n id: field.controlProps.id,\n // The editing surface is a CHILD of the mount point, so it is the\n // element a theme has to style. Named here rather than left as the\n // editor library's own class, so a theme is not coupled to TipTap.\n 'data-formancy-part': 'richtext-surface',\n },\n })\n\n handle.current = editor\n onReady(editor)\n return () => {\n editor.destroy()\n handle.current = undefined\n onReady(undefined)\n }\n // Mount once. Everything that changes afterwards is pushed in below.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [make])\n\n\n useEffect(() => {\n const editor = handle.current\n if (editor === undefined) return\n // Only when the form changed the value from somewhere else — a calculation,\n // a draft being resumed, a reset. Comparing first is what stops the editor\n // resetting its own caret on every keystroke it just reported.\n if (editor.value() === value) return\n\n // And never while the person is in the editor.\n //\n // Without this the editor reverts its own change. Pressing Bold updates the\n // document, reports the new answer, and React re-renders — but for one\n // render `value` is still the answer from BEFORE the command. That render\n // reaches here, sees a difference, and pushes the stale answer back, which\n // un-bolds the word and then reports THAT.\n //\n // A value arriving from elsewhere while somebody is typing is rare; losing\n // what they just did is not recoverable. So the sync waits for them to leave,\n // and the comparison above catches it then.\n if (host.current?.contains(document.activeElement) === true) return\n\n editor.setValue(value)\n }, [value])\n\n return (\n <div\n data-formancy-part=\"richtext-editor\"\n ref={host}\n onBlur={() => field.touch()}\n />\n )\n}\n\nfunction RichTextField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n const value = typeof field.value === 'string' ? field.value : ''\n const box = useRef<HTMLTextAreaElement | null>(null)\n const make = useRichTextEditorFactory()\n // State rather than a ref: the toolbar has to re-render once the editor\n // exists, or its buttons are wired to nothing on the first paint.\n const [editor, setEditor] = useState<RichTextEditorHandle | undefined>(undefined)\n\n /**\n * Run a toolbar command against the live selection and put the caret back.\n *\n * The selection is restored in an effect-free way {@link queueMicrotask}\n * would not guarantee: React has to have written the new value first, so\n * the box is updated on the next frame rather than immediately. An editor\n * that drops the caret to the end after every button is one nobody can use\n * for a second word.\n */\n const run = (command: RichCommand, href?: string): void => {\n const element = box.current\n if (element === null) return\n\n const next = applyRichCommand(\n command,\n { value, start: element.selectionStart, end: element.selectionEnd },\n href === undefined ? {} : { href },\n )\n field.setValue(next.value)\n requestAnimationFrame(() => {\n element.focus()\n element.setSelectionRange(next.start, next.end)\n })\n }\n\n if (make !== undefined) {\n // The toolbar stays. An editor library brings keyboard shortcuts and no\n // toolbar UI, so leaving ours out made Bold reachable by Ctrl+B and by no\n // visible control — worse than the textarea it replaced. One toolbar drives\n // either surface, over the same `RichCommand` values, so the two cannot come\n // to offer different things.\n //\n // No preview, though: the surface IS the preview, which is the whole reason\n // somebody wanted this.\n return (\n <FieldShell path={path} field={field} label={label}>\n <RichTextToolbar\n disabled={field.disabled}\n label={label}\n onCommand={(command, href) => editor?.run(command, href)}\n />\n <MountedRichText field={field} value={value} make={make} onReady={setEditor} />\n </FieldShell>\n )\n }\n\n return (\n <FieldShell path={path} field={field} label={label}>\n <RichTextToolbar\n disabled={field.disabled}\n label={label}\n onCommand={run}\n />\n <textarea\n {...field.controlProps}\n ref={box}\n rows={5}\n value={value}\n onKeyDown={(event) => {\n // The two shortcuts every editor has. Nothing else is bound: a\n // surprise binding in a form field is worse than no binding.\n if (!(event.ctrlKey || event.metaKey)) return\n const command =\n event.key.toLowerCase() === 'b'\n ? 'strong'\n : event.key.toLowerCase() === 'i'\n ? 'emphasis'\n : undefined\n if (command === undefined) return\n event.preventDefault()\n run(command)\n }}\n onChange={(event) => field.setValue(event.target.value)}\n onBlur={() => field.touch()}\n />\n {/* What the stored answer will look like, from the same parser the form\n that displays it will use. Not decoration: the grammar is small\n enough that a preview is how somebody learns it. */}\n <div data-formancy-part=\"richtext-preview\" aria-live=\"off\">\n <RichText source={value} />\n </div>\n </FieldShell>\n )\n}\n\n/**\n * Attached files.\n *\n * The control picks files; something else uploads them and reports back what\n * was stored. That split is the whole design: this package has no opinion\n * about where bytes go, which is what lets the same field work against local\n * disk, S3 or a customer's own service.\n *\n * Without an uploader the field is read-only and says so, rather than\n * pretending to accept a file it has nowhere to put.\n */\nfunction FileField({ path, label }: FieldComponentProps) {\n const field = useField(path)\n const upload = useUploader()\n const files = Array.isArray(field.value) ? (field.value as StoredFile[]) : []\n const [busy, setBusy] = useState(false)\n const [failure, setFailure] = useState<string | undefined>(undefined)\n const [over, setOver] = useState(false)\n /**\n * Attachments taken out of the answer but not yet forgotten.\n *\n * Held with the position they came from, so undoing puts a file BACK where it\n * was rather than on the end — the order matters to somebody who numbered\n * their attachments in a covering note.\n */\n const [removed, setRemoved] = useState<ReadonlyArray<{ at: number; file: StoredFile }>>([])\n\n const accept = field.def.accept\n const multiple = field.def.maxItems === undefined || field.def.maxItems > 1\n\n const onPick = async (picked: FileList | null): Promise<void> => {\n if (picked === null || picked.length === 0 || upload === undefined) return\n setBusy(true)\n setFailure(undefined)\n\n // Each file succeeds or fails on its own.\n //\n // The first version collected them into an array and set the value once, so\n // a throw on the third of five discarded the two that had ALREADY uploaded:\n // their bytes were in storage, the submission never mentioned them, the\n // collector reclaimed them within the day, and the person was told the\n // upload failed when half of it had not. Whose fault the failure is does not\n // change who loses the file.\n const uploaded: StoredFile[] = []\n const refused: string[] = []\n\n for (const file of Array.from(picked)) {\n try {\n uploaded.push(await upload(file))\n } catch (error) {\n refused.push(`${file.name} (${error instanceof Error ? error.message : String(error)})`)\n }\n }\n\n // Recorded before the failure is reported, so nothing that reached storage\n // is left unclaimed while somebody reads the message.\n if (uploaded.length > 0) field.setValue([...files, ...uploaded])\n\n if (refused.length > 0) {\n // Named, because \"the upload failed\" over a list of five attachments does\n // not say which one to try again.\n setFailure(\n refused.length === 1\n ? `${refused[0]!} was not attached.`\n : `${String(refused.length)} files were not attached: ${refused.join(', ')}.`,\n )\n }\n\n setBusy(false)\n field.touch()\n }\n\n return (\n <FieldShell path={path} field={field} label={label}>\n {upload === undefined ? (\n <p data-formancy-part=\"file-unavailable\">\n This form cannot accept files here, because no upload destination has been configured.\n </p>\n ) : (\n /*\n * The picker inside a drop target, not instead of one.\n *\n * Dropping is a pointer gesture with no keyboard equivalent, so it can\n * only ever be a SECOND route (WCAG 2.1.1). The input stays exactly as\n * it was and keeps the field's label and ARIA wiring; the region around\n * it accepts a drop and hands the files to the same function. Two routes,\n * one implementation.\n */\n <div\n data-formancy-part=\"file-dropzone\"\n {...(over ? { 'data-state': 'over' } : {})}\n onDragOver={(event) => {\n // Without preventDefault the browser navigates to the file instead\n // of letting the page have it, which looks like the form vanishing.\n event.preventDefault()\n if (field.disabled || busy) return\n setOver(true)\n }}\n onDragLeave={() => setOver(false)}\n onDrop={(event) => {\n event.preventDefault()\n setOver(false)\n if (field.disabled || busy) return\n void onPick(event.dataTransfer.files)\n }}\n >\n <input\n {...field.controlProps}\n type=\"file\"\n multiple={multiple}\n {...(accept === undefined ? {} : { accept: accept.join(',') })}\n disabled={field.disabled || busy}\n onChange={(event) => {\n void onPick(event.target.files)\n event.target.value = ''\n }}\n />\n </div>\n )}\n\n {files.length === 0 && removed.length === 0 ? null : (\n <ul data-formancy-part=\"file-list\">\n {files.map((file) => (\n <li key={file.id} data-formancy-part=\"file-item\">\n <span>{file.name}</span>\n <button\n type=\"button\"\n disabled={field.disabled}\n onClick={() => {\n // Out of the answer immediately, so a submit in between is\n // correct, and remembered so it can come back.\n setRemoved((before) => [\n ...before,\n { at: files.findIndex((other) => other.id === file.id), file },\n ])\n field.setValue(files.filter((other) => other.id !== file.id))\n }}\n >\n {/* Named with the file, so a screen reader user hears which\n attachment a button removes rather than \"remove\" six\n times over. */}\n Remove {file.name}\n </button>\n </li>\n ))}\n\n {/*\n * A removed attachment stays visible with a way back.\n *\n * The bytes are still in storage until the unclaimed collector runs, so\n * the removal is recoverable for free — and a misclick on the wrong row\n * of six is the ordinary way somebody loses the evidence they came to\n * attach. A row that simply disappears offers no way to notice.\n */}\n {removed.map(({ at, file }) => (\n <li key={file.id} data-formancy-part=\"file-item\" data-state=\"removed\">\n <span>{file.name}</span>\n <button\n type=\"button\"\n disabled={field.disabled}\n onClick={() => {\n setRemoved((before) => before.filter((other) => other.file.id !== file.id))\n // Back where it was, not on the end: the order matters to\n // somebody who numbered their attachments in a covering note.\n const next = [...files]\n next.splice(Math.min(at, next.length), 0, file)\n field.setValue(next)\n }}\n >\n Undo removing {file.name}\n </button>\n </li>\n ))}\n </ul>\n )}\n\n {/* One polite region per field for the upload's own progress: the form's\n error region belongs to validation, and an upload failure is not a\n validation error. */}\n <p role=\"status\" data-formancy-part=\"file-status\">\n {busy ? 'Uploading…' : (failure ?? '')}\n </p>\n </FieldShell>\n )\n}\n\n/**\n * The built-in unstyled components. `null` means the type renders nothing here:\n * a hidden field is carried in the submission and never shown, and the\n * container types are laid out by their own machinery rather than by a leaf\n * slot.\n */\nconst DEFAULT_COMPONENTS: Record<FieldType, FieldComponent | null> = {\n text: TextField,\n textarea: TextareaField,\n number: NumberField,\n checkbox: CheckboxField,\n date: DateField,\n time: TimeField,\n datetime: DateTimeField,\n select: SelectField,\n radio: RadioGroupField,\n selectboxes: SelectBoxesField,\n file: FileField,\n richtext: RichTextField,\n hidden: null,\n static: StaticField,\n group: null,\n page: null,\n repeater: null,\n}\n","import { focusControl } from './focus-control.js'\nimport { useCallback, useEffect, useRef, useSyncExternalStore } from 'react'\nimport { parsePath } from '@formancy/core'\nimport { useFormEngine } from './context.js'\n\nexport interface ErrorSummaryProps {\n labels?: Record<string, string>\n}\n\n/**\n * The error summary a failed submit focuses.\n *\n * The semantics are deliberate and easy to get wrong: the container takes\n * focus via tabindex=\"-1\" — focusing it already makes screen readers announce\n * it, so role=\"alert\" would announce it TWICE. Each entry is a real in-page\n * link to the offending control, because links are what \"take me to the\n * problem\" already means to assistive tech.\n */\nexport function ErrorSummary({ labels }: ErrorSummaryProps) {\n const engine = useFormEngine()\n const subscribe = useCallback((onChange: () => void) => engine.subscribe(onChange), [engine])\n const getErrors = useCallback(() => engine.visibleErrors(), [engine])\n const errors = useSyncExternalStore(subscribe, getErrors, getErrors)\n const region = useRef<HTMLDivElement | null>(null)\n const previousCount = useRef(0)\n\n // Focus when errors APPEAR (a submit surfaced them), not on every keystroke\n // that edits an already-broken field.\n useEffect(() => {\n if (errors.length > 0 && previousCount.current === 0) region.current?.focus()\n previousCount.current = errors.length\n }, [errors])\n\n if (errors.length === 0) return null\n\n const labelFor = (path: string): string =>\n labels?.[path] ?? labels?.[path.replace(/\\[\\d+\\]/, '[]')] ?? path\n\n return (\n <div data-formancy-part=\"error-summary\" tabIndex={-1} ref={region}>\n <h2 data-formancy-part=\"error-summary-heading\">\n {errors.length === 1 ? 'There is 1 problem to fix' : `There are ${errors.length} problems to fix`}\n </h2>\n <ul>\n {errors.map(({ path, codes }) => {\n const controlId = engine.getFieldSnapshot(parsePath(path)).ids.control\n return (\n <li key={path}>\n <a\n href={`#${controlId}`}\n onClick={(event) => {\n // The hash alone scrolls but does not focus; do both.\n event.preventDefault()\n focusControl(document.getElementById(controlId))\n }}\n >\n {`${labelFor(path)}: ${codes.join(', ')}`}\n </a>\n </li>\n )\n })}\n </ul>\n </div>\n )\n}\n","import { useEffect, useRef } from 'react'\nimport type { ReactElement } from 'react'\n\n/**\n * Telling somebody their draft came back changed.\n *\n * The server already does the careful half. A republished form migrates a draft\n * lazily on resume, and answers whose field no longer exists move to\n * `data.__orphaned` rather than being deleted\n * ([0027](../../../docs/decisions/0027-lazy-draft-migration.md)). It reports what\n * happened as a severity and a list of changes.\n *\n * This is what shows it. Without it somebody resumes a draft, some of their answers are\n * no longer on the form, and they submit believing everything they typed is in it — the\n * answers are not lost from storage, they are lost from the submission.\n *\n * It is built like the error summary, and for the same reason: something important\n * happened and the person may be looking at the middle of a long form. The container\n * takes focus through `tabindex=\"-1\"`, and is **not** `role=\"alert\"` and carries no\n * `aria-live` — focusing a container already makes a screen reader announce it, and\n * doing both announces it twice.\n *\n * **A library cannot make a host render it.** Resuming a draft is the host's call, so\n * this component exists and is documented, and showing it is the deployment's\n * responsibility. `SAFETY-ANALYSIS.md` says so rather than claiming a guarantee.\n */\n\n/** What `resumeDraft` reports. Structural, so a host need not import a type. */\nexport interface ResumeMigration {\n readonly severity: 'lossy' | 'breaking'\n readonly changes: ReadonlyArray<{ readonly kind: string; readonly path?: string }>\n}\n\nexport interface ResumeNoticeProps {\n /** Omitted or undefined when the draft came back unchanged. */\n readonly migration?: ResumeMigration | undefined\n /** Question wording for a field key, since a key is not what the form asked. */\n readonly labels?: Readonly<Record<string, string>>\n}\n\nexport function ResumeNotice({ migration, labels }: ResumeNoticeProps): ReactElement | null {\n const region = useRef<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (migration !== undefined) region.current?.focus()\n }, [migration])\n\n // A form that opens by announcing that nothing happened teaches people to\n // dismiss the notice without reading it, which is how the one that matters\n // gets missed.\n if (migration === undefined) return null\n\n const setAside = migration.changes\n .map((change) => change.path)\n .filter((path): path is string => path !== undefined)\n\n return (\n <div\n role=\"region\"\n aria-label=\"This form changed while you were away\"\n data-formancy-part=\"resume-notice\"\n data-state={migration.severity}\n tabIndex={-1}\n ref={region}\n >\n <h2 data-formancy-part=\"resume-notice-heading\">This form changed while you were away</h2>\n\n {migration.severity === 'breaking' ? (\n <p>\n It changed too much for your answers to be moved across, so this is being shown as you\n left it and <strong>cannot be submitted</strong>. Starting again will give you the current\n form.\n </p>\n ) : (\n <>\n <p>\n {setAside.length === 1\n ? 'One question is no longer on this form. Your answer to it is still kept with the rest and will be sent with them — it is just not shown here any more.'\n : `${String(setAside.length)} questions are no longer on this form. Your answers to them are still kept with the rest and will be sent with them — they are just not shown here any more.`}\n </p>\n {setAside.length === 0 ? null : (\n <ul data-formancy-part=\"resume-notice-list\">\n {setAside.map((path) => (\n // The question's wording when the caller knows it. A field key\n // is what the schema calls it, not what the form asked.\n <li key={path}>{labels?.[path] ?? path}</li>\n ))}\n </ul>\n )}\n </>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formancy/react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Idiomatic React binding for the formancy form engine.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -17,19 +17,24 @@
|
|
|
17
17
|
"NOTICE"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@formancy/
|
|
21
|
-
"@formancy/
|
|
22
|
-
"
|
|
20
|
+
"@formancy/core": "0.2.0",
|
|
21
|
+
"@formancy/spec": "0.2.0",
|
|
22
|
+
"uqr": "^0.1.3"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"react": "^19.0.0"
|
|
25
|
+
"react": "^19.0.0",
|
|
26
|
+
"react-dom": "^19.0.0"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
30
|
+
"@formancy/conformance": "0.2.0",
|
|
29
31
|
"@testing-library/react": "^16.3.3",
|
|
30
32
|
"@testing-library/user-event": "^14.6.7",
|
|
31
33
|
"@types/react": "^19.3.0",
|
|
32
34
|
"@types/react-dom": "^19.3.0",
|
|
35
|
+
"@vitest/coverage-v8": "^5.0.1",
|
|
36
|
+
"axe-core": "^4.13.0",
|
|
37
|
+
"dom-accessibility-api": "^0.5.16",
|
|
33
38
|
"jsdom": "^30.1.0",
|
|
34
39
|
"publint": "^0.3.24",
|
|
35
40
|
"react": "^19.3.0",
|
|
@@ -51,6 +56,7 @@
|
|
|
51
56
|
"bugs": "https://github.com/sharkysan/formancy.ai/issues",
|
|
52
57
|
"scripts": {
|
|
53
58
|
"build": "tsdown",
|
|
59
|
+
"test:coverage": "vitest run --coverage",
|
|
54
60
|
"test": "vitest run",
|
|
55
61
|
"typecheck": "tsc --noEmit",
|
|
56
62
|
"check:pkg": "publint && attw --pack . --profile esm-only"
|