@fogpipe/forma-react 0.15.0 → 0.17.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.ts +29 -4
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/FieldRenderer.tsx +24 -1
- package/src/FormRenderer.tsx +23 -1
- package/src/__tests__/FieldRenderer.test.tsx +182 -0
- package/src/index.ts +2 -0
- package/src/types.ts +30 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FieldError, SelectOption, FieldDefinition, Forma, OptionsVisibilityResult, ValidationResult } from '@fogpipe/forma-core';
|
|
1
|
+
import { FieldError, SelectOption, FieldDefinition, Forma, MatrixColumn, OptionsVisibilityResult, ValidationResult } from '@fogpipe/forma-core';
|
|
2
2
|
export { ComputedField, FieldDefinition, FieldError, FieldType, Forma, PageDefinition, SelectOption, ValidationResult, ValidationRule } from '@fogpipe/forma-core';
|
|
3
3
|
import * as React$1 from 'react';
|
|
4
4
|
import React__default from 'react';
|
|
@@ -133,7 +133,7 @@ interface BaseFieldProps {
|
|
|
133
133
|
* Props for text-based fields (text, email, password, url, textarea)
|
|
134
134
|
*/
|
|
135
135
|
interface TextFieldProps extends Omit<BaseFieldProps, "value" | "onChange"> {
|
|
136
|
-
fieldType: "text" | "email" | "password" | "url" | "textarea";
|
|
136
|
+
fieldType: "text" | "phone" | "email" | "password" | "url" | "textarea";
|
|
137
137
|
value: string;
|
|
138
138
|
onChange: (value: string) => void;
|
|
139
139
|
}
|
|
@@ -331,16 +331,36 @@ interface DisplayFieldProps extends Omit<BaseFieldProps, "value" | "onChange"> {
|
|
|
331
331
|
onChange?: never;
|
|
332
332
|
value?: never;
|
|
333
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Props for matrix/grid fields
|
|
336
|
+
*/
|
|
337
|
+
interface MatrixFieldProps extends Omit<BaseFieldProps, "value" | "onChange"> {
|
|
338
|
+
fieldType: "matrix";
|
|
339
|
+
/** Current matrix value: row ID → selected column value(s) */
|
|
340
|
+
value: Record<string, string | number | string[] | number[]> | null;
|
|
341
|
+
onChange: (value: Record<string, string | number | string[] | number[]>) => void;
|
|
342
|
+
/** Row definitions with visibility state */
|
|
343
|
+
rows: Array<{
|
|
344
|
+
id: string;
|
|
345
|
+
label: string;
|
|
346
|
+
visible: boolean;
|
|
347
|
+
}>;
|
|
348
|
+
/** Column definitions (shared options for all rows) */
|
|
349
|
+
columns: MatrixColumn[];
|
|
350
|
+
/** Whether multiple selections per row are allowed */
|
|
351
|
+
multiSelect: boolean;
|
|
352
|
+
}
|
|
334
353
|
/**
|
|
335
354
|
* Union of all field prop types
|
|
336
355
|
*/
|
|
337
|
-
type FieldProps = TextFieldProps | NumberFieldProps | IntegerFieldProps | BooleanFieldProps | DateFieldProps | DateTimeFieldProps | SelectFieldProps | MultiSelectFieldProps | ArrayFieldProps | ObjectFieldProps | ComputedFieldProps | DisplayFieldProps;
|
|
356
|
+
type FieldProps = TextFieldProps | NumberFieldProps | IntegerFieldProps | BooleanFieldProps | DateFieldProps | DateTimeFieldProps | SelectFieldProps | MultiSelectFieldProps | ArrayFieldProps | ObjectFieldProps | ComputedFieldProps | DisplayFieldProps | MatrixFieldProps;
|
|
338
357
|
/**
|
|
339
358
|
* Map of field types to React components
|
|
340
359
|
* Components receive wrapper props with { field, spec } structure
|
|
341
360
|
*/
|
|
342
361
|
interface ComponentMap {
|
|
343
362
|
text?: React.ComponentType<TextComponentProps>;
|
|
363
|
+
phone?: React.ComponentType<TextComponentProps>;
|
|
344
364
|
email?: React.ComponentType<TextComponentProps>;
|
|
345
365
|
password?: React.ComponentType<TextComponentProps>;
|
|
346
366
|
url?: React.ComponentType<TextComponentProps>;
|
|
@@ -356,6 +376,7 @@ interface ComponentMap {
|
|
|
356
376
|
object?: React.ComponentType<ObjectComponentProps>;
|
|
357
377
|
computed?: React.ComponentType<ComputedComponentProps>;
|
|
358
378
|
display?: React.ComponentType<DisplayComponentProps>;
|
|
379
|
+
matrix?: React.ComponentType<MatrixComponentProps>;
|
|
359
380
|
fallback?: React.ComponentType<FieldComponentProps>;
|
|
360
381
|
}
|
|
361
382
|
/**
|
|
@@ -448,6 +469,10 @@ interface DisplayComponentProps {
|
|
|
448
469
|
field: DisplayFieldProps;
|
|
449
470
|
spec: Forma;
|
|
450
471
|
}
|
|
472
|
+
interface MatrixComponentProps {
|
|
473
|
+
field: MatrixFieldProps;
|
|
474
|
+
spec: Forma;
|
|
475
|
+
}
|
|
451
476
|
/**
|
|
452
477
|
* Generic field component props (for fallback/dynamic components)
|
|
453
478
|
*/
|
|
@@ -813,4 +838,4 @@ declare const FormaContext: React$1.Context<UseFormaReturn | null>;
|
|
|
813
838
|
*/
|
|
814
839
|
declare function useFormaContext(): UseFormaReturn;
|
|
815
840
|
|
|
816
|
-
export { type ArrayComponentProps, type ArrayFieldProps, type ArrayHelpers, type ArrayItemFieldProps, type ArrayItemFieldPropsResult, type BaseFieldProps, type BooleanComponentProps, type BooleanFieldProps, type ComponentMap, type ComputedComponentProps, type ComputedFieldProps, type DateComponentProps, type DateFieldProps, type DateTimeComponentProps, type DateTimeFieldProps, type DisplayComponentProps, type DisplayFieldProps, type FieldComponentProps, type FieldProps, FieldRenderer, type FieldRendererProps, type FieldWrapperProps, FormRenderer, type FormRendererHandle, type FormRendererProps, type UseFormaReturn as FormState, FormaContext, FormaErrorBoundary, type FormaErrorBoundaryProps, type FormaEventListener, type FormaEventMap, type FormaEvents, type GetArrayHelpersResult, type GetFieldPropsResult, type GetSelectFieldPropsResult, type IntegerComponentProps, type IntegerFieldProps, type LayoutProps, type LegacyFieldProps, type MultiSelectComponentProps, type MultiSelectFieldProps, type NumberComponentProps, type NumberFieldProps, type ObjectComponentProps, type ObjectFieldProps, type PageState, type PageWrapperProps, type SelectComponentProps, type SelectFieldProps, type SelectionFieldProps, type TextComponentProps, type TextFieldProps, type UseFormaOptions, type UseFormaReturn, type WizardHelpers, useForma, useFormaContext };
|
|
841
|
+
export { type ArrayComponentProps, type ArrayFieldProps, type ArrayHelpers, type ArrayItemFieldProps, type ArrayItemFieldPropsResult, type BaseFieldProps, type BooleanComponentProps, type BooleanFieldProps, type ComponentMap, type ComputedComponentProps, type ComputedFieldProps, type DateComponentProps, type DateFieldProps, type DateTimeComponentProps, type DateTimeFieldProps, type DisplayComponentProps, type DisplayFieldProps, type FieldComponentProps, type FieldProps, FieldRenderer, type FieldRendererProps, type FieldWrapperProps, FormRenderer, type FormRendererHandle, type FormRendererProps, type UseFormaReturn as FormState, FormaContext, FormaErrorBoundary, type FormaErrorBoundaryProps, type FormaEventListener, type FormaEventMap, type FormaEvents, type GetArrayHelpersResult, type GetFieldPropsResult, type GetSelectFieldPropsResult, type IntegerComponentProps, type IntegerFieldProps, type LayoutProps, type LegacyFieldProps, type MatrixComponentProps, type MatrixFieldProps, type MultiSelectComponentProps, type MultiSelectFieldProps, type NumberComponentProps, type NumberFieldProps, type ObjectComponentProps, type ObjectFieldProps, type PageState, type PageWrapperProps, type SelectComponentProps, type SelectFieldProps, type SelectionFieldProps, type TextComponentProps, type TextFieldProps, type UseFormaOptions, type UseFormaReturn, type WizardHelpers, useForma, useFormaContext };
|
package/dist/index.js
CHANGED
|
@@ -1143,6 +1143,22 @@ var FormRenderer = forwardRef(
|
|
|
1143
1143
|
minItems,
|
|
1144
1144
|
maxItems
|
|
1145
1145
|
};
|
|
1146
|
+
} else if (fieldType === "matrix" && fieldDef.type === "matrix") {
|
|
1147
|
+
const matrixValue = baseProps.value ?? null;
|
|
1148
|
+
const rows = fieldDef.rows.map((row) => ({
|
|
1149
|
+
id: row.id,
|
|
1150
|
+
label: row.label,
|
|
1151
|
+
visible: formaVisibility[`${fieldPath}.${row.id}`] !== false
|
|
1152
|
+
}));
|
|
1153
|
+
fieldProps = {
|
|
1154
|
+
...baseProps,
|
|
1155
|
+
fieldType: "matrix",
|
|
1156
|
+
value: matrixValue,
|
|
1157
|
+
onChange: baseProps.onChange,
|
|
1158
|
+
rows,
|
|
1159
|
+
columns: fieldDef.columns,
|
|
1160
|
+
multiSelect: fieldDef.multiSelect ?? false
|
|
1161
|
+
};
|
|
1146
1162
|
} else if (fieldType === "display" && fieldDef.type === "display") {
|
|
1147
1163
|
const sourceValue = fieldDef.source ? formaData[fieldDef.source] ?? formaComputed[fieldDef.source] : void 0;
|
|
1148
1164
|
const {
|
|
@@ -1442,6 +1458,22 @@ function FieldRenderer({
|
|
|
1442
1458
|
minItems,
|
|
1443
1459
|
maxItems
|
|
1444
1460
|
};
|
|
1461
|
+
} else if (fieldType === "matrix" && fieldDef.type === "matrix") {
|
|
1462
|
+
const matrixValue = baseProps.value ?? null;
|
|
1463
|
+
const rows = fieldDef.rows.map((row) => ({
|
|
1464
|
+
id: row.id,
|
|
1465
|
+
label: row.label,
|
|
1466
|
+
visible: forma.visibility[`${fieldPath}.${row.id}`] !== false
|
|
1467
|
+
}));
|
|
1468
|
+
fieldProps = {
|
|
1469
|
+
...baseProps,
|
|
1470
|
+
fieldType: "matrix",
|
|
1471
|
+
value: matrixValue,
|
|
1472
|
+
onChange: baseProps.onChange,
|
|
1473
|
+
rows,
|
|
1474
|
+
columns: fieldDef.columns,
|
|
1475
|
+
multiSelect: fieldDef.multiSelect ?? false
|
|
1476
|
+
};
|
|
1445
1477
|
} else if (fieldType === "display" && fieldDef.type === "display") {
|
|
1446
1478
|
const sourceValue = fieldDef.source ? forma.data[fieldDef.source] ?? forma.computed[fieldDef.source] : void 0;
|
|
1447
1479
|
const {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useForma.ts","../src/events.ts","../src/FormRenderer.tsx","../src/context.ts","../src/FieldRenderer.tsx","../src/ErrorBoundary.tsx"],"sourcesContent":["/**\n * useForma Hook\n *\n * Main hook for managing Forma form state.\n * This is a placeholder - the full implementation will be migrated from formidable.\n */\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Forma,\n FieldError,\n ValidationResult,\n SelectOption,\n} from \"@fogpipe/forma-core\";\nimport { isAdornableField } from \"@fogpipe/forma-core\";\nimport type {\n GetFieldPropsResult,\n GetSelectFieldPropsResult,\n GetArrayHelpersResult,\n} from \"./types.js\";\nimport { FormaEventEmitter } from \"./events.js\";\nimport type { FormaEventMap, FormaEvents } from \"./events.js\";\nimport {\n getVisibility,\n getRequired,\n getEnabled,\n getReadonly,\n validate,\n calculate,\n getPageVisibility,\n getOptionsVisibility,\n} from \"@fogpipe/forma-core\";\nimport type { OptionsVisibilityResult } from \"@fogpipe/forma-core\";\n\n/**\n * Options for useForma hook\n */\nexport interface UseFormaOptions {\n /** The Forma specification */\n spec: Forma;\n /** Initial form data */\n initialData?: Record<string, unknown>;\n /** Submit handler */\n onSubmit?: (data: Record<string, unknown>) => void | Promise<void>;\n /** Change handler */\n onChange?: (\n data: Record<string, unknown>,\n computed?: Record<string, unknown>,\n ) => void;\n /** When to validate: on change, blur, or submit only */\n validateOn?: \"change\" | \"blur\" | \"submit\";\n /** Additional reference data to merge with spec.referenceData */\n referenceData?: Record<string, unknown>;\n /**\n * Debounce validation by this many milliseconds.\n * Useful for large forms to improve performance.\n * Set to 0 (default) for immediate validation.\n */\n validationDebounceMs?: number;\n /**\n * Declarative event listeners for form lifecycle events.\n * Listeners are stable for the lifetime of the hook — the latest\n * callback is always invoked via refs, without causing dependency changes.\n */\n on?: FormaEvents;\n}\n\n/**\n * Form state\n */\ninterface FormState {\n data: Record<string, unknown>;\n touched: Record<string, boolean>;\n isSubmitting: boolean;\n isSubmitted: boolean;\n isDirty: boolean;\n currentPage: number;\n}\n\n/**\n * State actions\n */\ntype FormAction =\n | { type: \"SET_FIELD_VALUE\"; field: string; value: unknown }\n | { type: \"SET_FIELD_TOUCHED\"; field: string; touched: boolean }\n | { type: \"SET_VALUES\"; values: Record<string, unknown> }\n | { type: \"SET_SUBMITTING\"; isSubmitting: boolean }\n | { type: \"SET_SUBMITTED\"; isSubmitted: boolean }\n | { type: \"SET_PAGE\"; page: number }\n | { type: \"RESET\"; initialData: Record<string, unknown> };\n\n/**\n * Page state for multi-page forms\n */\nexport interface PageState {\n id: string;\n title: string;\n description?: string;\n visible: boolean;\n fields: string[];\n}\n\n/**\n * Wizard navigation helpers\n */\nexport interface WizardHelpers {\n pages: PageState[];\n currentPageIndex: number;\n currentPage: PageState | null;\n goToPage: (index: number) => void;\n nextPage: () => void;\n previousPage: () => void;\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n canProceed: boolean;\n isLastPage: boolean;\n touchCurrentPageFields: () => void;\n validateCurrentPage: () => boolean;\n}\n\n/**\n * Return type of useForma hook\n */\nexport interface UseFormaReturn {\n /** Current form data */\n data: Record<string, unknown>;\n /** Computed field values */\n computed: Record<string, unknown>;\n /** Field visibility map */\n visibility: Record<string, boolean>;\n /** Field required state map */\n required: Record<string, boolean>;\n /** Field enabled state map */\n enabled: Record<string, boolean>;\n /** Field readonly state map */\n readonly: Record<string, boolean>;\n /** Visible options for select/multiselect fields, keyed by field path */\n optionsVisibility: OptionsVisibilityResult;\n /** Field touched state map */\n touched: Record<string, boolean>;\n /** Validation errors */\n errors: FieldError[];\n /** Whether form is valid */\n isValid: boolean;\n /** Whether form is submitting */\n isSubmitting: boolean;\n /** Whether form has been submitted */\n isSubmitted: boolean;\n /** Whether any field has been modified */\n isDirty: boolean;\n /** The Forma spec */\n spec: Forma;\n /** Wizard helpers (if multi-page) */\n wizard: WizardHelpers | null;\n\n /** Set a field value */\n setFieldValue: (path: string, value: unknown) => void;\n /** Set a field as touched */\n setFieldTouched: (path: string, touched?: boolean) => void;\n /** Set multiple values */\n setValues: (values: Record<string, unknown>) => void;\n /** Validate a single field */\n validateField: (path: string) => FieldError[];\n /** Validate entire form */\n validateForm: () => ValidationResult;\n /** Submit the form */\n submitForm: () => Promise<void>;\n /** Reset the form */\n resetForm: () => void;\n\n /**\n * Register an imperative event listener. Returns an unsubscribe function.\n * Multiple listeners per event are supported; they fire in registration order.\n */\n on: <K extends keyof FormaEventMap>(\n event: K,\n listener: (payload: FormaEventMap[K]) => void | Promise<void>,\n ) => () => void;\n\n // Helper methods for getting field props\n /** Get props for any field */\n getFieldProps: (path: string) => GetFieldPropsResult;\n /** Get props for select field (includes options) */\n getSelectFieldProps: (path: string) => GetSelectFieldPropsResult;\n /** Get array helpers for array field */\n getArrayHelpers: (path: string) => GetArrayHelpersResult;\n}\n\n/**\n * State reducer\n */\nfunction formReducer(state: FormState, action: FormAction): FormState {\n switch (action.type) {\n case \"SET_FIELD_VALUE\":\n return {\n ...state,\n data: { ...state.data, [action.field]: action.value },\n isDirty: true,\n isSubmitted: false, // Clear on data change\n };\n case \"SET_FIELD_TOUCHED\":\n return {\n ...state,\n touched: { ...state.touched, [action.field]: action.touched },\n };\n case \"SET_VALUES\":\n return {\n ...state,\n data: { ...state.data, ...action.values },\n isDirty: true,\n isSubmitted: false, // Clear on data change\n };\n case \"SET_SUBMITTING\":\n return { ...state, isSubmitting: action.isSubmitting };\n case \"SET_SUBMITTED\":\n return { ...state, isSubmitted: action.isSubmitted };\n case \"SET_PAGE\":\n return { ...state, currentPage: action.page };\n case \"RESET\":\n return {\n data: action.initialData,\n touched: {},\n isSubmitting: false,\n isSubmitted: false,\n isDirty: false,\n currentPage: 0,\n };\n default:\n return state;\n }\n}\n\n/**\n * Get default initial values for boolean fields.\n * Boolean fields default to false to avoid undefined state,\n * which provides better UX since false is a valid answer.\n */\nfunction getDefaultBooleanValues(spec: Forma): Record<string, boolean> {\n const defaults: Record<string, boolean> = {};\n for (const fieldPath of spec.fieldOrder) {\n const schemaProperty = spec.schema.properties?.[fieldPath];\n const fieldDef = spec.fields[fieldPath];\n if (schemaProperty?.type === \"boolean\" || fieldDef?.type === \"boolean\") {\n defaults[fieldPath] = false;\n }\n }\n return defaults;\n}\n\n/**\n * Get default values from field definitions.\n * Collects `defaultValue` from each field that specifies one.\n * These are applied after boolean defaults but before initialData,\n * so explicit defaults override type-implicit defaults,\n * and runtime initialData overrides everything.\n */\nfunction getFieldDefaults(spec: Forma): Record<string, unknown> {\n const defaults: Record<string, unknown> = {};\n for (const [fieldPath, fieldDef] of Object.entries(spec.fields)) {\n if (fieldDef.defaultValue !== undefined) {\n defaults[fieldPath] = fieldDef.defaultValue;\n }\n }\n return defaults;\n}\n\n/**\n * Main Forma hook\n */\nexport function useForma(options: UseFormaOptions): UseFormaReturn {\n const {\n spec: inputSpec,\n initialData = {},\n onSubmit,\n onChange,\n validateOn = \"blur\",\n referenceData,\n validationDebounceMs = 0,\n on: onEvents,\n } = options;\n\n // Merge referenceData from options with spec.referenceData\n const spec = useMemo((): Forma => {\n if (!referenceData) return inputSpec;\n return {\n ...inputSpec,\n referenceData: {\n ...inputSpec.referenceData,\n ...referenceData,\n },\n };\n }, [inputSpec, referenceData]);\n\n const [state, dispatch] = useReducer(formReducer, {\n data: {\n ...getDefaultBooleanValues(spec),\n ...getFieldDefaults(spec),\n ...initialData,\n },\n touched: {},\n isSubmitting: false,\n isSubmitted: false,\n isDirty: false,\n currentPage: 0,\n });\n\n // Keep a ref to current state.data to avoid stale closures in cached handlers\n const stateDataRef = useRef(state.data);\n stateDataRef.current = state.data;\n\n // Track if we've initialized (to avoid calling onChange on first render)\n const hasInitialized = useRef(false);\n\n // ── Event system ──────────────────────────────────────────────────────\n const emitterRef = useRef(new FormaEventEmitter());\n const onEventsRef = useRef(onEvents);\n onEventsRef.current = onEvents;\n const pendingEventsRef = useRef<\n Array<{ event: keyof FormaEventMap; payload: unknown }>\n >([]);\n const isFiringEventsRef = useRef(false);\n\n // Cleanup emitter on unmount\n useEffect(() => {\n const emitter = emitterRef.current;\n return () => {\n emitter.clear();\n };\n }, []);\n\n // Helper: fire an event to both declarative `on` handlers and imperative listeners\n const fireEvent = useCallback(\n <K extends keyof FormaEventMap>(\n event: K,\n payload: FormaEventMap[K],\n ) => {\n // Declarative handler (via ref for latest callback)\n try {\n const handler = onEventsRef.current?.[event];\n if (handler) (handler as (p: FormaEventMap[K]) => void)(payload);\n } catch (error) {\n console.error(`[forma] Error in \"${event}\" event handler:`, error);\n }\n // Imperative listeners\n emitterRef.current.fire(event, payload);\n },\n [],\n );\n\n // Calculate computed values\n const computed = useMemo(\n () => calculate(state.data, spec),\n [state.data, spec],\n );\n\n // Calculate visibility\n const visibility = useMemo(\n () => getVisibility(state.data, spec, { computed }),\n [state.data, spec, computed],\n );\n\n // Calculate required state\n const required = useMemo(\n () => getRequired(state.data, spec, { computed }),\n [state.data, spec, computed],\n );\n\n // Calculate enabled state\n const enabled = useMemo(\n () => getEnabled(state.data, spec, { computed }),\n [state.data, spec, computed],\n );\n\n // Calculate readonly state\n const readonly = useMemo(\n () => getReadonly(state.data, spec, { computed }),\n [state.data, spec, computed],\n );\n\n // Calculate visible options for all select/multiselect fields (memoized)\n const optionsVisibility = useMemo(\n () => getOptionsVisibility(state.data, spec, { computed }),\n [state.data, spec, computed],\n );\n\n // Validate form - compute immediate result\n const immediateValidation = useMemo(\n () => validate(state.data, spec, { computed, onlyVisible: true }),\n [state.data, spec, computed],\n );\n\n // Debounced validation state (only used when validationDebounceMs > 0)\n const [debouncedValidation, setDebouncedValidation] =\n useState<ValidationResult>(immediateValidation);\n\n // Apply debouncing if configured\n useEffect(() => {\n if (validationDebounceMs <= 0) {\n // No debouncing - use immediate validation\n setDebouncedValidation(immediateValidation);\n return;\n }\n\n // Debounce validation updates\n const timeoutId = setTimeout(() => {\n setDebouncedValidation(immediateValidation);\n }, validationDebounceMs);\n\n return () => clearTimeout(timeoutId);\n }, [immediateValidation, validationDebounceMs]);\n\n // Use debounced validation for display, but immediate for submit\n const validation =\n validationDebounceMs > 0 ? debouncedValidation : immediateValidation;\n\n // isDirty is tracked via reducer state for O(1) performance\n\n // Call onChange when data changes (not on initial render)\n useEffect(() => {\n if (hasInitialized.current) {\n onChange?.(state.data, computed);\n } else {\n hasInitialized.current = true;\n }\n }, [state.data, computed, onChange]);\n\n // Helper function to set value at nested path\n const setNestedValue = useCallback(\n (path: string, value: unknown): void => {\n // Handle array index notation: \"items[0].name\" -> nested structure\n const parts = path.replace(/\\[(\\d+)\\]/g, \".$1\").split(\".\");\n\n if (parts.length === 1) {\n // Simple path - just set directly\n dispatch({ type: \"SET_FIELD_VALUE\", field: path, value });\n return;\n }\n\n // Build nested object for complex paths\n const buildNestedObject = (\n data: Record<string, unknown>,\n pathParts: string[],\n val: unknown,\n ): Record<string, unknown> => {\n const result = { ...data };\n let current: Record<string, unknown> = result;\n\n for (let i = 0; i < pathParts.length - 1; i++) {\n const part = pathParts[i];\n const nextPart = pathParts[i + 1];\n const isNextArrayIndex = /^\\d+$/.test(nextPart);\n\n if (current[part] === undefined) {\n current[part] = isNextArrayIndex ? [] : {};\n } else if (Array.isArray(current[part])) {\n current[part] = [...(current[part] as unknown[])];\n } else {\n current[part] = { ...(current[part] as Record<string, unknown>) };\n }\n current = current[part] as Record<string, unknown>;\n }\n\n current[pathParts[pathParts.length - 1]] = val;\n return result;\n };\n\n dispatch({\n type: \"SET_VALUES\",\n values: buildNestedObject(state.data, parts, value),\n });\n },\n [state.data],\n );\n\n // Helper to get value at nested path\n // Uses stateDataRef to always access current state, avoiding stale closure issues\n const getValueAtPath = useCallback((path: string): unknown => {\n // Handle array index notation: \"items[0].name\" -> [\"items\", \"0\", \"name\"]\n const parts = path.replace(/\\[(\\d+)\\]/g, \".$1\").split(\".\");\n let value: unknown = stateDataRef.current;\n for (const part of parts) {\n if (value === null || value === undefined) return undefined;\n value = (value as Record<string, unknown>)[part];\n }\n return value;\n }, []); // No dependencies - uses ref for current state\n\n // Queue a fieldChanged event (captures previousValue from current state ref)\n const queueFieldChangedEvent = useCallback(\n (\n path: string,\n value: unknown,\n source: \"user\" | \"reset\" | \"setValues\",\n ) => {\n if (isFiringEventsRef.current) return; // recursion guard\n const previousValue = getValueAtPath(path);\n if (previousValue === value) return; // no actual change\n pendingEventsRef.current.push({\n event: \"fieldChanged\",\n payload: { path, value, previousValue, source },\n });\n },\n [getValueAtPath],\n );\n\n // Actions\n const setFieldValue = useCallback(\n (path: string, value: unknown) => {\n queueFieldChangedEvent(path, value, \"user\");\n setNestedValue(path, value);\n if (validateOn === \"change\") {\n dispatch({ type: \"SET_FIELD_TOUCHED\", field: path, touched: true });\n }\n },\n [validateOn, setNestedValue, queueFieldChangedEvent],\n );\n\n const setFieldTouched = useCallback((path: string, touched = true) => {\n dispatch({ type: \"SET_FIELD_TOUCHED\", field: path, touched });\n }, []);\n\n const setValues = useCallback(\n (values: Record<string, unknown>) => {\n for (const [key, value] of Object.entries(values)) {\n queueFieldChangedEvent(key, value, \"setValues\");\n }\n dispatch({ type: \"SET_VALUES\", values });\n },\n [queueFieldChangedEvent],\n );\n\n const validateField = useCallback(\n (path: string): FieldError[] => {\n return validation.errors.filter((e) => e.field === path);\n },\n [validation],\n );\n\n const validateForm = useCallback((): ValidationResult => {\n return validation;\n }, [validation]);\n\n const submitForm = useCallback(async () => {\n dispatch({ type: \"SET_SUBMITTING\", isSubmitting: true });\n\n const submissionData = { ...state.data };\n let postSubmitPayload: FormaEventMap[\"postSubmit\"] | undefined;\n\n try {\n // Fire preSubmit (async, inline — listeners can mutate submissionData)\n const preSubmitPayload = {\n data: submissionData,\n computed: { ...computed },\n };\n // Declarative handler\n const declarativePreSubmit = onEventsRef.current?.preSubmit;\n if (declarativePreSubmit) {\n await declarativePreSubmit(preSubmitPayload);\n }\n // Imperative listeners\n if (emitterRef.current.hasListeners(\"preSubmit\")) {\n await emitterRef.current.fireAsync(\"preSubmit\", preSubmitPayload);\n }\n\n // Always use immediate validation on submit to ensure accurate result\n if (!immediateValidation.valid) {\n postSubmitPayload = {\n data: submissionData,\n success: false,\n validationErrors: immediateValidation.errors,\n };\n } else if (onSubmit) {\n try {\n await onSubmit(submissionData);\n postSubmitPayload = { data: submissionData, success: true };\n } catch (error) {\n postSubmitPayload = {\n data: submissionData,\n success: false,\n error:\n error instanceof Error ? error : new Error(String(error)),\n };\n }\n } else {\n postSubmitPayload = { data: submissionData, success: true };\n }\n\n dispatch({ type: \"SET_SUBMITTED\", isSubmitted: true });\n } finally {\n dispatch({ type: \"SET_SUBMITTING\", isSubmitting: false });\n // Fire postSubmit after state updates\n if (postSubmitPayload) {\n fireEvent(\"postSubmit\", postSubmitPayload);\n }\n }\n }, [immediateValidation, onSubmit, state.data, computed, fireEvent]);\n\n const resetForm = useCallback(() => {\n const resetData = {\n ...getDefaultBooleanValues(spec),\n ...getFieldDefaults(spec),\n ...initialData,\n };\n\n // Queue fieldChanged for each field that actually changes\n if (!isFiringEventsRef.current) {\n const currentData = stateDataRef.current;\n const allKeys = new Set([\n ...Object.keys(currentData),\n ...Object.keys(resetData),\n ]);\n for (const key of allKeys) {\n const currentVal = currentData[key];\n const resetVal = resetData[key];\n if (currentVal !== resetVal) {\n pendingEventsRef.current.push({\n event: \"fieldChanged\",\n payload: {\n path: key,\n value: resetVal,\n previousValue: currentVal,\n source: \"reset\" as const,\n },\n });\n }\n }\n // Queue formReset (fires after fieldChanged events)\n pendingEventsRef.current.push({\n event: \"formReset\",\n payload: {} as FormaEventMap[\"formReset\"],\n });\n }\n\n dispatch({ type: \"RESET\", initialData: resetData });\n }, [spec, initialData]);\n\n // Wizard helpers\n const wizard = useMemo((): WizardHelpers | null => {\n if (!spec.pages || spec.pages.length === 0) return null;\n\n const pageVisibility = getPageVisibility(state.data, spec, { computed });\n\n // Include all pages with their visibility status\n const pages: PageState[] = spec.pages.map((p) => ({\n id: p.id,\n title: p.title,\n description: p.description,\n visible: pageVisibility[p.id] !== false,\n fields: p.fields,\n }));\n\n // For navigation, only count visible pages\n const visiblePages = pages.filter((p) => p.visible);\n\n // Clamp currentPage to valid range (handles case where current page becomes hidden)\n const maxPageIndex = Math.max(0, visiblePages.length - 1);\n const clampedPageIndex = Math.min(\n Math.max(0, state.currentPage),\n maxPageIndex,\n );\n\n // Auto-correct page index if it's out of bounds\n if (clampedPageIndex !== state.currentPage && visiblePages.length > 0) {\n dispatch({ type: \"SET_PAGE\", page: clampedPageIndex });\n }\n\n const currentPage = visiblePages[clampedPageIndex] || null;\n const hasNextPage = clampedPageIndex < visiblePages.length - 1;\n const hasPreviousPage = clampedPageIndex > 0;\n const isLastPage = clampedPageIndex === visiblePages.length - 1;\n\n return {\n pages,\n currentPageIndex: clampedPageIndex,\n currentPage,\n goToPage: (index: number) => {\n const validIndex = Math.min(Math.max(0, index), maxPageIndex);\n if (validIndex !== clampedPageIndex) {\n dispatch({ type: \"SET_PAGE\", page: validIndex });\n const newPage = visiblePages[validIndex];\n if (newPage) {\n fireEvent(\"pageChanged\", {\n fromIndex: clampedPageIndex,\n toIndex: validIndex,\n page: newPage,\n });\n }\n }\n },\n nextPage: () => {\n if (hasNextPage) {\n const toIndex = clampedPageIndex + 1;\n dispatch({ type: \"SET_PAGE\", page: toIndex });\n const newPage = visiblePages[toIndex];\n if (newPage) {\n fireEvent(\"pageChanged\", {\n fromIndex: clampedPageIndex,\n toIndex,\n page: newPage,\n });\n }\n }\n },\n previousPage: () => {\n if (hasPreviousPage) {\n const toIndex = clampedPageIndex - 1;\n dispatch({ type: \"SET_PAGE\", page: toIndex });\n const newPage = visiblePages[toIndex];\n if (newPage) {\n fireEvent(\"pageChanged\", {\n fromIndex: clampedPageIndex,\n toIndex,\n page: newPage,\n });\n }\n }\n },\n hasNextPage,\n hasPreviousPage,\n canProceed: (() => {\n if (!currentPage) return true;\n // Get errors only for visible fields on the current page\n const pageErrors = validation.errors.filter((e) => {\n // Check if field is on current page (including array items like \"items[0].name\")\n const isOnCurrentPage =\n currentPage.fields.includes(e.field) ||\n currentPage.fields.some((f) => e.field.startsWith(`${f}[`));\n // Only count errors for visible fields\n const isVisible = visibility[e.field] !== false;\n // Only count actual errors, not warnings\n const isError = e.severity === \"error\";\n return isOnCurrentPage && isVisible && isError;\n });\n return pageErrors.length === 0;\n })(),\n isLastPage,\n touchCurrentPageFields: () => {\n if (currentPage) {\n currentPage.fields.forEach((field) => {\n dispatch({ type: \"SET_FIELD_TOUCHED\", field, touched: true });\n });\n }\n },\n validateCurrentPage: () => {\n if (!currentPage) return true;\n const pageErrors = validation.errors.filter((e) =>\n currentPage.fields.includes(e.field),\n );\n return pageErrors.length === 0;\n },\n };\n }, [spec, state.data, state.currentPage, computed, validation, visibility, fireEvent]);\n\n // Flush pending events after render (fieldChanged, formReset)\n useEffect(() => {\n const events = pendingEventsRef.current;\n if (events.length === 0) return;\n pendingEventsRef.current = [];\n\n isFiringEventsRef.current = true;\n try {\n for (const pending of events) {\n fireEvent(\n pending.event as keyof FormaEventMap,\n pending.payload as FormaEventMap[keyof FormaEventMap],\n );\n }\n } finally {\n isFiringEventsRef.current = false;\n }\n });\n\n // Helper to set value at nested path\n // Uses stateDataRef to always access current state, avoiding stale closure issues\n const setValueAtPath = useCallback(\n (path: string, value: unknown): void => {\n queueFieldChangedEvent(path, value, \"user\");\n // For nested paths, we need to build the nested structure\n const parts = path.replace(/\\[(\\d+)\\]/g, \".$1\").split(\".\");\n if (parts.length === 1) {\n dispatch({ type: \"SET_FIELD_VALUE\", field: path, value });\n return;\n }\n\n // Build nested object from CURRENT state via ref (not stale closure)\n const newData = { ...stateDataRef.current };\n let current: Record<string, unknown> = newData;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n const nextPart = parts[i + 1];\n const isNextArrayIndex = /^\\d+$/.test(nextPart);\n\n if (current[part] === undefined) {\n current[part] = isNextArrayIndex ? [] : {};\n } else if (Array.isArray(current[part])) {\n current[part] = [...(current[part] as unknown[])];\n } else {\n current[part] = { ...(current[part] as Record<string, unknown>) };\n }\n current = current[part] as Record<string, unknown>;\n }\n\n current[parts[parts.length - 1]] = value;\n dispatch({ type: \"SET_VALUES\", values: newData });\n },\n [queueFieldChangedEvent],\n );\n\n // Memoized onChange/onBlur handlers for fields\n const fieldHandlers = useRef<\n Map<string, { onChange: (value: unknown) => void; onBlur: () => void }>\n >(new Map());\n\n // Clean up stale field handlers when spec changes to prevent memory leaks\n useEffect(() => {\n const validFields = new Set(spec.fieldOrder);\n // Also include array item field patterns\n for (const fieldId of spec.fieldOrder) {\n const fieldDef = spec.fields[fieldId];\n if (fieldDef?.type === \"array\" && fieldDef.itemFields) {\n for (const key of fieldHandlers.current.keys()) {\n if (key.startsWith(`${fieldId}[`)) {\n validFields.add(key);\n }\n }\n }\n }\n // Remove handlers for fields that no longer exist\n for (const key of fieldHandlers.current.keys()) {\n const baseField = key.split(\"[\")[0];\n if (!validFields.has(key) && !validFields.has(baseField)) {\n fieldHandlers.current.delete(key);\n }\n }\n }, [spec]);\n\n const getFieldHandlers = useCallback(\n (path: string) => {\n if (!fieldHandlers.current.has(path)) {\n fieldHandlers.current.set(path, {\n onChange: (value: unknown) => setValueAtPath(path, value),\n onBlur: () => setFieldTouched(path),\n });\n }\n return fieldHandlers.current.get(path)!;\n },\n [setValueAtPath, setFieldTouched],\n );\n\n // Get field props for any field\n const getFieldProps = useCallback(\n (path: string): GetFieldPropsResult => {\n const fieldDef = spec.fields[path];\n const handlers = getFieldHandlers(path);\n\n // Determine field type from definition or infer from schema\n let fieldType = fieldDef?.type || \"text\";\n if (!fieldType || fieldType === \"computed\") {\n const schemaProperty = spec.schema.properties[path];\n if (schemaProperty) {\n if (schemaProperty.type === \"number\") fieldType = \"number\";\n else if (schemaProperty.type === \"integer\") fieldType = \"integer\";\n else if (schemaProperty.type === \"boolean\") fieldType = \"boolean\";\n else if (schemaProperty.type === \"array\") fieldType = \"array\";\n else if (schemaProperty.type === \"object\") fieldType = \"object\";\n else if (\"enum\" in schemaProperty && schemaProperty.enum)\n fieldType = \"select\";\n else if (\"format\" in schemaProperty) {\n if (schemaProperty.format === \"date\") fieldType = \"date\";\n else if (schemaProperty.format === \"date-time\")\n fieldType = \"datetime\";\n else if (schemaProperty.format === \"email\") fieldType = \"email\";\n else if (schemaProperty.format === \"uri\") fieldType = \"url\";\n }\n }\n }\n\n const fieldErrors = validation.errors.filter((e) => e.field === path);\n const isTouched = state.touched[path] ?? false;\n const showErrors =\n validateOn === \"change\" ||\n (validateOn === \"blur\" && isTouched) ||\n state.isSubmitted;\n const displayedErrors = showErrors ? fieldErrors : [];\n const hasErrors = displayedErrors.length > 0;\n const isRequired = required[path] ?? false;\n\n // Boolean fields: hide asterisk unless they have validation rules (consent pattern)\n // - Binary question (\"Do you smoke?\"): no validation → false is valid → hide asterisk\n // - Consent checkbox (\"I accept terms\"): has validation rule → show asterisk\n const schemaProperty = spec.schema.properties[path];\n const isBooleanField =\n schemaProperty?.type === \"boolean\" || fieldDef?.type === \"boolean\";\n const hasValidationRules = (fieldDef?.validations?.length ?? 0) > 0;\n const showRequiredIndicator =\n isRequired && (!isBooleanField || hasValidationRules);\n\n // Pass through adorner props for adornable field types\n const adornerProps =\n fieldDef && isAdornableField(fieldDef)\n ? { prefix: fieldDef.prefix, suffix: fieldDef.suffix }\n : {};\n\n return {\n name: path,\n value: getValueAtPath(path),\n type: fieldType,\n label: fieldDef?.label || path.charAt(0).toUpperCase() + path.slice(1),\n description: fieldDef?.description,\n placeholder: fieldDef?.placeholder,\n visible: visibility[path] !== false,\n enabled: enabled[path] !== false,\n readonly: readonly[path] ?? false,\n required: isRequired,\n showRequiredIndicator,\n touched: isTouched,\n errors: displayedErrors,\n onChange: handlers.onChange,\n onBlur: handlers.onBlur,\n // ARIA accessibility attributes\n \"aria-invalid\": hasErrors || undefined,\n \"aria-describedby\": hasErrors ? `${path}-error` : undefined,\n \"aria-required\": isRequired || undefined,\n // Adorner props (only for adornable field types)\n ...adornerProps,\n // Presentation variant\n variant: fieldDef?.variant,\n variantConfig: fieldDef?.variantConfig,\n };\n },\n [\n spec,\n state.touched,\n state.isSubmitted,\n visibility,\n enabled,\n readonly,\n required,\n validation.errors,\n validateOn,\n getValueAtPath,\n getFieldHandlers,\n ],\n );\n\n // Get select field props - uses pre-computed optionsVisibility map\n const getSelectFieldProps = useCallback(\n (path: string): GetSelectFieldPropsResult => {\n const baseProps = getFieldProps(path);\n\n // Look up pre-computed visible options from memoized map\n const visibleOptions = optionsVisibility[path] ?? [];\n\n return {\n ...baseProps,\n options: visibleOptions as SelectOption[],\n };\n },\n [getFieldProps, optionsVisibility],\n );\n\n // Get array helpers\n const getArrayHelpers = useCallback(\n (path: string): GetArrayHelpersResult => {\n const fieldDef = spec.fields[path];\n const currentValue = (getValueAtPath(path) as unknown[]) ?? [];\n const arrayDef = fieldDef?.type === \"array\" ? fieldDef : undefined;\n const minItems = arrayDef?.minItems ?? 0;\n const maxItems = arrayDef?.maxItems ?? Infinity;\n\n const canAdd = currentValue.length < maxItems;\n const canRemove = currentValue.length > minItems;\n\n const getItemFieldProps = (\n index: number,\n fieldName: string,\n ): GetFieldPropsResult => {\n const itemPath = `${path}[${index}].${fieldName}`;\n const itemFieldDef = arrayDef?.itemFields?.[fieldName];\n const handlers = getFieldHandlers(itemPath);\n\n // Get item value\n const item = (currentValue[index] as Record<string, unknown>) ?? {};\n const itemValue = item[fieldName];\n\n const fieldErrors = validation.errors.filter(\n (e) => e.field === itemPath,\n );\n const isTouched = state.touched[itemPath] ?? false;\n const showErrors =\n validateOn === \"change\" ||\n (validateOn === \"blur\" && isTouched) ||\n state.isSubmitted;\n\n // Look up pre-computed visible options from memoized map\n const visibleOptions = optionsVisibility[itemPath] as\n | SelectOption[]\n | undefined;\n\n return {\n name: itemPath,\n value: itemValue,\n type: itemFieldDef?.type || \"text\",\n label:\n itemFieldDef?.label ||\n fieldName.charAt(0).toUpperCase() + fieldName.slice(1),\n description: itemFieldDef?.description,\n placeholder: itemFieldDef?.placeholder,\n visible: true,\n enabled: enabled[path] !== false,\n readonly: readonly[itemPath] ?? false,\n required: false, // TODO: Evaluate item field required\n showRequiredIndicator: false, // Item fields don't show required indicator\n touched: isTouched,\n errors: showErrors ? fieldErrors : [],\n onChange: handlers.onChange,\n onBlur: handlers.onBlur,\n options: visibleOptions,\n };\n };\n\n return {\n items: currentValue,\n push: (item: unknown) => {\n if (canAdd) {\n setValueAtPath(path, [...currentValue, item]);\n }\n },\n remove: (index: number) => {\n if (canRemove) {\n const newArray = [...currentValue];\n newArray.splice(index, 1);\n setValueAtPath(path, newArray);\n }\n },\n move: (from: number, to: number) => {\n const newArray = [...currentValue];\n const [item] = newArray.splice(from, 1);\n newArray.splice(to, 0, item);\n setValueAtPath(path, newArray);\n },\n swap: (indexA: number, indexB: number) => {\n const newArray = [...currentValue];\n [newArray[indexA], newArray[indexB]] = [\n newArray[indexB],\n newArray[indexA],\n ];\n setValueAtPath(path, newArray);\n },\n insert: (index: number, item: unknown) => {\n if (canAdd) {\n const newArray = [...currentValue];\n newArray.splice(index, 0, item);\n setValueAtPath(path, newArray);\n }\n },\n getItemFieldProps,\n minItems,\n maxItems,\n canAdd,\n canRemove,\n };\n },\n [\n spec.fields,\n getValueAtPath,\n setValueAtPath,\n getFieldHandlers,\n enabled,\n readonly,\n state.touched,\n state.isSubmitted,\n validation.errors,\n validateOn,\n optionsVisibility,\n ],\n );\n\n // Stable reference for imperative event subscription — only depends on the\n // emitter ref, so consumers can safely use it as a useEffect dependency.\n const on = useCallback(\n <K extends keyof FormaEventMap>(\n event: K,\n listener: (payload: FormaEventMap[K]) => void | Promise<void>,\n ) => emitterRef.current.on(event, listener),\n [],\n );\n\n return useMemo(\n (): UseFormaReturn => ({\n data: state.data,\n computed,\n visibility,\n required,\n enabled,\n readonly,\n optionsVisibility,\n touched: state.touched,\n errors: validation.errors,\n isValid: validation.valid,\n isSubmitting: state.isSubmitting,\n isSubmitted: state.isSubmitted,\n isDirty: state.isDirty,\n spec,\n wizard,\n setFieldValue,\n setFieldTouched,\n setValues,\n validateField,\n validateForm,\n submitForm,\n resetForm,\n on,\n getFieldProps,\n getSelectFieldProps,\n getArrayHelpers,\n }),\n [\n state.data,\n state.touched,\n state.isSubmitting,\n state.isSubmitted,\n state.isDirty,\n computed,\n visibility,\n required,\n enabled,\n readonly,\n optionsVisibility,\n validation.errors,\n validation.valid,\n spec,\n wizard,\n setFieldValue,\n setFieldTouched,\n setValues,\n validateField,\n validateForm,\n submitForm,\n resetForm,\n on,\n getFieldProps,\n getSelectFieldProps,\n getArrayHelpers,\n ],\n );\n}\n","/**\n * Event system for forma-react\n *\n * Lightweight event emitter for form lifecycle events.\n * Events are for side effects (analytics, data injection, external state sync)\n * — they do not trigger React re-renders.\n */\n\nimport type { FieldError } from \"@fogpipe/forma-core\";\nimport type { PageState } from \"./useForma.js\";\n\n// ============================================================================\n// Event Type Definitions\n// ============================================================================\n\n/**\n * Map of all forma event names to their payload types.\n */\nexport interface FormaEventMap {\n /**\n * Fires after a field value changes via user input, setFieldValue, setValues, or resetForm.\n * Does NOT fire for computed/calculated field changes or on initial mount.\n */\n fieldChanged: {\n /** Field path (e.g., \"age\" or \"medications[0].dosage\") */\n path: string;\n /** New value */\n value: unknown;\n /** Value before the change */\n previousValue: unknown;\n /** What triggered the change */\n source: \"user\" | \"reset\" | \"setValues\";\n };\n\n /**\n * Fires at the start of submitForm(), before validation.\n * The `data` object is mutable — consumers can inject extra fields.\n * Async handlers are awaited before proceeding to validation.\n */\n preSubmit: {\n /** Mutable form data — add/modify fields here */\n data: Record<string, unknown>;\n /** Read-only snapshot of computed values */\n computed: Record<string, unknown>;\n };\n\n /**\n * Fires after onSubmit resolves/rejects or after validation failure.\n */\n postSubmit: {\n /** The submitted data (reflects any preSubmit mutations) */\n data: Record<string, unknown>;\n /** Whether submission succeeded */\n success: boolean;\n /** Present when onSubmit threw an error */\n error?: Error;\n /** Present when validation failed (onSubmit was never called) */\n validationErrors?: FieldError[];\n };\n\n /**\n * Fires when the wizard page changes via nextPage, previousPage, or goToPage.\n * Does NOT fire on initial render or automatic page clamping.\n */\n pageChanged: {\n /** Previous page index */\n fromIndex: number;\n /** New page index */\n toIndex: number;\n /** The new current page */\n page: PageState;\n };\n\n /**\n * Fires after resetForm() completes and state is back to initial values.\n */\n formReset: Record<string, never>;\n}\n\n// ============================================================================\n// Helper Types\n// ============================================================================\n\n/**\n * Listener function type for a specific event.\n */\nexport type FormaEventListener<K extends keyof FormaEventMap> = (\n event: FormaEventMap[K],\n) => void | Promise<void>;\n\n/**\n * Declarative event listener map for useForma `on` option.\n */\nexport type FormaEvents = Partial<{\n [K in keyof FormaEventMap]: FormaEventListener<K>;\n}>;\n\n// ============================================================================\n// Event Emitter\n// ============================================================================\n\n/**\n * Lightweight event emitter for forma lifecycle events.\n * Uses Map<string, Set<listener>> internally — no external dependencies.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyListener = (...args: any[]) => void | Promise<void>;\n\nexport class FormaEventEmitter {\n private listeners = new Map<string, Set<AnyListener>>();\n\n /**\n * Register a listener for an event. Returns an unsubscribe function.\n */\n on<K extends keyof FormaEventMap>(\n event: K,\n listener: FormaEventListener<K>,\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n\n return () => {\n const set = this.listeners.get(event);\n if (set) {\n set.delete(listener);\n if (set.size === 0) {\n this.listeners.delete(event);\n }\n }\n };\n }\n\n /**\n * Fire an event synchronously. Listener errors are caught and logged\n * to prevent one listener from breaking others.\n */\n fire<K extends keyof FormaEventMap>(event: K, payload: FormaEventMap[K]): void {\n const set = this.listeners.get(event);\n if (!set || set.size === 0) return;\n\n for (const listener of set) {\n try {\n listener(payload);\n } catch (error) {\n console.error(`[forma] Error in \"${event}\" event listener:`, error);\n }\n }\n }\n\n /**\n * Fire an event and await all async listeners sequentially.\n * Used for preSubmit where handlers can be async.\n */\n async fireAsync<K extends keyof FormaEventMap>(\n event: K,\n payload: FormaEventMap[K],\n ): Promise<void> {\n const set = this.listeners.get(event);\n if (!set || set.size === 0) return;\n\n for (const listener of set) {\n try {\n await listener(payload);\n } catch (error) {\n console.error(`[forma] Error in \"${event}\" event listener:`, error);\n }\n }\n }\n\n /**\n * Check if any listeners are registered for an event.\n */\n hasListeners(event: keyof FormaEventMap): boolean {\n const set = this.listeners.get(event);\n return set !== undefined && set.size > 0;\n }\n\n /**\n * Remove all listeners. Called on cleanup.\n */\n clear(): void {\n this.listeners.clear();\n }\n}\n","/**\n * FormRenderer Component\n *\n * Renders a complete form from a Forma specification.\n * Supports single-page and multi-page (wizard) forms.\n */\n\nimport React, {\n forwardRef,\n useImperativeHandle,\n useRef,\n useMemo,\n useCallback,\n} from \"react\";\nimport type {\n Forma,\n FieldDefinition,\n ValidationResult,\n JSONSchemaProperty,\n SelectOption,\n} from \"@fogpipe/forma-core\";\nimport { isAdornableField, isSelectionField } from \"@fogpipe/forma-core\";\nimport { useForma } from \"./useForma.js\";\nimport { FormaContext } from \"./context.js\";\nimport type {\n ComponentMap,\n LayoutProps,\n FieldWrapperProps,\n PageWrapperProps,\n BaseFieldProps,\n TextFieldProps,\n NumberFieldProps,\n SelectFieldProps,\n ArrayFieldProps,\n ArrayHelpers,\n DisplayFieldProps,\n} from \"./types.js\";\n\n/**\n * Props for FormRenderer component\n */\nexport interface FormRendererProps {\n /** The Forma specification */\n spec: Forma;\n /** Initial form data */\n initialData?: Record<string, unknown>;\n /** Submit handler */\n onSubmit?: (data: Record<string, unknown>) => void | Promise<void>;\n /** Change handler */\n onChange?: (\n data: Record<string, unknown>,\n computed?: Record<string, unknown>,\n ) => void;\n /** Component map for rendering fields */\n components: ComponentMap;\n /** Custom layout component */\n layout?: React.ComponentType<LayoutProps>;\n /** Custom field wrapper component */\n fieldWrapper?: React.ComponentType<FieldWrapperProps>;\n /** Custom page wrapper component */\n pageWrapper?: React.ComponentType<PageWrapperProps>;\n /** When to validate */\n validateOn?: \"change\" | \"blur\" | \"submit\";\n /** Current page for controlled wizard */\n page?: number;\n}\n\n/**\n * Imperative handle for FormRenderer\n */\nexport interface FormRendererHandle {\n submitForm: () => Promise<void>;\n resetForm: () => void;\n validateForm: () => ValidationResult;\n focusField: (path: string) => void;\n focusFirstError: () => void;\n getValues: () => Record<string, unknown>;\n setValues: (values: Record<string, unknown>) => void;\n isValid: boolean;\n isDirty: boolean;\n}\n\n/**\n * Default layout component\n */\nfunction DefaultLayout({ children, onSubmit, isSubmitting }: LayoutProps) {\n return (\n <form\n onSubmit={(e) => {\n e.preventDefault();\n onSubmit();\n }}\n >\n {children}\n <button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting ? \"Submitting...\" : \"Submit\"}\n </button>\n </form>\n );\n}\n\n/**\n * Default field wrapper component with accessibility support\n */\nfunction DefaultFieldWrapper({\n fieldPath,\n field,\n children,\n errors,\n showRequiredIndicator,\n visible,\n}: FieldWrapperProps) {\n if (!visible) return null;\n\n const errorId = `${fieldPath}-error`;\n const descriptionId = field.description\n ? `${fieldPath}-description`\n : undefined;\n const hasErrors = errors.length > 0;\n\n return (\n <div className=\"field-wrapper\" data-field-path={fieldPath}>\n {field.label && (\n <label htmlFor={fieldPath}>\n {field.label}\n {showRequiredIndicator && (\n <span className=\"required\" aria-hidden=\"true\">\n *\n </span>\n )}\n {showRequiredIndicator && (\n <span className=\"sr-only\"> (required)</span>\n )}\n </label>\n )}\n {children}\n {hasErrors && (\n <div\n id={errorId}\n className=\"field-errors\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n {errors.map((error, i) => (\n <span key={i} className=\"error\">\n {error.message}\n </span>\n ))}\n </div>\n )}\n {field.description && (\n <p id={descriptionId} className=\"field-description\">\n {field.description}\n </p>\n )}\n </div>\n );\n}\n\n/**\n * Default page wrapper component\n */\nfunction DefaultPageWrapper({\n title,\n description,\n children,\n}: PageWrapperProps) {\n return (\n <div className=\"page-wrapper\">\n <h2>{title}</h2>\n {description && <p>{description}</p>}\n {children}\n </div>\n );\n}\n\n/**\n * Extract numeric constraints from JSON Schema property\n */\nfunction getNumberConstraints(schema?: JSONSchemaProperty): {\n min?: number;\n max?: number;\n step?: number;\n} {\n if (!schema) return {};\n if (schema.type !== \"number\" && schema.type !== \"integer\") return {};\n\n // Extract min/max from schema\n const min =\n \"minimum\" in schema && typeof schema.minimum === \"number\"\n ? schema.minimum\n : undefined;\n const max =\n \"maximum\" in schema && typeof schema.maximum === \"number\"\n ? schema.maximum\n : undefined;\n\n // Use multipleOf for step if defined, otherwise default to 1 for integers\n let step: number | undefined;\n if (\"multipleOf\" in schema && typeof schema.multipleOf === \"number\") {\n step = schema.multipleOf;\n } else if (schema.type === \"integer\") {\n step = 1;\n }\n\n return { min, max, step };\n}\n\n/**\n * Create a default item for an array field based on item field definitions\n */\nfunction createDefaultItem(\n itemFields: Record<string, FieldDefinition>,\n): Record<string, unknown> {\n const item: Record<string, unknown> = {};\n for (const [fieldName, fieldDef] of Object.entries(itemFields)) {\n if (fieldDef.defaultValue !== undefined) {\n item[fieldName] = fieldDef.defaultValue;\n } else if (fieldDef.type === \"boolean\") {\n item[fieldName] = false;\n } else if (fieldDef.type === \"number\" || fieldDef.type === \"integer\") {\n item[fieldName] = null;\n } else {\n item[fieldName] = \"\";\n }\n }\n return item;\n}\n\n/**\n * FormRenderer component\n */\nexport const FormRenderer = forwardRef<FormRendererHandle, FormRendererProps>(\n function FormRenderer(props, ref) {\n const {\n spec,\n initialData,\n onSubmit,\n onChange,\n components,\n layout: Layout = DefaultLayout,\n fieldWrapper: FieldWrapper = DefaultFieldWrapper,\n pageWrapper: PageWrapper = DefaultPageWrapper,\n validateOn,\n } = props;\n\n const forma = useForma({\n spec,\n initialData,\n onSubmit,\n onChange,\n validateOn,\n });\n\n const fieldRefs = useRef<Map<string, HTMLElement>>(new Map());\n\n // Focus a specific field by path\n const focusField = useCallback((path: string) => {\n const element = fieldRefs.current.get(path);\n element?.focus();\n }, []);\n\n // Focus the first field with an error\n const focusFirstError = useCallback(() => {\n const firstError = forma.errors[0];\n if (firstError) {\n focusField(firstError.field);\n }\n }, [forma.errors, focusField]);\n\n // Expose imperative handle\n useImperativeHandle(\n ref,\n () => ({\n submitForm: forma.submitForm,\n resetForm: forma.resetForm,\n validateForm: forma.validateForm,\n focusField,\n focusFirstError,\n getValues: () => forma.data,\n setValues: forma.setValues,\n isValid: forma.isValid,\n isDirty: forma.isDirty,\n }),\n [forma, focusField, focusFirstError],\n );\n\n // Destructure only the values renderField needs from forma.\n // This prevents renderField from recreating when unrelated state changes\n // (isSubmitting, isDirty, wizard page navigation, etc.)\n const {\n data: formaData,\n computed: formaComputed,\n visibility: formaVisibility,\n required: formaRequired,\n enabled: formaEnabled,\n readonly: formaReadonly,\n optionsVisibility: formaOptionsVisibility,\n touched: formaTouched,\n errors: formaErrors,\n setFieldValue,\n setFieldTouched,\n getArrayHelpers,\n } = forma;\n\n // Determine which fields to render based on pages or fieldOrder\n const fieldsToRender = useMemo(() => {\n if (spec.pages && spec.pages.length > 0 && forma.wizard) {\n // Wizard mode - render fields for the active page\n const currentPage = forma.wizard.currentPage;\n if (currentPage) {\n return currentPage.fields;\n }\n // Fallback to first page\n return spec.pages[0]?.fields ?? [];\n }\n // Single page mode - render all fields in order\n return spec.fieldOrder;\n }, [spec.pages, spec.fieldOrder, forma.wizard]);\n\n // Render a single field (memoized)\n const renderField = useCallback(\n (fieldPath: string) => {\n const fieldDef = spec.fields[fieldPath];\n if (!fieldDef) return null;\n\n const isVisible = formaVisibility[fieldPath] !== false;\n if (!isVisible) {\n return <div key={fieldPath} data-field-path={fieldPath} hidden />;\n }\n\n // Get field type (type is required on all field definitions)\n const fieldType = fieldDef.type;\n const componentKey = fieldType as keyof ComponentMap;\n const Component = components[componentKey] || components.fallback;\n\n if (!Component) {\n console.warn(`No component found for field type: ${fieldType}`);\n return null;\n }\n\n const errors = formaErrors.filter((e) => e.field === fieldPath);\n const touched = formaTouched[fieldPath] ?? false;\n const required = formaRequired[fieldPath] ?? false;\n const disabled = formaEnabled[fieldPath] === false;\n\n // Get schema property for additional constraints\n const schemaProperty = spec.schema.properties[fieldPath];\n\n // Boolean fields: hide asterisk unless they have validation rules (consent pattern)\n // - Binary question (\"Do you smoke?\"): no validation → false is valid → hide asterisk\n // - Consent checkbox (\"I accept terms\"): has validation rule → show asterisk\n const isBooleanField =\n schemaProperty?.type === \"boolean\" || fieldDef?.type === \"boolean\";\n const hasValidationRules = (fieldDef?.validations?.length ?? 0) > 0;\n const showRequiredIndicator =\n required && (!isBooleanField || hasValidationRules);\n\n // Base field props\n const isReadonly = formaReadonly[fieldPath] ?? false;\n const baseProps: BaseFieldProps = {\n name: fieldPath,\n field: fieldDef,\n value: formaData[fieldPath],\n touched,\n required,\n disabled,\n errors,\n onChange: (value: unknown) => setFieldValue(fieldPath, value),\n onBlur: () => setFieldTouched(fieldPath),\n // Convenience properties\n visible: true, // Always true since we already filtered for visibility\n enabled: !disabled,\n readonly: isReadonly,\n label: fieldDef.label ?? fieldPath,\n description: fieldDef.description,\n placeholder: fieldDef.placeholder,\n // Adorner properties (only for adornable field types)\n ...(isAdornableField(fieldDef) && {\n prefix: fieldDef.prefix,\n suffix: fieldDef.suffix,\n }),\n // Presentation variant\n variant: fieldDef.variant,\n variantConfig: fieldDef.variantConfig,\n };\n\n // Build type-specific props\n let fieldProps:\n | BaseFieldProps\n | TextFieldProps\n | NumberFieldProps\n | SelectFieldProps\n | ArrayFieldProps\n | DisplayFieldProps = baseProps;\n\n if (fieldType === \"number\" || fieldType === \"integer\") {\n const constraints = getNumberConstraints(schemaProperty);\n fieldProps = {\n ...baseProps,\n fieldType,\n value: baseProps.value as number | null,\n onChange: baseProps.onChange as (value: number | null) => void,\n ...constraints,\n } as NumberFieldProps;\n } else if (fieldType === \"select\" || fieldType === \"multiselect\") {\n const selectOptions = isSelectionField(fieldDef)\n ? fieldDef.options\n : [];\n fieldProps = {\n ...baseProps,\n fieldType,\n value: baseProps.value as string | string[] | null,\n onChange: baseProps.onChange as (\n value: string | string[] | null,\n ) => void,\n options: formaOptionsVisibility[fieldPath] ?? selectOptions ?? [],\n } as SelectFieldProps;\n } else if (\n fieldType === \"array\" &&\n fieldDef.type === \"array\" &&\n fieldDef.itemFields\n ) {\n const arrayValue = Array.isArray(baseProps.value)\n ? baseProps.value\n : [];\n const minItems = fieldDef.minItems ?? 0;\n const maxItems = fieldDef.maxItems ?? Infinity;\n const itemFieldDefs = fieldDef.itemFields;\n\n // Get helpers from useForma - these are fresh on each render, avoiding stale closures\n const baseHelpers = getArrayHelpers(fieldPath);\n\n // Wrap push to add default item creation when called without arguments\n const pushWithDefault = (item?: unknown): void => {\n const newItem = item ?? createDefaultItem(itemFieldDefs);\n baseHelpers.push(newItem);\n };\n\n // Extend getItemFieldProps to include additional metadata (itemIndex, fieldName, options)\n const getItemFieldPropsExtended = (\n index: number,\n fieldName: string,\n ) => {\n const baseProps = baseHelpers.getItemFieldProps(index, fieldName);\n const itemFieldDef = itemFieldDefs[fieldName];\n const itemPath = `${fieldPath}[${index}].${fieldName}`;\n return {\n ...baseProps,\n itemIndex: index,\n fieldName,\n options:\n (formaOptionsVisibility[itemPath] as\n | SelectOption[]\n | undefined) ??\n (itemFieldDef && isSelectionField(itemFieldDef)\n ? itemFieldDef.options\n : undefined),\n };\n };\n\n const helpers: ArrayHelpers = {\n items: arrayValue,\n push: pushWithDefault,\n insert: baseHelpers.insert,\n remove: baseHelpers.remove,\n move: baseHelpers.move,\n swap: baseHelpers.swap,\n getItemFieldProps: getItemFieldPropsExtended,\n minItems,\n maxItems,\n canAdd: arrayValue.length < maxItems,\n canRemove: arrayValue.length > minItems,\n };\n fieldProps = {\n ...baseProps,\n fieldType: \"array\",\n value: arrayValue,\n onChange: baseProps.onChange as (value: unknown[]) => void,\n helpers,\n itemFields: itemFieldDefs,\n itemFieldOrder: fieldDef.itemFieldOrder,\n minItems,\n maxItems,\n } as ArrayFieldProps;\n } else if (fieldType === \"display\" && fieldDef.type === \"display\") {\n // Display fields (read-only presentation content)\n // Resolve source value if the display field has a source property\n const sourceValue = fieldDef.source\n ? (formaData[fieldDef.source] ?? formaComputed[fieldDef.source])\n : undefined;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n onChange: _onChange,\n value: _value,\n ...displayBaseProps\n } = baseProps;\n fieldProps = {\n ...displayBaseProps,\n fieldType: \"display\",\n content: fieldDef.content,\n sourceValue,\n format: fieldDef.format,\n } as DisplayFieldProps;\n } else {\n // Text-based fields\n fieldProps = {\n ...baseProps,\n fieldType: fieldType as\n | \"text\"\n | \"email\"\n | \"password\"\n | \"url\"\n | \"textarea\",\n value: (baseProps.value as string) ?? \"\",\n onChange: baseProps.onChange as (value: string) => void,\n };\n }\n\n // Wrap props in { field, spec } structure for components\n const componentProps = { field: fieldProps, spec };\n\n return (\n <div key={fieldPath} data-field-path={fieldPath}>\n <FieldWrapper\n fieldPath={fieldPath}\n field={fieldDef}\n errors={errors}\n touched={touched}\n required={required}\n showRequiredIndicator={showRequiredIndicator}\n visible={isVisible}\n >\n {React.createElement(\n Component as React.ComponentType<typeof componentProps>,\n componentProps,\n )}\n </FieldWrapper>\n </div>\n );\n },\n [\n spec,\n components,\n FieldWrapper,\n formaData,\n formaComputed,\n formaVisibility,\n formaRequired,\n formaEnabled,\n formaReadonly,\n formaOptionsVisibility,\n formaTouched,\n formaErrors,\n setFieldValue,\n setFieldTouched,\n getArrayHelpers,\n ],\n );\n\n // Render fields (memoized)\n const renderedFields = useMemo(\n () => fieldsToRender.map(renderField),\n [fieldsToRender, renderField],\n );\n\n // Render with page wrapper if using pages\n const content = useMemo(() => {\n if (spec.pages && spec.pages.length > 0 && forma.wizard) {\n const currentPage = forma.wizard.currentPage;\n if (!currentPage) return null;\n\n return (\n <PageWrapper\n title={currentPage.title}\n description={currentPage.description}\n pageIndex={forma.wizard.currentPageIndex}\n totalPages={forma.wizard.pages.length}\n >\n {renderedFields}\n </PageWrapper>\n );\n }\n\n return <>{renderedFields}</>;\n }, [spec.pages, forma.wizard, PageWrapper, renderedFields]);\n\n return (\n <FormaContext.Provider value={forma}>\n <Layout\n onSubmit={forma.submitForm}\n isSubmitting={forma.isSubmitting}\n isValid={forma.isValid}\n >\n {content}\n </Layout>\n </FormaContext.Provider>\n );\n },\n);\n","/**\n * React Context for Forma\n */\n\nimport { createContext, useContext } from \"react\";\nimport type { UseFormaReturn } from \"./useForma.js\";\n\n/**\n * Context for sharing form state across components\n */\nexport const FormaContext = createContext<UseFormaReturn | null>(null);\n\n/**\n * Hook to access Forma context\n * @throws Error if used outside of FormaContext.Provider\n */\nexport function useFormaContext(): UseFormaReturn {\n const context = useContext(FormaContext);\n if (!context) {\n throw new Error(\n \"useFormaContext must be used within a FormaContext.Provider\",\n );\n }\n return context;\n}\n","/**\n * FieldRenderer Component\n *\n * Routes a single field to the appropriate component based on its type.\n * This is useful for custom form layouts where you need field-by-field control.\n */\n\nimport React from \"react\";\nimport type {\n FieldDefinition,\n JSONSchemaProperty,\n SelectOption,\n} from \"@fogpipe/forma-core\";\nimport { isAdornableField } from \"@fogpipe/forma-core\";\nimport { useFormaContext } from \"./context.js\";\nimport type {\n ComponentMap,\n BaseFieldProps,\n TextFieldProps,\n NumberFieldProps,\n IntegerFieldProps,\n SelectFieldProps,\n MultiSelectFieldProps,\n ArrayFieldProps,\n ArrayHelpers,\n DisplayFieldProps,\n} from \"./types.js\";\n\n/**\n * Props for FieldRenderer component\n */\nexport interface FieldRendererProps {\n /** Field path (e.g., \"firstName\" or \"address.city\") */\n fieldPath: string;\n /** Component map for rendering fields */\n components: ComponentMap;\n /** Optional class name for the wrapper */\n className?: string;\n}\n\n/**\n * Extract numeric constraints from JSON Schema property\n */\nfunction getNumberConstraints(schema?: JSONSchemaProperty): {\n min?: number;\n max?: number;\n step?: number;\n} {\n if (!schema) return {};\n if (schema.type !== \"number\" && schema.type !== \"integer\") return {};\n\n // Extract min/max from schema\n const min =\n \"minimum\" in schema && typeof schema.minimum === \"number\"\n ? schema.minimum\n : undefined;\n const max =\n \"maximum\" in schema && typeof schema.maximum === \"number\"\n ? schema.maximum\n : undefined;\n\n // Use multipleOf for step if defined, otherwise default to 1 for integers\n let step: number | undefined;\n if (\"multipleOf\" in schema && typeof schema.multipleOf === \"number\") {\n step = schema.multipleOf;\n } else if (schema.type === \"integer\") {\n step = 1;\n }\n\n return { min, max, step };\n}\n\n/**\n * Create a default item for an array field based on item field definitions\n */\nfunction createDefaultItem(\n itemFields: Record<string, FieldDefinition>,\n): Record<string, unknown> {\n const item: Record<string, unknown> = {};\n for (const [fieldName, fieldDef] of Object.entries(itemFields)) {\n if (fieldDef.defaultValue !== undefined) {\n item[fieldName] = fieldDef.defaultValue;\n } else if (fieldDef.type === \"boolean\") {\n item[fieldName] = false;\n } else if (fieldDef.type === \"number\" || fieldDef.type === \"integer\") {\n item[fieldName] = null;\n } else {\n item[fieldName] = \"\";\n }\n }\n return item;\n}\n\n/**\n * FieldRenderer component\n *\n * @example\n * ```tsx\n * // Render a specific field with custom components\n * <FieldRenderer fieldPath=\"email\" components={componentMap} />\n * ```\n */\nexport function FieldRenderer({\n fieldPath,\n components,\n className,\n}: FieldRendererProps) {\n const forma = useFormaContext();\n const { spec } = forma;\n\n const fieldDef = spec.fields[fieldPath];\n if (!fieldDef) {\n console.warn(`Field not found: ${fieldPath}`);\n return null;\n }\n\n const isVisible = forma.visibility[fieldPath] !== false;\n if (!isVisible) {\n return <div data-field-path={fieldPath} hidden />;\n }\n\n // Get field type (type is required on all field definitions)\n const fieldType = fieldDef.type;\n const componentKey = fieldType as keyof ComponentMap;\n const Component = components[componentKey] || components.fallback;\n\n if (!Component) {\n console.warn(`No component found for field type: ${fieldType}`);\n return null;\n }\n\n const errors = forma.errors.filter((e) => e.field === fieldPath);\n const touched = forma.touched[fieldPath] ?? false;\n const required = forma.required[fieldPath] ?? false;\n const disabled = forma.enabled[fieldPath] === false;\n\n // Get schema property for additional constraints\n const schemaProperty = spec.schema.properties[fieldPath];\n\n // Base field props\n const isReadonly = forma.readonly[fieldPath] ?? false;\n const baseProps: BaseFieldProps = {\n name: fieldPath,\n field: fieldDef,\n value: forma.data[fieldPath],\n touched,\n required,\n disabled,\n errors,\n onChange: (value: unknown) => forma.setFieldValue(fieldPath, value),\n onBlur: () => forma.setFieldTouched(fieldPath),\n // Convenience properties\n visible: true, // Always true since we already filtered for visibility\n enabled: !disabled,\n readonly: isReadonly,\n label: fieldDef.label ?? fieldPath,\n description: fieldDef.description,\n placeholder: fieldDef.placeholder,\n // Adorner properties (only for adornable field types)\n ...(isAdornableField(fieldDef) && {\n prefix: fieldDef.prefix,\n suffix: fieldDef.suffix,\n }),\n // Presentation variant\n variant: fieldDef.variant,\n variantConfig: fieldDef.variantConfig,\n };\n\n // Build type-specific props\n let fieldProps:\n | BaseFieldProps\n | TextFieldProps\n | NumberFieldProps\n | IntegerFieldProps\n | SelectFieldProps\n | MultiSelectFieldProps\n | ArrayFieldProps\n | DisplayFieldProps = baseProps;\n\n if (fieldType === \"number\") {\n const constraints = getNumberConstraints(schemaProperty);\n fieldProps = {\n ...baseProps,\n fieldType: \"number\",\n value: baseProps.value as number | null,\n onChange: baseProps.onChange as (value: number | null) => void,\n ...constraints,\n } as NumberFieldProps;\n } else if (fieldType === \"integer\") {\n const constraints = getNumberConstraints(schemaProperty);\n fieldProps = {\n ...baseProps,\n fieldType: \"integer\",\n value: baseProps.value as number | null,\n onChange: baseProps.onChange as (value: number | null) => void,\n min: constraints.min,\n max: constraints.max,\n } as IntegerFieldProps;\n } else if (fieldType === \"select\") {\n // Use pre-computed visible options from memoized map\n const visibleOptions = (forma.optionsVisibility[fieldPath] ??\n []) as SelectOption[];\n fieldProps = {\n ...baseProps,\n fieldType: \"select\",\n value: baseProps.value as string | null,\n onChange: baseProps.onChange as (value: string | null) => void,\n options: visibleOptions,\n } as SelectFieldProps;\n } else if (fieldType === \"multiselect\") {\n // Use pre-computed visible options from memoized map\n const visibleOptions = (forma.optionsVisibility[fieldPath] ??\n []) as SelectOption[];\n fieldProps = {\n ...baseProps,\n fieldType: \"multiselect\",\n value: (baseProps.value as string[] | undefined) ?? [],\n onChange: baseProps.onChange as (value: string[]) => void,\n options: visibleOptions,\n } as MultiSelectFieldProps;\n } else if (\n fieldType === \"array\" &&\n fieldDef.type === \"array\" &&\n fieldDef.itemFields\n ) {\n const arrayValue = (baseProps.value as unknown[] | undefined) ?? [];\n const minItems = fieldDef.minItems ?? 0;\n const maxItems = fieldDef.maxItems ?? Infinity;\n const itemFieldDefs = fieldDef.itemFields;\n\n const helpers: ArrayHelpers = {\n items: arrayValue,\n push: (item?: unknown) => {\n const newItem = item ?? createDefaultItem(itemFieldDefs);\n forma.setFieldValue(fieldPath, [...arrayValue, newItem]);\n },\n insert: (index: number, item: unknown) => {\n const newArray = [...arrayValue];\n newArray.splice(index, 0, item);\n forma.setFieldValue(fieldPath, newArray);\n },\n remove: (index: number) => {\n const newArray = [...arrayValue];\n newArray.splice(index, 1);\n forma.setFieldValue(fieldPath, newArray);\n },\n move: (from: number, to: number) => {\n const newArray = [...arrayValue];\n const [item] = newArray.splice(from, 1);\n newArray.splice(to, 0, item);\n forma.setFieldValue(fieldPath, newArray);\n },\n swap: (indexA: number, indexB: number) => {\n const newArray = [...arrayValue];\n [newArray[indexA], newArray[indexB]] = [\n newArray[indexB],\n newArray[indexA],\n ];\n forma.setFieldValue(fieldPath, newArray);\n },\n getItemFieldProps: (index: number, fieldName: string) => {\n const itemFieldDef = itemFieldDefs[fieldName];\n const itemPath = `${fieldPath}[${index}].${fieldName}`;\n const item = (arrayValue[index] as Record<string, unknown>) ?? {};\n const itemValue = item[fieldName];\n\n // Use pre-computed visible options from memoized map\n const visibleOptions = forma.optionsVisibility[itemPath] as\n | SelectOption[]\n | undefined;\n\n return {\n name: itemPath,\n value: itemValue,\n type: itemFieldDef?.type ?? \"text\",\n label: itemFieldDef?.label ?? fieldName,\n description: itemFieldDef?.description,\n placeholder: itemFieldDef?.placeholder,\n visible: true,\n enabled: !disabled,\n readonly: forma.readonly[itemPath] ?? false,\n required: itemFieldDef?.requiredWhen === \"true\",\n touched: forma.touched[itemPath] ?? false,\n errors: forma.errors.filter((e) => e.field === itemPath),\n onChange: (value: unknown) => {\n const newArray = [...arrayValue];\n const existingItem = (newArray[index] ?? {}) as Record<\n string,\n unknown\n >;\n newArray[index] = { ...existingItem, [fieldName]: value };\n forma.setFieldValue(fieldPath, newArray);\n },\n onBlur: () => forma.setFieldTouched(itemPath),\n itemIndex: index,\n fieldName,\n options: visibleOptions,\n };\n },\n minItems,\n maxItems,\n canAdd: arrayValue.length < maxItems,\n canRemove: arrayValue.length > minItems,\n };\n fieldProps = {\n ...baseProps,\n fieldType: \"array\",\n value: arrayValue,\n onChange: baseProps.onChange as (value: unknown[]) => void,\n helpers,\n itemFields: itemFieldDefs,\n itemFieldOrder: fieldDef.itemFieldOrder,\n minItems,\n maxItems,\n } as ArrayFieldProps;\n } else if (fieldType === \"display\" && fieldDef.type === \"display\") {\n // Display fields (read-only presentation content)\n const sourceValue = fieldDef.source\n ? (forma.data[fieldDef.source] ?? forma.computed[fieldDef.source])\n : undefined;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n onChange: _onChange,\n value: _value,\n ...displayBaseProps\n } = baseProps;\n fieldProps = {\n ...displayBaseProps,\n fieldType: \"display\",\n content: fieldDef.content,\n sourceValue,\n format: fieldDef.format,\n } as DisplayFieldProps;\n } else {\n // Text-based fields\n fieldProps = {\n ...baseProps,\n fieldType: fieldType as\n | \"text\"\n | \"email\"\n | \"password\"\n | \"url\"\n | \"textarea\",\n value: (baseProps.value as string) ?? \"\",\n onChange: baseProps.onChange as (value: string) => void,\n };\n }\n\n // Wrap props in { field, spec } structure for components\n const componentProps = { field: fieldProps, spec };\n const element = React.createElement(\n Component as React.ComponentType<typeof componentProps>,\n componentProps,\n );\n\n if (className) {\n return (\n <div data-field-path={fieldPath} className={className}>\n {element}\n </div>\n );\n }\n\n return <div data-field-path={fieldPath}>{element}</div>;\n}\n","/**\n * FormaErrorBoundary Component\n *\n * Error boundary for catching render errors in form components.\n * Provides graceful error handling and recovery options.\n */\n\nimport React from \"react\";\n\n/**\n * Props for FormaErrorBoundary component\n */\nexport interface FormaErrorBoundaryProps {\n /** Child components to render */\n children: React.ReactNode;\n /** Custom fallback UI to show when an error occurs */\n fallback?:\n | React.ReactNode\n | ((error: Error, reset: () => void) => React.ReactNode);\n /** Callback when an error is caught */\n onError?: (error: Error, errorInfo: React.ErrorInfo) => void;\n /** Key to reset the error boundary (change this to reset) */\n resetKey?: string | number;\n}\n\n/**\n * State for FormaErrorBoundary component\n */\ninterface FormaErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\n/**\n * Default fallback component shown when an error occurs\n */\nfunction DefaultErrorFallback({\n error,\n onReset,\n}: {\n error: Error;\n onReset: () => void;\n}) {\n return (\n <div className=\"forma-error-boundary\" role=\"alert\">\n <h3>Something went wrong</h3>\n <p>An error occurred while rendering the form.</p>\n <details>\n <summary>Error details</summary>\n <pre>{error.message}</pre>\n </details>\n <button type=\"button\" onClick={onReset}>\n Try again\n </button>\n </div>\n );\n}\n\n/**\n * Error boundary component for Forma forms\n *\n * Catches JavaScript errors in child component tree and displays\n * a fallback UI instead of crashing the entire application.\n *\n * @example\n * ```tsx\n * <FormaErrorBoundary\n * fallback={<div>Form error occurred</div>}\n * onError={(error) => logError(error)}\n * >\n * <FormRenderer spec={spec} components={components} />\n * </FormaErrorBoundary>\n * ```\n */\nexport class FormaErrorBoundary extends React.Component<\n FormaErrorBoundaryProps,\n FormaErrorBoundaryState\n> {\n constructor(props: FormaErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): FormaErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {\n this.props.onError?.(error, errorInfo);\n }\n\n componentDidUpdate(prevProps: FormaErrorBoundaryProps): void {\n // Reset error state when resetKey changes\n if (this.state.hasError && prevProps.resetKey !== this.props.resetKey) {\n this.setState({ hasError: false, error: null });\n }\n }\n\n reset = (): void => {\n this.setState({ hasError: false, error: null });\n };\n\n render(): React.ReactNode {\n if (this.state.hasError && this.state.error) {\n const { fallback } = this.props;\n\n if (typeof fallback === \"function\") {\n return fallback(this.state.error, this.reset);\n }\n\n if (fallback) {\n return fallback;\n }\n\n return (\n <DefaultErrorFallback error={this.state.error} onReset={this.reset} />\n );\n }\n\n return this.props.children;\n }\n}\n"],"mappings":"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","names":["schemaProperty","useRef","useMemo","useCallback","isAdornableField","FormRenderer","useRef","useCallback","useMemo","isAdornableField","baseProps","React","isAdornableField","jsx","getNumberConstraints","createDefaultItem","isAdornableField","React","React","jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../src/useForma.ts","../src/events.ts","../src/FormRenderer.tsx","../src/context.ts","../src/FieldRenderer.tsx","../src/ErrorBoundary.tsx"],"sourcesContent":["/**\n * useForma Hook\n *\n * Main hook for managing Forma form state.\n * This is a placeholder - the full implementation will be migrated from formidable.\n */\n\nimport {\n useCallback,\n useEffect,\n useMemo,\n useReducer,\n useRef,\n useState,\n} from \"react\";\nimport type {\n Forma,\n FieldError,\n ValidationResult,\n SelectOption,\n} from \"@fogpipe/forma-core\";\nimport { isAdornableField } from \"@fogpipe/forma-core\";\nimport type {\n GetFieldPropsResult,\n GetSelectFieldPropsResult,\n GetArrayHelpersResult,\n} from \"./types.js\";\nimport { FormaEventEmitter } from \"./events.js\";\nimport type { FormaEventMap, FormaEvents } from \"./events.js\";\nimport {\n getVisibility,\n getRequired,\n getEnabled,\n getReadonly,\n validate,\n calculate,\n getPageVisibility,\n getOptionsVisibility,\n} from \"@fogpipe/forma-core\";\nimport type { OptionsVisibilityResult } from \"@fogpipe/forma-core\";\n\n/**\n * Options for useForma hook\n */\nexport interface UseFormaOptions {\n /** The Forma specification */\n spec: Forma;\n /** Initial form data */\n initialData?: Record<string, unknown>;\n /** Submit handler */\n onSubmit?: (data: Record<string, unknown>) => void | Promise<void>;\n /** Change handler */\n onChange?: (\n data: Record<string, unknown>,\n computed?: Record<string, unknown>,\n ) => void;\n /** When to validate: on change, blur, or submit only */\n validateOn?: \"change\" | \"blur\" | \"submit\";\n /** Additional reference data to merge with spec.referenceData */\n referenceData?: Record<string, unknown>;\n /**\n * Debounce validation by this many milliseconds.\n * Useful for large forms to improve performance.\n * Set to 0 (default) for immediate validation.\n */\n validationDebounceMs?: number;\n /**\n * Declarative event listeners for form lifecycle events.\n * Listeners are stable for the lifetime of the hook — the latest\n * callback is always invoked via refs, without causing dependency changes.\n */\n on?: FormaEvents;\n}\n\n/**\n * Form state\n */\ninterface FormState {\n data: Record<string, unknown>;\n touched: Record<string, boolean>;\n isSubmitting: boolean;\n isSubmitted: boolean;\n isDirty: boolean;\n currentPage: number;\n}\n\n/**\n * State actions\n */\ntype FormAction =\n | { type: \"SET_FIELD_VALUE\"; field: string; value: unknown }\n | { type: \"SET_FIELD_TOUCHED\"; field: string; touched: boolean }\n | { type: \"SET_VALUES\"; values: Record<string, unknown> }\n | { type: \"SET_SUBMITTING\"; isSubmitting: boolean }\n | { type: \"SET_SUBMITTED\"; isSubmitted: boolean }\n | { type: \"SET_PAGE\"; page: number }\n | { type: \"RESET\"; initialData: Record<string, unknown> };\n\n/**\n * Page state for multi-page forms\n */\nexport interface PageState {\n id: string;\n title: string;\n description?: string;\n visible: boolean;\n fields: string[];\n}\n\n/**\n * Wizard navigation helpers\n */\nexport interface WizardHelpers {\n pages: PageState[];\n currentPageIndex: number;\n currentPage: PageState | null;\n goToPage: (index: number) => void;\n nextPage: () => void;\n previousPage: () => void;\n hasNextPage: boolean;\n hasPreviousPage: boolean;\n canProceed: boolean;\n isLastPage: boolean;\n touchCurrentPageFields: () => void;\n validateCurrentPage: () => boolean;\n}\n\n/**\n * Return type of useForma hook\n */\nexport interface UseFormaReturn {\n /** Current form data */\n data: Record<string, unknown>;\n /** Computed field values */\n computed: Record<string, unknown>;\n /** Field visibility map */\n visibility: Record<string, boolean>;\n /** Field required state map */\n required: Record<string, boolean>;\n /** Field enabled state map */\n enabled: Record<string, boolean>;\n /** Field readonly state map */\n readonly: Record<string, boolean>;\n /** Visible options for select/multiselect fields, keyed by field path */\n optionsVisibility: OptionsVisibilityResult;\n /** Field touched state map */\n touched: Record<string, boolean>;\n /** Validation errors */\n errors: FieldError[];\n /** Whether form is valid */\n isValid: boolean;\n /** Whether form is submitting */\n isSubmitting: boolean;\n /** Whether form has been submitted */\n isSubmitted: boolean;\n /** Whether any field has been modified */\n isDirty: boolean;\n /** The Forma spec */\n spec: Forma;\n /** Wizard helpers (if multi-page) */\n wizard: WizardHelpers | null;\n\n /** Set a field value */\n setFieldValue: (path: string, value: unknown) => void;\n /** Set a field as touched */\n setFieldTouched: (path: string, touched?: boolean) => void;\n /** Set multiple values */\n setValues: (values: Record<string, unknown>) => void;\n /** Validate a single field */\n validateField: (path: string) => FieldError[];\n /** Validate entire form */\n validateForm: () => ValidationResult;\n /** Submit the form */\n submitForm: () => Promise<void>;\n /** Reset the form */\n resetForm: () => void;\n\n /**\n * Register an imperative event listener. Returns an unsubscribe function.\n * Multiple listeners per event are supported; they fire in registration order.\n */\n on: <K extends keyof FormaEventMap>(\n event: K,\n listener: (payload: FormaEventMap[K]) => void | Promise<void>,\n ) => () => void;\n\n // Helper methods for getting field props\n /** Get props for any field */\n getFieldProps: (path: string) => GetFieldPropsResult;\n /** Get props for select field (includes options) */\n getSelectFieldProps: (path: string) => GetSelectFieldPropsResult;\n /** Get array helpers for array field */\n getArrayHelpers: (path: string) => GetArrayHelpersResult;\n}\n\n/**\n * State reducer\n */\nfunction formReducer(state: FormState, action: FormAction): FormState {\n switch (action.type) {\n case \"SET_FIELD_VALUE\":\n return {\n ...state,\n data: { ...state.data, [action.field]: action.value },\n isDirty: true,\n isSubmitted: false, // Clear on data change\n };\n case \"SET_FIELD_TOUCHED\":\n return {\n ...state,\n touched: { ...state.touched, [action.field]: action.touched },\n };\n case \"SET_VALUES\":\n return {\n ...state,\n data: { ...state.data, ...action.values },\n isDirty: true,\n isSubmitted: false, // Clear on data change\n };\n case \"SET_SUBMITTING\":\n return { ...state, isSubmitting: action.isSubmitting };\n case \"SET_SUBMITTED\":\n return { ...state, isSubmitted: action.isSubmitted };\n case \"SET_PAGE\":\n return { ...state, currentPage: action.page };\n case \"RESET\":\n return {\n data: action.initialData,\n touched: {},\n isSubmitting: false,\n isSubmitted: false,\n isDirty: false,\n currentPage: 0,\n };\n default:\n return state;\n }\n}\n\n/**\n * Get default initial values for boolean fields.\n * Boolean fields default to false to avoid undefined state,\n * which provides better UX since false is a valid answer.\n */\nfunction getDefaultBooleanValues(spec: Forma): Record<string, boolean> {\n const defaults: Record<string, boolean> = {};\n for (const fieldPath of spec.fieldOrder) {\n const schemaProperty = spec.schema.properties?.[fieldPath];\n const fieldDef = spec.fields[fieldPath];\n if (schemaProperty?.type === \"boolean\" || fieldDef?.type === \"boolean\") {\n defaults[fieldPath] = false;\n }\n }\n return defaults;\n}\n\n/**\n * Get default values from field definitions.\n * Collects `defaultValue` from each field that specifies one.\n * These are applied after boolean defaults but before initialData,\n * so explicit defaults override type-implicit defaults,\n * and runtime initialData overrides everything.\n */\nfunction getFieldDefaults(spec: Forma): Record<string, unknown> {\n const defaults: Record<string, unknown> = {};\n for (const [fieldPath, fieldDef] of Object.entries(spec.fields)) {\n if (fieldDef.defaultValue !== undefined) {\n defaults[fieldPath] = fieldDef.defaultValue;\n }\n }\n return defaults;\n}\n\n/**\n * Main Forma hook\n */\nexport function useForma(options: UseFormaOptions): UseFormaReturn {\n const {\n spec: inputSpec,\n initialData = {},\n onSubmit,\n onChange,\n validateOn = \"blur\",\n referenceData,\n validationDebounceMs = 0,\n on: onEvents,\n } = options;\n\n // Merge referenceData from options with spec.referenceData\n const spec = useMemo((): Forma => {\n if (!referenceData) return inputSpec;\n return {\n ...inputSpec,\n referenceData: {\n ...inputSpec.referenceData,\n ...referenceData,\n },\n };\n }, [inputSpec, referenceData]);\n\n const [state, dispatch] = useReducer(formReducer, {\n data: {\n ...getDefaultBooleanValues(spec),\n ...getFieldDefaults(spec),\n ...initialData,\n },\n touched: {},\n isSubmitting: false,\n isSubmitted: false,\n isDirty: false,\n currentPage: 0,\n });\n\n // Keep a ref to current state.data to avoid stale closures in cached handlers\n const stateDataRef = useRef(state.data);\n stateDataRef.current = state.data;\n\n // Track if we've initialized (to avoid calling onChange on first render)\n const hasInitialized = useRef(false);\n\n // ── Event system ──────────────────────────────────────────────────────\n const emitterRef = useRef(new FormaEventEmitter());\n const onEventsRef = useRef(onEvents);\n onEventsRef.current = onEvents;\n const pendingEventsRef = useRef<\n Array<{ event: keyof FormaEventMap; payload: unknown }>\n >([]);\n const isFiringEventsRef = useRef(false);\n\n // Cleanup emitter on unmount\n useEffect(() => {\n const emitter = emitterRef.current;\n return () => {\n emitter.clear();\n };\n }, []);\n\n // Helper: fire an event to both declarative `on` handlers and imperative listeners\n const fireEvent = useCallback(\n <K extends keyof FormaEventMap>(\n event: K,\n payload: FormaEventMap[K],\n ) => {\n // Declarative handler (via ref for latest callback)\n try {\n const handler = onEventsRef.current?.[event];\n if (handler) (handler as (p: FormaEventMap[K]) => void)(payload);\n } catch (error) {\n console.error(`[forma] Error in \"${event}\" event handler:`, error);\n }\n // Imperative listeners\n emitterRef.current.fire(event, payload);\n },\n [],\n );\n\n // Calculate computed values\n const computed = useMemo(\n () => calculate(state.data, spec),\n [state.data, spec],\n );\n\n // Calculate visibility\n const visibility = useMemo(\n () => getVisibility(state.data, spec, { computed }),\n [state.data, spec, computed],\n );\n\n // Calculate required state\n const required = useMemo(\n () => getRequired(state.data, spec, { computed }),\n [state.data, spec, computed],\n );\n\n // Calculate enabled state\n const enabled = useMemo(\n () => getEnabled(state.data, spec, { computed }),\n [state.data, spec, computed],\n );\n\n // Calculate readonly state\n const readonly = useMemo(\n () => getReadonly(state.data, spec, { computed }),\n [state.data, spec, computed],\n );\n\n // Calculate visible options for all select/multiselect fields (memoized)\n const optionsVisibility = useMemo(\n () => getOptionsVisibility(state.data, spec, { computed }),\n [state.data, spec, computed],\n );\n\n // Validate form - compute immediate result\n const immediateValidation = useMemo(\n () => validate(state.data, spec, { computed, onlyVisible: true }),\n [state.data, spec, computed],\n );\n\n // Debounced validation state (only used when validationDebounceMs > 0)\n const [debouncedValidation, setDebouncedValidation] =\n useState<ValidationResult>(immediateValidation);\n\n // Apply debouncing if configured\n useEffect(() => {\n if (validationDebounceMs <= 0) {\n // No debouncing - use immediate validation\n setDebouncedValidation(immediateValidation);\n return;\n }\n\n // Debounce validation updates\n const timeoutId = setTimeout(() => {\n setDebouncedValidation(immediateValidation);\n }, validationDebounceMs);\n\n return () => clearTimeout(timeoutId);\n }, [immediateValidation, validationDebounceMs]);\n\n // Use debounced validation for display, but immediate for submit\n const validation =\n validationDebounceMs > 0 ? debouncedValidation : immediateValidation;\n\n // isDirty is tracked via reducer state for O(1) performance\n\n // Call onChange when data changes (not on initial render)\n useEffect(() => {\n if (hasInitialized.current) {\n onChange?.(state.data, computed);\n } else {\n hasInitialized.current = true;\n }\n }, [state.data, computed, onChange]);\n\n // Helper function to set value at nested path\n const setNestedValue = useCallback(\n (path: string, value: unknown): void => {\n // Handle array index notation: \"items[0].name\" -> nested structure\n const parts = path.replace(/\\[(\\d+)\\]/g, \".$1\").split(\".\");\n\n if (parts.length === 1) {\n // Simple path - just set directly\n dispatch({ type: \"SET_FIELD_VALUE\", field: path, value });\n return;\n }\n\n // Build nested object for complex paths\n const buildNestedObject = (\n data: Record<string, unknown>,\n pathParts: string[],\n val: unknown,\n ): Record<string, unknown> => {\n const result = { ...data };\n let current: Record<string, unknown> = result;\n\n for (let i = 0; i < pathParts.length - 1; i++) {\n const part = pathParts[i];\n const nextPart = pathParts[i + 1];\n const isNextArrayIndex = /^\\d+$/.test(nextPart);\n\n if (current[part] === undefined) {\n current[part] = isNextArrayIndex ? [] : {};\n } else if (Array.isArray(current[part])) {\n current[part] = [...(current[part] as unknown[])];\n } else {\n current[part] = { ...(current[part] as Record<string, unknown>) };\n }\n current = current[part] as Record<string, unknown>;\n }\n\n current[pathParts[pathParts.length - 1]] = val;\n return result;\n };\n\n dispatch({\n type: \"SET_VALUES\",\n values: buildNestedObject(state.data, parts, value),\n });\n },\n [state.data],\n );\n\n // Helper to get value at nested path\n // Uses stateDataRef to always access current state, avoiding stale closure issues\n const getValueAtPath = useCallback((path: string): unknown => {\n // Handle array index notation: \"items[0].name\" -> [\"items\", \"0\", \"name\"]\n const parts = path.replace(/\\[(\\d+)\\]/g, \".$1\").split(\".\");\n let value: unknown = stateDataRef.current;\n for (const part of parts) {\n if (value === null || value === undefined) return undefined;\n value = (value as Record<string, unknown>)[part];\n }\n return value;\n }, []); // No dependencies - uses ref for current state\n\n // Queue a fieldChanged event (captures previousValue from current state ref)\n const queueFieldChangedEvent = useCallback(\n (\n path: string,\n value: unknown,\n source: \"user\" | \"reset\" | \"setValues\",\n ) => {\n if (isFiringEventsRef.current) return; // recursion guard\n const previousValue = getValueAtPath(path);\n if (previousValue === value) return; // no actual change\n pendingEventsRef.current.push({\n event: \"fieldChanged\",\n payload: { path, value, previousValue, source },\n });\n },\n [getValueAtPath],\n );\n\n // Actions\n const setFieldValue = useCallback(\n (path: string, value: unknown) => {\n queueFieldChangedEvent(path, value, \"user\");\n setNestedValue(path, value);\n if (validateOn === \"change\") {\n dispatch({ type: \"SET_FIELD_TOUCHED\", field: path, touched: true });\n }\n },\n [validateOn, setNestedValue, queueFieldChangedEvent],\n );\n\n const setFieldTouched = useCallback((path: string, touched = true) => {\n dispatch({ type: \"SET_FIELD_TOUCHED\", field: path, touched });\n }, []);\n\n const setValues = useCallback(\n (values: Record<string, unknown>) => {\n for (const [key, value] of Object.entries(values)) {\n queueFieldChangedEvent(key, value, \"setValues\");\n }\n dispatch({ type: \"SET_VALUES\", values });\n },\n [queueFieldChangedEvent],\n );\n\n const validateField = useCallback(\n (path: string): FieldError[] => {\n return validation.errors.filter((e) => e.field === path);\n },\n [validation],\n );\n\n const validateForm = useCallback((): ValidationResult => {\n return validation;\n }, [validation]);\n\n const submitForm = useCallback(async () => {\n dispatch({ type: \"SET_SUBMITTING\", isSubmitting: true });\n\n const submissionData = { ...state.data };\n let postSubmitPayload: FormaEventMap[\"postSubmit\"] | undefined;\n\n try {\n // Fire preSubmit (async, inline — listeners can mutate submissionData)\n const preSubmitPayload = {\n data: submissionData,\n computed: { ...computed },\n };\n // Declarative handler\n const declarativePreSubmit = onEventsRef.current?.preSubmit;\n if (declarativePreSubmit) {\n await declarativePreSubmit(preSubmitPayload);\n }\n // Imperative listeners\n if (emitterRef.current.hasListeners(\"preSubmit\")) {\n await emitterRef.current.fireAsync(\"preSubmit\", preSubmitPayload);\n }\n\n // Always use immediate validation on submit to ensure accurate result\n if (!immediateValidation.valid) {\n postSubmitPayload = {\n data: submissionData,\n success: false,\n validationErrors: immediateValidation.errors,\n };\n } else if (onSubmit) {\n try {\n await onSubmit(submissionData);\n postSubmitPayload = { data: submissionData, success: true };\n } catch (error) {\n postSubmitPayload = {\n data: submissionData,\n success: false,\n error:\n error instanceof Error ? error : new Error(String(error)),\n };\n }\n } else {\n postSubmitPayload = { data: submissionData, success: true };\n }\n\n dispatch({ type: \"SET_SUBMITTED\", isSubmitted: true });\n } finally {\n dispatch({ type: \"SET_SUBMITTING\", isSubmitting: false });\n // Fire postSubmit after state updates\n if (postSubmitPayload) {\n fireEvent(\"postSubmit\", postSubmitPayload);\n }\n }\n }, [immediateValidation, onSubmit, state.data, computed, fireEvent]);\n\n const resetForm = useCallback(() => {\n const resetData = {\n ...getDefaultBooleanValues(spec),\n ...getFieldDefaults(spec),\n ...initialData,\n };\n\n // Queue fieldChanged for each field that actually changes\n if (!isFiringEventsRef.current) {\n const currentData = stateDataRef.current;\n const allKeys = new Set([\n ...Object.keys(currentData),\n ...Object.keys(resetData),\n ]);\n for (const key of allKeys) {\n const currentVal = currentData[key];\n const resetVal = resetData[key];\n if (currentVal !== resetVal) {\n pendingEventsRef.current.push({\n event: \"fieldChanged\",\n payload: {\n path: key,\n value: resetVal,\n previousValue: currentVal,\n source: \"reset\" as const,\n },\n });\n }\n }\n // Queue formReset (fires after fieldChanged events)\n pendingEventsRef.current.push({\n event: \"formReset\",\n payload: {} as FormaEventMap[\"formReset\"],\n });\n }\n\n dispatch({ type: \"RESET\", initialData: resetData });\n }, [spec, initialData]);\n\n // Wizard helpers\n const wizard = useMemo((): WizardHelpers | null => {\n if (!spec.pages || spec.pages.length === 0) return null;\n\n const pageVisibility = getPageVisibility(state.data, spec, { computed });\n\n // Include all pages with their visibility status\n const pages: PageState[] = spec.pages.map((p) => ({\n id: p.id,\n title: p.title,\n description: p.description,\n visible: pageVisibility[p.id] !== false,\n fields: p.fields,\n }));\n\n // For navigation, only count visible pages\n const visiblePages = pages.filter((p) => p.visible);\n\n // Clamp currentPage to valid range (handles case where current page becomes hidden)\n const maxPageIndex = Math.max(0, visiblePages.length - 1);\n const clampedPageIndex = Math.min(\n Math.max(0, state.currentPage),\n maxPageIndex,\n );\n\n // Auto-correct page index if it's out of bounds\n if (clampedPageIndex !== state.currentPage && visiblePages.length > 0) {\n dispatch({ type: \"SET_PAGE\", page: clampedPageIndex });\n }\n\n const currentPage = visiblePages[clampedPageIndex] || null;\n const hasNextPage = clampedPageIndex < visiblePages.length - 1;\n const hasPreviousPage = clampedPageIndex > 0;\n const isLastPage = clampedPageIndex === visiblePages.length - 1;\n\n return {\n pages,\n currentPageIndex: clampedPageIndex,\n currentPage,\n goToPage: (index: number) => {\n const validIndex = Math.min(Math.max(0, index), maxPageIndex);\n if (validIndex !== clampedPageIndex) {\n dispatch({ type: \"SET_PAGE\", page: validIndex });\n const newPage = visiblePages[validIndex];\n if (newPage) {\n fireEvent(\"pageChanged\", {\n fromIndex: clampedPageIndex,\n toIndex: validIndex,\n page: newPage,\n });\n }\n }\n },\n nextPage: () => {\n if (hasNextPage) {\n const toIndex = clampedPageIndex + 1;\n dispatch({ type: \"SET_PAGE\", page: toIndex });\n const newPage = visiblePages[toIndex];\n if (newPage) {\n fireEvent(\"pageChanged\", {\n fromIndex: clampedPageIndex,\n toIndex,\n page: newPage,\n });\n }\n }\n },\n previousPage: () => {\n if (hasPreviousPage) {\n const toIndex = clampedPageIndex - 1;\n dispatch({ type: \"SET_PAGE\", page: toIndex });\n const newPage = visiblePages[toIndex];\n if (newPage) {\n fireEvent(\"pageChanged\", {\n fromIndex: clampedPageIndex,\n toIndex,\n page: newPage,\n });\n }\n }\n },\n hasNextPage,\n hasPreviousPage,\n canProceed: (() => {\n if (!currentPage) return true;\n // Get errors only for visible fields on the current page\n const pageErrors = validation.errors.filter((e) => {\n // Check if field is on current page (including array items like \"items[0].name\")\n const isOnCurrentPage =\n currentPage.fields.includes(e.field) ||\n currentPage.fields.some((f) => e.field.startsWith(`${f}[`));\n // Only count errors for visible fields\n const isVisible = visibility[e.field] !== false;\n // Only count actual errors, not warnings\n const isError = e.severity === \"error\";\n return isOnCurrentPage && isVisible && isError;\n });\n return pageErrors.length === 0;\n })(),\n isLastPage,\n touchCurrentPageFields: () => {\n if (currentPage) {\n currentPage.fields.forEach((field) => {\n dispatch({ type: \"SET_FIELD_TOUCHED\", field, touched: true });\n });\n }\n },\n validateCurrentPage: () => {\n if (!currentPage) return true;\n const pageErrors = validation.errors.filter((e) =>\n currentPage.fields.includes(e.field),\n );\n return pageErrors.length === 0;\n },\n };\n }, [spec, state.data, state.currentPage, computed, validation, visibility, fireEvent]);\n\n // Flush pending events after render (fieldChanged, formReset)\n useEffect(() => {\n const events = pendingEventsRef.current;\n if (events.length === 0) return;\n pendingEventsRef.current = [];\n\n isFiringEventsRef.current = true;\n try {\n for (const pending of events) {\n fireEvent(\n pending.event as keyof FormaEventMap,\n pending.payload as FormaEventMap[keyof FormaEventMap],\n );\n }\n } finally {\n isFiringEventsRef.current = false;\n }\n });\n\n // Helper to set value at nested path\n // Uses stateDataRef to always access current state, avoiding stale closure issues\n const setValueAtPath = useCallback(\n (path: string, value: unknown): void => {\n queueFieldChangedEvent(path, value, \"user\");\n // For nested paths, we need to build the nested structure\n const parts = path.replace(/\\[(\\d+)\\]/g, \".$1\").split(\".\");\n if (parts.length === 1) {\n dispatch({ type: \"SET_FIELD_VALUE\", field: path, value });\n return;\n }\n\n // Build nested object from CURRENT state via ref (not stale closure)\n const newData = { ...stateDataRef.current };\n let current: Record<string, unknown> = newData;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n const nextPart = parts[i + 1];\n const isNextArrayIndex = /^\\d+$/.test(nextPart);\n\n if (current[part] === undefined) {\n current[part] = isNextArrayIndex ? [] : {};\n } else if (Array.isArray(current[part])) {\n current[part] = [...(current[part] as unknown[])];\n } else {\n current[part] = { ...(current[part] as Record<string, unknown>) };\n }\n current = current[part] as Record<string, unknown>;\n }\n\n current[parts[parts.length - 1]] = value;\n dispatch({ type: \"SET_VALUES\", values: newData });\n },\n [queueFieldChangedEvent],\n );\n\n // Memoized onChange/onBlur handlers for fields\n const fieldHandlers = useRef<\n Map<string, { onChange: (value: unknown) => void; onBlur: () => void }>\n >(new Map());\n\n // Clean up stale field handlers when spec changes to prevent memory leaks\n useEffect(() => {\n const validFields = new Set(spec.fieldOrder);\n // Also include array item field patterns\n for (const fieldId of spec.fieldOrder) {\n const fieldDef = spec.fields[fieldId];\n if (fieldDef?.type === \"array\" && fieldDef.itemFields) {\n for (const key of fieldHandlers.current.keys()) {\n if (key.startsWith(`${fieldId}[`)) {\n validFields.add(key);\n }\n }\n }\n }\n // Remove handlers for fields that no longer exist\n for (const key of fieldHandlers.current.keys()) {\n const baseField = key.split(\"[\")[0];\n if (!validFields.has(key) && !validFields.has(baseField)) {\n fieldHandlers.current.delete(key);\n }\n }\n }, [spec]);\n\n const getFieldHandlers = useCallback(\n (path: string) => {\n if (!fieldHandlers.current.has(path)) {\n fieldHandlers.current.set(path, {\n onChange: (value: unknown) => setValueAtPath(path, value),\n onBlur: () => setFieldTouched(path),\n });\n }\n return fieldHandlers.current.get(path)!;\n },\n [setValueAtPath, setFieldTouched],\n );\n\n // Get field props for any field\n const getFieldProps = useCallback(\n (path: string): GetFieldPropsResult => {\n const fieldDef = spec.fields[path];\n const handlers = getFieldHandlers(path);\n\n // Determine field type from definition or infer from schema\n let fieldType = fieldDef?.type || \"text\";\n if (!fieldType || fieldType === \"computed\") {\n const schemaProperty = spec.schema.properties[path];\n if (schemaProperty) {\n if (schemaProperty.type === \"number\") fieldType = \"number\";\n else if (schemaProperty.type === \"integer\") fieldType = \"integer\";\n else if (schemaProperty.type === \"boolean\") fieldType = \"boolean\";\n else if (schemaProperty.type === \"array\") fieldType = \"array\";\n else if (schemaProperty.type === \"object\") fieldType = \"object\";\n else if (\"enum\" in schemaProperty && schemaProperty.enum)\n fieldType = \"select\";\n else if (\"format\" in schemaProperty) {\n if (schemaProperty.format === \"date\") fieldType = \"date\";\n else if (schemaProperty.format === \"date-time\")\n fieldType = \"datetime\";\n else if (schemaProperty.format === \"email\") fieldType = \"email\";\n else if (schemaProperty.format === \"uri\") fieldType = \"url\";\n }\n }\n }\n\n const fieldErrors = validation.errors.filter((e) => e.field === path);\n const isTouched = state.touched[path] ?? false;\n const showErrors =\n validateOn === \"change\" ||\n (validateOn === \"blur\" && isTouched) ||\n state.isSubmitted;\n const displayedErrors = showErrors ? fieldErrors : [];\n const hasErrors = displayedErrors.length > 0;\n const isRequired = required[path] ?? false;\n\n // Boolean fields: hide asterisk unless they have validation rules (consent pattern)\n // - Binary question (\"Do you smoke?\"): no validation → false is valid → hide asterisk\n // - Consent checkbox (\"I accept terms\"): has validation rule → show asterisk\n const schemaProperty = spec.schema.properties[path];\n const isBooleanField =\n schemaProperty?.type === \"boolean\" || fieldDef?.type === \"boolean\";\n const hasValidationRules = (fieldDef?.validations?.length ?? 0) > 0;\n const showRequiredIndicator =\n isRequired && (!isBooleanField || hasValidationRules);\n\n // Pass through adorner props for adornable field types\n const adornerProps =\n fieldDef && isAdornableField(fieldDef)\n ? { prefix: fieldDef.prefix, suffix: fieldDef.suffix }\n : {};\n\n return {\n name: path,\n value: getValueAtPath(path),\n type: fieldType,\n label: fieldDef?.label || path.charAt(0).toUpperCase() + path.slice(1),\n description: fieldDef?.description,\n placeholder: fieldDef?.placeholder,\n visible: visibility[path] !== false,\n enabled: enabled[path] !== false,\n readonly: readonly[path] ?? false,\n required: isRequired,\n showRequiredIndicator,\n touched: isTouched,\n errors: displayedErrors,\n onChange: handlers.onChange,\n onBlur: handlers.onBlur,\n // ARIA accessibility attributes\n \"aria-invalid\": hasErrors || undefined,\n \"aria-describedby\": hasErrors ? `${path}-error` : undefined,\n \"aria-required\": isRequired || undefined,\n // Adorner props (only for adornable field types)\n ...adornerProps,\n // Presentation variant\n variant: fieldDef?.variant,\n variantConfig: fieldDef?.variantConfig,\n };\n },\n [\n spec,\n state.touched,\n state.isSubmitted,\n visibility,\n enabled,\n readonly,\n required,\n validation.errors,\n validateOn,\n getValueAtPath,\n getFieldHandlers,\n ],\n );\n\n // Get select field props - uses pre-computed optionsVisibility map\n const getSelectFieldProps = useCallback(\n (path: string): GetSelectFieldPropsResult => {\n const baseProps = getFieldProps(path);\n\n // Look up pre-computed visible options from memoized map\n const visibleOptions = optionsVisibility[path] ?? [];\n\n return {\n ...baseProps,\n options: visibleOptions as SelectOption[],\n };\n },\n [getFieldProps, optionsVisibility],\n );\n\n // Get array helpers\n const getArrayHelpers = useCallback(\n (path: string): GetArrayHelpersResult => {\n const fieldDef = spec.fields[path];\n const currentValue = (getValueAtPath(path) as unknown[]) ?? [];\n const arrayDef = fieldDef?.type === \"array\" ? fieldDef : undefined;\n const minItems = arrayDef?.minItems ?? 0;\n const maxItems = arrayDef?.maxItems ?? Infinity;\n\n const canAdd = currentValue.length < maxItems;\n const canRemove = currentValue.length > minItems;\n\n const getItemFieldProps = (\n index: number,\n fieldName: string,\n ): GetFieldPropsResult => {\n const itemPath = `${path}[${index}].${fieldName}`;\n const itemFieldDef = arrayDef?.itemFields?.[fieldName];\n const handlers = getFieldHandlers(itemPath);\n\n // Get item value\n const item = (currentValue[index] as Record<string, unknown>) ?? {};\n const itemValue = item[fieldName];\n\n const fieldErrors = validation.errors.filter(\n (e) => e.field === itemPath,\n );\n const isTouched = state.touched[itemPath] ?? false;\n const showErrors =\n validateOn === \"change\" ||\n (validateOn === \"blur\" && isTouched) ||\n state.isSubmitted;\n\n // Look up pre-computed visible options from memoized map\n const visibleOptions = optionsVisibility[itemPath] as\n | SelectOption[]\n | undefined;\n\n return {\n name: itemPath,\n value: itemValue,\n type: itemFieldDef?.type || \"text\",\n label:\n itemFieldDef?.label ||\n fieldName.charAt(0).toUpperCase() + fieldName.slice(1),\n description: itemFieldDef?.description,\n placeholder: itemFieldDef?.placeholder,\n visible: true,\n enabled: enabled[path] !== false,\n readonly: readonly[itemPath] ?? false,\n required: false, // TODO: Evaluate item field required\n showRequiredIndicator: false, // Item fields don't show required indicator\n touched: isTouched,\n errors: showErrors ? fieldErrors : [],\n onChange: handlers.onChange,\n onBlur: handlers.onBlur,\n options: visibleOptions,\n };\n };\n\n return {\n items: currentValue,\n push: (item: unknown) => {\n if (canAdd) {\n setValueAtPath(path, [...currentValue, item]);\n }\n },\n remove: (index: number) => {\n if (canRemove) {\n const newArray = [...currentValue];\n newArray.splice(index, 1);\n setValueAtPath(path, newArray);\n }\n },\n move: (from: number, to: number) => {\n const newArray = [...currentValue];\n const [item] = newArray.splice(from, 1);\n newArray.splice(to, 0, item);\n setValueAtPath(path, newArray);\n },\n swap: (indexA: number, indexB: number) => {\n const newArray = [...currentValue];\n [newArray[indexA], newArray[indexB]] = [\n newArray[indexB],\n newArray[indexA],\n ];\n setValueAtPath(path, newArray);\n },\n insert: (index: number, item: unknown) => {\n if (canAdd) {\n const newArray = [...currentValue];\n newArray.splice(index, 0, item);\n setValueAtPath(path, newArray);\n }\n },\n getItemFieldProps,\n minItems,\n maxItems,\n canAdd,\n canRemove,\n };\n },\n [\n spec.fields,\n getValueAtPath,\n setValueAtPath,\n getFieldHandlers,\n enabled,\n readonly,\n state.touched,\n state.isSubmitted,\n validation.errors,\n validateOn,\n optionsVisibility,\n ],\n );\n\n // Stable reference for imperative event subscription — only depends on the\n // emitter ref, so consumers can safely use it as a useEffect dependency.\n const on = useCallback(\n <K extends keyof FormaEventMap>(\n event: K,\n listener: (payload: FormaEventMap[K]) => void | Promise<void>,\n ) => emitterRef.current.on(event, listener),\n [],\n );\n\n return useMemo(\n (): UseFormaReturn => ({\n data: state.data,\n computed,\n visibility,\n required,\n enabled,\n readonly,\n optionsVisibility,\n touched: state.touched,\n errors: validation.errors,\n isValid: validation.valid,\n isSubmitting: state.isSubmitting,\n isSubmitted: state.isSubmitted,\n isDirty: state.isDirty,\n spec,\n wizard,\n setFieldValue,\n setFieldTouched,\n setValues,\n validateField,\n validateForm,\n submitForm,\n resetForm,\n on,\n getFieldProps,\n getSelectFieldProps,\n getArrayHelpers,\n }),\n [\n state.data,\n state.touched,\n state.isSubmitting,\n state.isSubmitted,\n state.isDirty,\n computed,\n visibility,\n required,\n enabled,\n readonly,\n optionsVisibility,\n validation.errors,\n validation.valid,\n spec,\n wizard,\n setFieldValue,\n setFieldTouched,\n setValues,\n validateField,\n validateForm,\n submitForm,\n resetForm,\n on,\n getFieldProps,\n getSelectFieldProps,\n getArrayHelpers,\n ],\n );\n}\n","/**\n * Event system for forma-react\n *\n * Lightweight event emitter for form lifecycle events.\n * Events are for side effects (analytics, data injection, external state sync)\n * — they do not trigger React re-renders.\n */\n\nimport type { FieldError } from \"@fogpipe/forma-core\";\nimport type { PageState } from \"./useForma.js\";\n\n// ============================================================================\n// Event Type Definitions\n// ============================================================================\n\n/**\n * Map of all forma event names to their payload types.\n */\nexport interface FormaEventMap {\n /**\n * Fires after a field value changes via user input, setFieldValue, setValues, or resetForm.\n * Does NOT fire for computed/calculated field changes or on initial mount.\n */\n fieldChanged: {\n /** Field path (e.g., \"age\" or \"medications[0].dosage\") */\n path: string;\n /** New value */\n value: unknown;\n /** Value before the change */\n previousValue: unknown;\n /** What triggered the change */\n source: \"user\" | \"reset\" | \"setValues\";\n };\n\n /**\n * Fires at the start of submitForm(), before validation.\n * The `data` object is mutable — consumers can inject extra fields.\n * Async handlers are awaited before proceeding to validation.\n */\n preSubmit: {\n /** Mutable form data — add/modify fields here */\n data: Record<string, unknown>;\n /** Read-only snapshot of computed values */\n computed: Record<string, unknown>;\n };\n\n /**\n * Fires after onSubmit resolves/rejects or after validation failure.\n */\n postSubmit: {\n /** The submitted data (reflects any preSubmit mutations) */\n data: Record<string, unknown>;\n /** Whether submission succeeded */\n success: boolean;\n /** Present when onSubmit threw an error */\n error?: Error;\n /** Present when validation failed (onSubmit was never called) */\n validationErrors?: FieldError[];\n };\n\n /**\n * Fires when the wizard page changes via nextPage, previousPage, or goToPage.\n * Does NOT fire on initial render or automatic page clamping.\n */\n pageChanged: {\n /** Previous page index */\n fromIndex: number;\n /** New page index */\n toIndex: number;\n /** The new current page */\n page: PageState;\n };\n\n /**\n * Fires after resetForm() completes and state is back to initial values.\n */\n formReset: Record<string, never>;\n}\n\n// ============================================================================\n// Helper Types\n// ============================================================================\n\n/**\n * Listener function type for a specific event.\n */\nexport type FormaEventListener<K extends keyof FormaEventMap> = (\n event: FormaEventMap[K],\n) => void | Promise<void>;\n\n/**\n * Declarative event listener map for useForma `on` option.\n */\nexport type FormaEvents = Partial<{\n [K in keyof FormaEventMap]: FormaEventListener<K>;\n}>;\n\n// ============================================================================\n// Event Emitter\n// ============================================================================\n\n/**\n * Lightweight event emitter for forma lifecycle events.\n * Uses Map<string, Set<listener>> internally — no external dependencies.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AnyListener = (...args: any[]) => void | Promise<void>;\n\nexport class FormaEventEmitter {\n private listeners = new Map<string, Set<AnyListener>>();\n\n /**\n * Register a listener for an event. Returns an unsubscribe function.\n */\n on<K extends keyof FormaEventMap>(\n event: K,\n listener: FormaEventListener<K>,\n ): () => void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n\n return () => {\n const set = this.listeners.get(event);\n if (set) {\n set.delete(listener);\n if (set.size === 0) {\n this.listeners.delete(event);\n }\n }\n };\n }\n\n /**\n * Fire an event synchronously. Listener errors are caught and logged\n * to prevent one listener from breaking others.\n */\n fire<K extends keyof FormaEventMap>(event: K, payload: FormaEventMap[K]): void {\n const set = this.listeners.get(event);\n if (!set || set.size === 0) return;\n\n for (const listener of set) {\n try {\n listener(payload);\n } catch (error) {\n console.error(`[forma] Error in \"${event}\" event listener:`, error);\n }\n }\n }\n\n /**\n * Fire an event and await all async listeners sequentially.\n * Used for preSubmit where handlers can be async.\n */\n async fireAsync<K extends keyof FormaEventMap>(\n event: K,\n payload: FormaEventMap[K],\n ): Promise<void> {\n const set = this.listeners.get(event);\n if (!set || set.size === 0) return;\n\n for (const listener of set) {\n try {\n await listener(payload);\n } catch (error) {\n console.error(`[forma] Error in \"${event}\" event listener:`, error);\n }\n }\n }\n\n /**\n * Check if any listeners are registered for an event.\n */\n hasListeners(event: keyof FormaEventMap): boolean {\n const set = this.listeners.get(event);\n return set !== undefined && set.size > 0;\n }\n\n /**\n * Remove all listeners. Called on cleanup.\n */\n clear(): void {\n this.listeners.clear();\n }\n}\n","/**\n * FormRenderer Component\n *\n * Renders a complete form from a Forma specification.\n * Supports single-page and multi-page (wizard) forms.\n */\n\nimport React, {\n forwardRef,\n useImperativeHandle,\n useRef,\n useMemo,\n useCallback,\n} from \"react\";\nimport type {\n Forma,\n FieldDefinition,\n ValidationResult,\n JSONSchemaProperty,\n SelectOption,\n} from \"@fogpipe/forma-core\";\nimport { isAdornableField, isSelectionField } from \"@fogpipe/forma-core\";\nimport { useForma } from \"./useForma.js\";\nimport { FormaContext } from \"./context.js\";\nimport type {\n ComponentMap,\n LayoutProps,\n FieldWrapperProps,\n PageWrapperProps,\n BaseFieldProps,\n TextFieldProps,\n NumberFieldProps,\n SelectFieldProps,\n ArrayFieldProps,\n ArrayHelpers,\n DisplayFieldProps,\n MatrixFieldProps,\n} from \"./types.js\";\n\n/**\n * Props for FormRenderer component\n */\nexport interface FormRendererProps {\n /** The Forma specification */\n spec: Forma;\n /** Initial form data */\n initialData?: Record<string, unknown>;\n /** Submit handler */\n onSubmit?: (data: Record<string, unknown>) => void | Promise<void>;\n /** Change handler */\n onChange?: (\n data: Record<string, unknown>,\n computed?: Record<string, unknown>,\n ) => void;\n /** Component map for rendering fields */\n components: ComponentMap;\n /** Custom layout component */\n layout?: React.ComponentType<LayoutProps>;\n /** Custom field wrapper component */\n fieldWrapper?: React.ComponentType<FieldWrapperProps>;\n /** Custom page wrapper component */\n pageWrapper?: React.ComponentType<PageWrapperProps>;\n /** When to validate */\n validateOn?: \"change\" | \"blur\" | \"submit\";\n /** Current page for controlled wizard */\n page?: number;\n}\n\n/**\n * Imperative handle for FormRenderer\n */\nexport interface FormRendererHandle {\n submitForm: () => Promise<void>;\n resetForm: () => void;\n validateForm: () => ValidationResult;\n focusField: (path: string) => void;\n focusFirstError: () => void;\n getValues: () => Record<string, unknown>;\n setValues: (values: Record<string, unknown>) => void;\n isValid: boolean;\n isDirty: boolean;\n}\n\n/**\n * Default layout component\n */\nfunction DefaultLayout({ children, onSubmit, isSubmitting }: LayoutProps) {\n return (\n <form\n onSubmit={(e) => {\n e.preventDefault();\n onSubmit();\n }}\n >\n {children}\n <button type=\"submit\" disabled={isSubmitting}>\n {isSubmitting ? \"Submitting...\" : \"Submit\"}\n </button>\n </form>\n );\n}\n\n/**\n * Default field wrapper component with accessibility support\n */\nfunction DefaultFieldWrapper({\n fieldPath,\n field,\n children,\n errors,\n showRequiredIndicator,\n visible,\n}: FieldWrapperProps) {\n if (!visible) return null;\n\n const errorId = `${fieldPath}-error`;\n const descriptionId = field.description\n ? `${fieldPath}-description`\n : undefined;\n const hasErrors = errors.length > 0;\n\n return (\n <div className=\"field-wrapper\" data-field-path={fieldPath}>\n {field.label && (\n <label htmlFor={fieldPath}>\n {field.label}\n {showRequiredIndicator && (\n <span className=\"required\" aria-hidden=\"true\">\n *\n </span>\n )}\n {showRequiredIndicator && (\n <span className=\"sr-only\"> (required)</span>\n )}\n </label>\n )}\n {children}\n {hasErrors && (\n <div\n id={errorId}\n className=\"field-errors\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n {errors.map((error, i) => (\n <span key={i} className=\"error\">\n {error.message}\n </span>\n ))}\n </div>\n )}\n {field.description && (\n <p id={descriptionId} className=\"field-description\">\n {field.description}\n </p>\n )}\n </div>\n );\n}\n\n/**\n * Default page wrapper component\n */\nfunction DefaultPageWrapper({\n title,\n description,\n children,\n}: PageWrapperProps) {\n return (\n <div className=\"page-wrapper\">\n <h2>{title}</h2>\n {description && <p>{description}</p>}\n {children}\n </div>\n );\n}\n\n/**\n * Extract numeric constraints from JSON Schema property\n */\nfunction getNumberConstraints(schema?: JSONSchemaProperty): {\n min?: number;\n max?: number;\n step?: number;\n} {\n if (!schema) return {};\n if (schema.type !== \"number\" && schema.type !== \"integer\") return {};\n\n // Extract min/max from schema\n const min =\n \"minimum\" in schema && typeof schema.minimum === \"number\"\n ? schema.minimum\n : undefined;\n const max =\n \"maximum\" in schema && typeof schema.maximum === \"number\"\n ? schema.maximum\n : undefined;\n\n // Use multipleOf for step if defined, otherwise default to 1 for integers\n let step: number | undefined;\n if (\"multipleOf\" in schema && typeof schema.multipleOf === \"number\") {\n step = schema.multipleOf;\n } else if (schema.type === \"integer\") {\n step = 1;\n }\n\n return { min, max, step };\n}\n\n/**\n * Create a default item for an array field based on item field definitions\n */\nfunction createDefaultItem(\n itemFields: Record<string, FieldDefinition>,\n): Record<string, unknown> {\n const item: Record<string, unknown> = {};\n for (const [fieldName, fieldDef] of Object.entries(itemFields)) {\n if (fieldDef.defaultValue !== undefined) {\n item[fieldName] = fieldDef.defaultValue;\n } else if (fieldDef.type === \"boolean\") {\n item[fieldName] = false;\n } else if (fieldDef.type === \"number\" || fieldDef.type === \"integer\") {\n item[fieldName] = null;\n } else {\n item[fieldName] = \"\";\n }\n }\n return item;\n}\n\n/**\n * FormRenderer component\n */\nexport const FormRenderer = forwardRef<FormRendererHandle, FormRendererProps>(\n function FormRenderer(props, ref) {\n const {\n spec,\n initialData,\n onSubmit,\n onChange,\n components,\n layout: Layout = DefaultLayout,\n fieldWrapper: FieldWrapper = DefaultFieldWrapper,\n pageWrapper: PageWrapper = DefaultPageWrapper,\n validateOn,\n } = props;\n\n const forma = useForma({\n spec,\n initialData,\n onSubmit,\n onChange,\n validateOn,\n });\n\n const fieldRefs = useRef<Map<string, HTMLElement>>(new Map());\n\n // Focus a specific field by path\n const focusField = useCallback((path: string) => {\n const element = fieldRefs.current.get(path);\n element?.focus();\n }, []);\n\n // Focus the first field with an error\n const focusFirstError = useCallback(() => {\n const firstError = forma.errors[0];\n if (firstError) {\n focusField(firstError.field);\n }\n }, [forma.errors, focusField]);\n\n // Expose imperative handle\n useImperativeHandle(\n ref,\n () => ({\n submitForm: forma.submitForm,\n resetForm: forma.resetForm,\n validateForm: forma.validateForm,\n focusField,\n focusFirstError,\n getValues: () => forma.data,\n setValues: forma.setValues,\n isValid: forma.isValid,\n isDirty: forma.isDirty,\n }),\n [forma, focusField, focusFirstError],\n );\n\n // Destructure only the values renderField needs from forma.\n // This prevents renderField from recreating when unrelated state changes\n // (isSubmitting, isDirty, wizard page navigation, etc.)\n const {\n data: formaData,\n computed: formaComputed,\n visibility: formaVisibility,\n required: formaRequired,\n enabled: formaEnabled,\n readonly: formaReadonly,\n optionsVisibility: formaOptionsVisibility,\n touched: formaTouched,\n errors: formaErrors,\n setFieldValue,\n setFieldTouched,\n getArrayHelpers,\n } = forma;\n\n // Determine which fields to render based on pages or fieldOrder\n const fieldsToRender = useMemo(() => {\n if (spec.pages && spec.pages.length > 0 && forma.wizard) {\n // Wizard mode - render fields for the active page\n const currentPage = forma.wizard.currentPage;\n if (currentPage) {\n return currentPage.fields;\n }\n // Fallback to first page\n return spec.pages[0]?.fields ?? [];\n }\n // Single page mode - render all fields in order\n return spec.fieldOrder;\n }, [spec.pages, spec.fieldOrder, forma.wizard]);\n\n // Render a single field (memoized)\n const renderField = useCallback(\n (fieldPath: string) => {\n const fieldDef = spec.fields[fieldPath];\n if (!fieldDef) return null;\n\n const isVisible = formaVisibility[fieldPath] !== false;\n if (!isVisible) {\n return <div key={fieldPath} data-field-path={fieldPath} hidden />;\n }\n\n // Get field type (type is required on all field definitions)\n const fieldType = fieldDef.type;\n const componentKey = fieldType as keyof ComponentMap;\n const Component = components[componentKey] || components.fallback;\n\n if (!Component) {\n console.warn(`No component found for field type: ${fieldType}`);\n return null;\n }\n\n const errors = formaErrors.filter((e) => e.field === fieldPath);\n const touched = formaTouched[fieldPath] ?? false;\n const required = formaRequired[fieldPath] ?? false;\n const disabled = formaEnabled[fieldPath] === false;\n\n // Get schema property for additional constraints\n const schemaProperty = spec.schema.properties[fieldPath];\n\n // Boolean fields: hide asterisk unless they have validation rules (consent pattern)\n // - Binary question (\"Do you smoke?\"): no validation → false is valid → hide asterisk\n // - Consent checkbox (\"I accept terms\"): has validation rule → show asterisk\n const isBooleanField =\n schemaProperty?.type === \"boolean\" || fieldDef?.type === \"boolean\";\n const hasValidationRules = (fieldDef?.validations?.length ?? 0) > 0;\n const showRequiredIndicator =\n required && (!isBooleanField || hasValidationRules);\n\n // Base field props\n const isReadonly = formaReadonly[fieldPath] ?? false;\n const baseProps: BaseFieldProps = {\n name: fieldPath,\n field: fieldDef,\n value: formaData[fieldPath],\n touched,\n required,\n disabled,\n errors,\n onChange: (value: unknown) => setFieldValue(fieldPath, value),\n onBlur: () => setFieldTouched(fieldPath),\n // Convenience properties\n visible: true, // Always true since we already filtered for visibility\n enabled: !disabled,\n readonly: isReadonly,\n label: fieldDef.label ?? fieldPath,\n description: fieldDef.description,\n placeholder: fieldDef.placeholder,\n // Adorner properties (only for adornable field types)\n ...(isAdornableField(fieldDef) && {\n prefix: fieldDef.prefix,\n suffix: fieldDef.suffix,\n }),\n // Presentation variant\n variant: fieldDef.variant,\n variantConfig: fieldDef.variantConfig,\n };\n\n // Build type-specific props\n let fieldProps:\n | BaseFieldProps\n | TextFieldProps\n | NumberFieldProps\n | SelectFieldProps\n | ArrayFieldProps\n | DisplayFieldProps\n | MatrixFieldProps = baseProps;\n\n if (fieldType === \"number\" || fieldType === \"integer\") {\n const constraints = getNumberConstraints(schemaProperty);\n fieldProps = {\n ...baseProps,\n fieldType,\n value: baseProps.value as number | null,\n onChange: baseProps.onChange as (value: number | null) => void,\n ...constraints,\n } as NumberFieldProps;\n } else if (fieldType === \"select\" || fieldType === \"multiselect\") {\n const selectOptions = isSelectionField(fieldDef)\n ? fieldDef.options\n : [];\n fieldProps = {\n ...baseProps,\n fieldType,\n value: baseProps.value as string | string[] | null,\n onChange: baseProps.onChange as (\n value: string | string[] | null,\n ) => void,\n options: formaOptionsVisibility[fieldPath] ?? selectOptions ?? [],\n } as SelectFieldProps;\n } else if (\n fieldType === \"array\" &&\n fieldDef.type === \"array\" &&\n fieldDef.itemFields\n ) {\n const arrayValue = Array.isArray(baseProps.value)\n ? baseProps.value\n : [];\n const minItems = fieldDef.minItems ?? 0;\n const maxItems = fieldDef.maxItems ?? Infinity;\n const itemFieldDefs = fieldDef.itemFields;\n\n // Get helpers from useForma - these are fresh on each render, avoiding stale closures\n const baseHelpers = getArrayHelpers(fieldPath);\n\n // Wrap push to add default item creation when called without arguments\n const pushWithDefault = (item?: unknown): void => {\n const newItem = item ?? createDefaultItem(itemFieldDefs);\n baseHelpers.push(newItem);\n };\n\n // Extend getItemFieldProps to include additional metadata (itemIndex, fieldName, options)\n const getItemFieldPropsExtended = (\n index: number,\n fieldName: string,\n ) => {\n const baseProps = baseHelpers.getItemFieldProps(index, fieldName);\n const itemFieldDef = itemFieldDefs[fieldName];\n const itemPath = `${fieldPath}[${index}].${fieldName}`;\n return {\n ...baseProps,\n itemIndex: index,\n fieldName,\n options:\n (formaOptionsVisibility[itemPath] as\n | SelectOption[]\n | undefined) ??\n (itemFieldDef && isSelectionField(itemFieldDef)\n ? itemFieldDef.options\n : undefined),\n };\n };\n\n const helpers: ArrayHelpers = {\n items: arrayValue,\n push: pushWithDefault,\n insert: baseHelpers.insert,\n remove: baseHelpers.remove,\n move: baseHelpers.move,\n swap: baseHelpers.swap,\n getItemFieldProps: getItemFieldPropsExtended,\n minItems,\n maxItems,\n canAdd: arrayValue.length < maxItems,\n canRemove: arrayValue.length > minItems,\n };\n fieldProps = {\n ...baseProps,\n fieldType: \"array\",\n value: arrayValue,\n onChange: baseProps.onChange as (value: unknown[]) => void,\n helpers,\n itemFields: itemFieldDefs,\n itemFieldOrder: fieldDef.itemFieldOrder,\n minItems,\n maxItems,\n } as ArrayFieldProps;\n } else if (fieldType === \"matrix\" && fieldDef.type === \"matrix\") {\n const matrixValue =\n (baseProps.value as Record<string, string | number | string[] | number[]> | null) ?? null;\n const rows = fieldDef.rows.map((row) => ({\n id: row.id,\n label: row.label,\n visible: formaVisibility[`${fieldPath}.${row.id}`] !== false,\n }));\n fieldProps = {\n ...baseProps,\n fieldType: \"matrix\",\n value: matrixValue,\n onChange: baseProps.onChange as (\n value: Record<string, string | number | string[] | number[]>,\n ) => void,\n rows,\n columns: fieldDef.columns,\n multiSelect: fieldDef.multiSelect ?? false,\n } as MatrixFieldProps;\n } else if (fieldType === \"display\" && fieldDef.type === \"display\") {\n // Display fields (read-only presentation content)\n // Resolve source value if the display field has a source property\n const sourceValue = fieldDef.source\n ? (formaData[fieldDef.source] ?? formaComputed[fieldDef.source])\n : undefined;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n onChange: _onChange,\n value: _value,\n ...displayBaseProps\n } = baseProps;\n fieldProps = {\n ...displayBaseProps,\n fieldType: \"display\",\n content: fieldDef.content,\n sourceValue,\n format: fieldDef.format,\n } as DisplayFieldProps;\n } else {\n // Text-based fields\n fieldProps = {\n ...baseProps,\n fieldType: fieldType as\n | \"text\"\n | \"phone\"\n | \"email\"\n | \"password\"\n | \"url\"\n | \"textarea\",\n value: (baseProps.value as string) ?? \"\",\n onChange: baseProps.onChange as (value: string) => void,\n };\n }\n\n // Wrap props in { field, spec } structure for components\n const componentProps = { field: fieldProps, spec };\n\n return (\n <div key={fieldPath} data-field-path={fieldPath}>\n <FieldWrapper\n fieldPath={fieldPath}\n field={fieldDef}\n errors={errors}\n touched={touched}\n required={required}\n showRequiredIndicator={showRequiredIndicator}\n visible={isVisible}\n >\n {React.createElement(\n Component as React.ComponentType<typeof componentProps>,\n componentProps,\n )}\n </FieldWrapper>\n </div>\n );\n },\n [\n spec,\n components,\n FieldWrapper,\n formaData,\n formaComputed,\n formaVisibility,\n formaRequired,\n formaEnabled,\n formaReadonly,\n formaOptionsVisibility,\n formaTouched,\n formaErrors,\n setFieldValue,\n setFieldTouched,\n getArrayHelpers,\n ],\n );\n\n // Render fields (memoized)\n const renderedFields = useMemo(\n () => fieldsToRender.map(renderField),\n [fieldsToRender, renderField],\n );\n\n // Render with page wrapper if using pages\n const content = useMemo(() => {\n if (spec.pages && spec.pages.length > 0 && forma.wizard) {\n const currentPage = forma.wizard.currentPage;\n if (!currentPage) return null;\n\n return (\n <PageWrapper\n title={currentPage.title}\n description={currentPage.description}\n pageIndex={forma.wizard.currentPageIndex}\n totalPages={forma.wizard.pages.length}\n >\n {renderedFields}\n </PageWrapper>\n );\n }\n\n return <>{renderedFields}</>;\n }, [spec.pages, forma.wizard, PageWrapper, renderedFields]);\n\n return (\n <FormaContext.Provider value={forma}>\n <Layout\n onSubmit={forma.submitForm}\n isSubmitting={forma.isSubmitting}\n isValid={forma.isValid}\n >\n {content}\n </Layout>\n </FormaContext.Provider>\n );\n },\n);\n","/**\n * React Context for Forma\n */\n\nimport { createContext, useContext } from \"react\";\nimport type { UseFormaReturn } from \"./useForma.js\";\n\n/**\n * Context for sharing form state across components\n */\nexport const FormaContext = createContext<UseFormaReturn | null>(null);\n\n/**\n * Hook to access Forma context\n * @throws Error if used outside of FormaContext.Provider\n */\nexport function useFormaContext(): UseFormaReturn {\n const context = useContext(FormaContext);\n if (!context) {\n throw new Error(\n \"useFormaContext must be used within a FormaContext.Provider\",\n );\n }\n return context;\n}\n","/**\n * FieldRenderer Component\n *\n * Routes a single field to the appropriate component based on its type.\n * This is useful for custom form layouts where you need field-by-field control.\n */\n\nimport React from \"react\";\nimport type {\n FieldDefinition,\n JSONSchemaProperty,\n SelectOption,\n} from \"@fogpipe/forma-core\";\nimport { isAdornableField } from \"@fogpipe/forma-core\";\nimport { useFormaContext } from \"./context.js\";\nimport type {\n ComponentMap,\n BaseFieldProps,\n TextFieldProps,\n NumberFieldProps,\n IntegerFieldProps,\n SelectFieldProps,\n MultiSelectFieldProps,\n ArrayFieldProps,\n ArrayHelpers,\n DisplayFieldProps,\n MatrixFieldProps,\n} from \"./types.js\";\n\n/**\n * Props for FieldRenderer component\n */\nexport interface FieldRendererProps {\n /** Field path (e.g., \"firstName\" or \"address.city\") */\n fieldPath: string;\n /** Component map for rendering fields */\n components: ComponentMap;\n /** Optional class name for the wrapper */\n className?: string;\n}\n\n/**\n * Extract numeric constraints from JSON Schema property\n */\nfunction getNumberConstraints(schema?: JSONSchemaProperty): {\n min?: number;\n max?: number;\n step?: number;\n} {\n if (!schema) return {};\n if (schema.type !== \"number\" && schema.type !== \"integer\") return {};\n\n // Extract min/max from schema\n const min =\n \"minimum\" in schema && typeof schema.minimum === \"number\"\n ? schema.minimum\n : undefined;\n const max =\n \"maximum\" in schema && typeof schema.maximum === \"number\"\n ? schema.maximum\n : undefined;\n\n // Use multipleOf for step if defined, otherwise default to 1 for integers\n let step: number | undefined;\n if (\"multipleOf\" in schema && typeof schema.multipleOf === \"number\") {\n step = schema.multipleOf;\n } else if (schema.type === \"integer\") {\n step = 1;\n }\n\n return { min, max, step };\n}\n\n/**\n * Create a default item for an array field based on item field definitions\n */\nfunction createDefaultItem(\n itemFields: Record<string, FieldDefinition>,\n): Record<string, unknown> {\n const item: Record<string, unknown> = {};\n for (const [fieldName, fieldDef] of Object.entries(itemFields)) {\n if (fieldDef.defaultValue !== undefined) {\n item[fieldName] = fieldDef.defaultValue;\n } else if (fieldDef.type === \"boolean\") {\n item[fieldName] = false;\n } else if (fieldDef.type === \"number\" || fieldDef.type === \"integer\") {\n item[fieldName] = null;\n } else {\n item[fieldName] = \"\";\n }\n }\n return item;\n}\n\n/**\n * FieldRenderer component\n *\n * @example\n * ```tsx\n * // Render a specific field with custom components\n * <FieldRenderer fieldPath=\"email\" components={componentMap} />\n * ```\n */\nexport function FieldRenderer({\n fieldPath,\n components,\n className,\n}: FieldRendererProps) {\n const forma = useFormaContext();\n const { spec } = forma;\n\n const fieldDef = spec.fields[fieldPath];\n if (!fieldDef) {\n console.warn(`Field not found: ${fieldPath}`);\n return null;\n }\n\n const isVisible = forma.visibility[fieldPath] !== false;\n if (!isVisible) {\n return <div data-field-path={fieldPath} hidden />;\n }\n\n // Get field type (type is required on all field definitions)\n const fieldType = fieldDef.type;\n const componentKey = fieldType as keyof ComponentMap;\n const Component = components[componentKey] || components.fallback;\n\n if (!Component) {\n console.warn(`No component found for field type: ${fieldType}`);\n return null;\n }\n\n const errors = forma.errors.filter((e) => e.field === fieldPath);\n const touched = forma.touched[fieldPath] ?? false;\n const required = forma.required[fieldPath] ?? false;\n const disabled = forma.enabled[fieldPath] === false;\n\n // Get schema property for additional constraints\n const schemaProperty = spec.schema.properties[fieldPath];\n\n // Base field props\n const isReadonly = forma.readonly[fieldPath] ?? false;\n const baseProps: BaseFieldProps = {\n name: fieldPath,\n field: fieldDef,\n value: forma.data[fieldPath],\n touched,\n required,\n disabled,\n errors,\n onChange: (value: unknown) => forma.setFieldValue(fieldPath, value),\n onBlur: () => forma.setFieldTouched(fieldPath),\n // Convenience properties\n visible: true, // Always true since we already filtered for visibility\n enabled: !disabled,\n readonly: isReadonly,\n label: fieldDef.label ?? fieldPath,\n description: fieldDef.description,\n placeholder: fieldDef.placeholder,\n // Adorner properties (only for adornable field types)\n ...(isAdornableField(fieldDef) && {\n prefix: fieldDef.prefix,\n suffix: fieldDef.suffix,\n }),\n // Presentation variant\n variant: fieldDef.variant,\n variantConfig: fieldDef.variantConfig,\n };\n\n // Build type-specific props\n let fieldProps:\n | BaseFieldProps\n | TextFieldProps\n | NumberFieldProps\n | IntegerFieldProps\n | SelectFieldProps\n | MultiSelectFieldProps\n | ArrayFieldProps\n | DisplayFieldProps\n | MatrixFieldProps = baseProps;\n\n if (fieldType === \"number\") {\n const constraints = getNumberConstraints(schemaProperty);\n fieldProps = {\n ...baseProps,\n fieldType: \"number\",\n value: baseProps.value as number | null,\n onChange: baseProps.onChange as (value: number | null) => void,\n ...constraints,\n } as NumberFieldProps;\n } else if (fieldType === \"integer\") {\n const constraints = getNumberConstraints(schemaProperty);\n fieldProps = {\n ...baseProps,\n fieldType: \"integer\",\n value: baseProps.value as number | null,\n onChange: baseProps.onChange as (value: number | null) => void,\n min: constraints.min,\n max: constraints.max,\n } as IntegerFieldProps;\n } else if (fieldType === \"select\") {\n // Use pre-computed visible options from memoized map\n const visibleOptions = (forma.optionsVisibility[fieldPath] ??\n []) as SelectOption[];\n fieldProps = {\n ...baseProps,\n fieldType: \"select\",\n value: baseProps.value as string | null,\n onChange: baseProps.onChange as (value: string | null) => void,\n options: visibleOptions,\n } as SelectFieldProps;\n } else if (fieldType === \"multiselect\") {\n // Use pre-computed visible options from memoized map\n const visibleOptions = (forma.optionsVisibility[fieldPath] ??\n []) as SelectOption[];\n fieldProps = {\n ...baseProps,\n fieldType: \"multiselect\",\n value: (baseProps.value as string[] | undefined) ?? [],\n onChange: baseProps.onChange as (value: string[]) => void,\n options: visibleOptions,\n } as MultiSelectFieldProps;\n } else if (\n fieldType === \"array\" &&\n fieldDef.type === \"array\" &&\n fieldDef.itemFields\n ) {\n const arrayValue = (baseProps.value as unknown[] | undefined) ?? [];\n const minItems = fieldDef.minItems ?? 0;\n const maxItems = fieldDef.maxItems ?? Infinity;\n const itemFieldDefs = fieldDef.itemFields;\n\n const helpers: ArrayHelpers = {\n items: arrayValue,\n push: (item?: unknown) => {\n const newItem = item ?? createDefaultItem(itemFieldDefs);\n forma.setFieldValue(fieldPath, [...arrayValue, newItem]);\n },\n insert: (index: number, item: unknown) => {\n const newArray = [...arrayValue];\n newArray.splice(index, 0, item);\n forma.setFieldValue(fieldPath, newArray);\n },\n remove: (index: number) => {\n const newArray = [...arrayValue];\n newArray.splice(index, 1);\n forma.setFieldValue(fieldPath, newArray);\n },\n move: (from: number, to: number) => {\n const newArray = [...arrayValue];\n const [item] = newArray.splice(from, 1);\n newArray.splice(to, 0, item);\n forma.setFieldValue(fieldPath, newArray);\n },\n swap: (indexA: number, indexB: number) => {\n const newArray = [...arrayValue];\n [newArray[indexA], newArray[indexB]] = [\n newArray[indexB],\n newArray[indexA],\n ];\n forma.setFieldValue(fieldPath, newArray);\n },\n getItemFieldProps: (index: number, fieldName: string) => {\n const itemFieldDef = itemFieldDefs[fieldName];\n const itemPath = `${fieldPath}[${index}].${fieldName}`;\n const item = (arrayValue[index] as Record<string, unknown>) ?? {};\n const itemValue = item[fieldName];\n\n // Use pre-computed visible options from memoized map\n const visibleOptions = forma.optionsVisibility[itemPath] as\n | SelectOption[]\n | undefined;\n\n return {\n name: itemPath,\n value: itemValue,\n type: itemFieldDef?.type ?? \"text\",\n label: itemFieldDef?.label ?? fieldName,\n description: itemFieldDef?.description,\n placeholder: itemFieldDef?.placeholder,\n visible: true,\n enabled: !disabled,\n readonly: forma.readonly[itemPath] ?? false,\n required: itemFieldDef?.requiredWhen === \"true\",\n touched: forma.touched[itemPath] ?? false,\n errors: forma.errors.filter((e) => e.field === itemPath),\n onChange: (value: unknown) => {\n const newArray = [...arrayValue];\n const existingItem = (newArray[index] ?? {}) as Record<\n string,\n unknown\n >;\n newArray[index] = { ...existingItem, [fieldName]: value };\n forma.setFieldValue(fieldPath, newArray);\n },\n onBlur: () => forma.setFieldTouched(itemPath),\n itemIndex: index,\n fieldName,\n options: visibleOptions,\n };\n },\n minItems,\n maxItems,\n canAdd: arrayValue.length < maxItems,\n canRemove: arrayValue.length > minItems,\n };\n fieldProps = {\n ...baseProps,\n fieldType: \"array\",\n value: arrayValue,\n onChange: baseProps.onChange as (value: unknown[]) => void,\n helpers,\n itemFields: itemFieldDefs,\n itemFieldOrder: fieldDef.itemFieldOrder,\n minItems,\n maxItems,\n } as ArrayFieldProps;\n } else if (fieldType === \"matrix\" && fieldDef.type === \"matrix\") {\n // Matrix fields — compute visible rows from visibility engine\n const matrixValue =\n (baseProps.value as Record<string, string | number | string[] | number[]> | null) ?? null;\n const rows = fieldDef.rows.map((row) => ({\n id: row.id,\n label: row.label,\n visible: forma.visibility[`${fieldPath}.${row.id}`] !== false,\n }));\n fieldProps = {\n ...baseProps,\n fieldType: \"matrix\",\n value: matrixValue,\n onChange: baseProps.onChange as (\n value: Record<string, string | number | string[] | number[]>,\n ) => void,\n rows,\n columns: fieldDef.columns,\n multiSelect: fieldDef.multiSelect ?? false,\n } as MatrixFieldProps;\n } else if (fieldType === \"display\" && fieldDef.type === \"display\") {\n // Display fields (read-only presentation content)\n const sourceValue = fieldDef.source\n ? (forma.data[fieldDef.source] ?? forma.computed[fieldDef.source])\n : undefined;\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const {\n onChange: _onChange,\n value: _value,\n ...displayBaseProps\n } = baseProps;\n fieldProps = {\n ...displayBaseProps,\n fieldType: \"display\",\n content: fieldDef.content,\n sourceValue,\n format: fieldDef.format,\n } as DisplayFieldProps;\n } else {\n // Text-based fields\n fieldProps = {\n ...baseProps,\n fieldType: fieldType as\n | \"text\"\n | \"phone\"\n | \"email\"\n | \"password\"\n | \"url\"\n | \"textarea\",\n value: (baseProps.value as string) ?? \"\",\n onChange: baseProps.onChange as (value: string) => void,\n };\n }\n\n // Wrap props in { field, spec } structure for components\n const componentProps = { field: fieldProps, spec };\n const element = React.createElement(\n Component as React.ComponentType<typeof componentProps>,\n componentProps,\n );\n\n if (className) {\n return (\n <div data-field-path={fieldPath} className={className}>\n {element}\n </div>\n );\n }\n\n return <div data-field-path={fieldPath}>{element}</div>;\n}\n","/**\n * FormaErrorBoundary Component\n *\n * Error boundary for catching render errors in form components.\n * Provides graceful error handling and recovery options.\n */\n\nimport React from \"react\";\n\n/**\n * Props for FormaErrorBoundary component\n */\nexport interface FormaErrorBoundaryProps {\n /** Child components to render */\n children: React.ReactNode;\n /** Custom fallback UI to show when an error occurs */\n fallback?:\n | React.ReactNode\n | ((error: Error, reset: () => void) => React.ReactNode);\n /** Callback when an error is caught */\n onError?: (error: Error, errorInfo: React.ErrorInfo) => void;\n /** Key to reset the error boundary (change this to reset) */\n resetKey?: string | number;\n}\n\n/**\n * State for FormaErrorBoundary component\n */\ninterface FormaErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n}\n\n/**\n * Default fallback component shown when an error occurs\n */\nfunction DefaultErrorFallback({\n error,\n onReset,\n}: {\n error: Error;\n onReset: () => void;\n}) {\n return (\n <div className=\"forma-error-boundary\" role=\"alert\">\n <h3>Something went wrong</h3>\n <p>An error occurred while rendering the form.</p>\n <details>\n <summary>Error details</summary>\n <pre>{error.message}</pre>\n </details>\n <button type=\"button\" onClick={onReset}>\n Try again\n </button>\n </div>\n );\n}\n\n/**\n * Error boundary component for Forma forms\n *\n * Catches JavaScript errors in child component tree and displays\n * a fallback UI instead of crashing the entire application.\n *\n * @example\n * ```tsx\n * <FormaErrorBoundary\n * fallback={<div>Form error occurred</div>}\n * onError={(error) => logError(error)}\n * >\n * <FormRenderer spec={spec} components={components} />\n * </FormaErrorBoundary>\n * ```\n */\nexport class FormaErrorBoundary extends React.Component<\n FormaErrorBoundaryProps,\n FormaErrorBoundaryState\n> {\n constructor(props: FormaErrorBoundaryProps) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error: Error): FormaErrorBoundaryState {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {\n this.props.onError?.(error, errorInfo);\n }\n\n componentDidUpdate(prevProps: FormaErrorBoundaryProps): void {\n // Reset error state when resetKey changes\n if (this.state.hasError && prevProps.resetKey !== this.props.resetKey) {\n this.setState({ hasError: false, error: null });\n }\n }\n\n reset = (): void => {\n this.setState({ hasError: false, error: null });\n };\n\n render(): React.ReactNode {\n if (this.state.hasError && this.state.error) {\n const { fallback } = this.props;\n\n if (typeof fallback === \"function\") {\n return fallback(this.state.error, this.reset);\n }\n\n if (fallback) {\n return fallback;\n }\n\n return (\n <DefaultErrorFallback error={this.state.error} onReset={this.reset} />\n );\n }\n\n return this.props.children;\n }\n}\n"],"mappings":"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","names":["schemaProperty","useRef","useMemo","useCallback","isAdornableField","FormRenderer","useRef","useCallback","useMemo","isAdornableField","baseProps","React","isAdornableField","jsx","getNumberConstraints","createDefaultItem","isAdornableField","React","React","jsx","jsxs"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fogpipe/forma-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Headless React form renderer for Forma specifications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"test:coverage": "vitest run --coverage"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@fogpipe/forma-core": "^0.
|
|
37
|
+
"@fogpipe/forma-core": "^0.17.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"react": "^18.0.0 || ^19.0.0"
|
package/src/FieldRenderer.tsx
CHANGED
|
@@ -24,6 +24,7 @@ import type {
|
|
|
24
24
|
ArrayFieldProps,
|
|
25
25
|
ArrayHelpers,
|
|
26
26
|
DisplayFieldProps,
|
|
27
|
+
MatrixFieldProps,
|
|
27
28
|
} from "./types.js";
|
|
28
29
|
|
|
29
30
|
/**
|
|
@@ -175,7 +176,8 @@ export function FieldRenderer({
|
|
|
175
176
|
| SelectFieldProps
|
|
176
177
|
| MultiSelectFieldProps
|
|
177
178
|
| ArrayFieldProps
|
|
178
|
-
| DisplayFieldProps
|
|
179
|
+
| DisplayFieldProps
|
|
180
|
+
| MatrixFieldProps = baseProps;
|
|
179
181
|
|
|
180
182
|
if (fieldType === "number") {
|
|
181
183
|
const constraints = getNumberConstraints(schemaProperty);
|
|
@@ -313,6 +315,26 @@ export function FieldRenderer({
|
|
|
313
315
|
minItems,
|
|
314
316
|
maxItems,
|
|
315
317
|
} as ArrayFieldProps;
|
|
318
|
+
} else if (fieldType === "matrix" && fieldDef.type === "matrix") {
|
|
319
|
+
// Matrix fields — compute visible rows from visibility engine
|
|
320
|
+
const matrixValue =
|
|
321
|
+
(baseProps.value as Record<string, string | number | string[] | number[]> | null) ?? null;
|
|
322
|
+
const rows = fieldDef.rows.map((row) => ({
|
|
323
|
+
id: row.id,
|
|
324
|
+
label: row.label,
|
|
325
|
+
visible: forma.visibility[`${fieldPath}.${row.id}`] !== false,
|
|
326
|
+
}));
|
|
327
|
+
fieldProps = {
|
|
328
|
+
...baseProps,
|
|
329
|
+
fieldType: "matrix",
|
|
330
|
+
value: matrixValue,
|
|
331
|
+
onChange: baseProps.onChange as (
|
|
332
|
+
value: Record<string, string | number | string[] | number[]>,
|
|
333
|
+
) => void,
|
|
334
|
+
rows,
|
|
335
|
+
columns: fieldDef.columns,
|
|
336
|
+
multiSelect: fieldDef.multiSelect ?? false,
|
|
337
|
+
} as MatrixFieldProps;
|
|
316
338
|
} else if (fieldType === "display" && fieldDef.type === "display") {
|
|
317
339
|
// Display fields (read-only presentation content)
|
|
318
340
|
const sourceValue = fieldDef.source
|
|
@@ -337,6 +359,7 @@ export function FieldRenderer({
|
|
|
337
359
|
...baseProps,
|
|
338
360
|
fieldType: fieldType as
|
|
339
361
|
| "text"
|
|
362
|
+
| "phone"
|
|
340
363
|
| "email"
|
|
341
364
|
| "password"
|
|
342
365
|
| "url"
|
package/src/FormRenderer.tsx
CHANGED
|
@@ -34,6 +34,7 @@ import type {
|
|
|
34
34
|
ArrayFieldProps,
|
|
35
35
|
ArrayHelpers,
|
|
36
36
|
DisplayFieldProps,
|
|
37
|
+
MatrixFieldProps,
|
|
37
38
|
} from "./types.js";
|
|
38
39
|
|
|
39
40
|
/**
|
|
@@ -392,7 +393,8 @@ export const FormRenderer = forwardRef<FormRendererHandle, FormRendererProps>(
|
|
|
392
393
|
| NumberFieldProps
|
|
393
394
|
| SelectFieldProps
|
|
394
395
|
| ArrayFieldProps
|
|
395
|
-
| DisplayFieldProps
|
|
396
|
+
| DisplayFieldProps
|
|
397
|
+
| MatrixFieldProps = baseProps;
|
|
396
398
|
|
|
397
399
|
if (fieldType === "number" || fieldType === "integer") {
|
|
398
400
|
const constraints = getNumberConstraints(schemaProperty);
|
|
@@ -483,6 +485,25 @@ export const FormRenderer = forwardRef<FormRendererHandle, FormRendererProps>(
|
|
|
483
485
|
minItems,
|
|
484
486
|
maxItems,
|
|
485
487
|
} as ArrayFieldProps;
|
|
488
|
+
} else if (fieldType === "matrix" && fieldDef.type === "matrix") {
|
|
489
|
+
const matrixValue =
|
|
490
|
+
(baseProps.value as Record<string, string | number | string[] | number[]> | null) ?? null;
|
|
491
|
+
const rows = fieldDef.rows.map((row) => ({
|
|
492
|
+
id: row.id,
|
|
493
|
+
label: row.label,
|
|
494
|
+
visible: formaVisibility[`${fieldPath}.${row.id}`] !== false,
|
|
495
|
+
}));
|
|
496
|
+
fieldProps = {
|
|
497
|
+
...baseProps,
|
|
498
|
+
fieldType: "matrix",
|
|
499
|
+
value: matrixValue,
|
|
500
|
+
onChange: baseProps.onChange as (
|
|
501
|
+
value: Record<string, string | number | string[] | number[]>,
|
|
502
|
+
) => void,
|
|
503
|
+
rows,
|
|
504
|
+
columns: fieldDef.columns,
|
|
505
|
+
multiSelect: fieldDef.multiSelect ?? false,
|
|
506
|
+
} as MatrixFieldProps;
|
|
486
507
|
} else if (fieldType === "display" && fieldDef.type === "display") {
|
|
487
508
|
// Display fields (read-only presentation content)
|
|
488
509
|
// Resolve source value if the display field has a source property
|
|
@@ -508,6 +529,7 @@ export const FormRenderer = forwardRef<FormRendererHandle, FormRendererProps>(
|
|
|
508
529
|
...baseProps,
|
|
509
530
|
fieldType: fieldType as
|
|
510
531
|
| "text"
|
|
532
|
+
| "phone"
|
|
511
533
|
| "email"
|
|
512
534
|
| "password"
|
|
513
535
|
| "url"
|
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
ArrayComponentProps,
|
|
23
23
|
ComponentMap,
|
|
24
24
|
LayoutProps,
|
|
25
|
+
MatrixComponentProps,
|
|
25
26
|
NumberComponentProps,
|
|
26
27
|
IntegerComponentProps,
|
|
27
28
|
} from "../types.js";
|
|
@@ -714,4 +715,185 @@ describe("FieldRenderer", () => {
|
|
|
714
715
|
});
|
|
715
716
|
});
|
|
716
717
|
});
|
|
718
|
+
|
|
719
|
+
// ============================================================================
|
|
720
|
+
// Matrix Field Rendering
|
|
721
|
+
// ============================================================================
|
|
722
|
+
|
|
723
|
+
describe("matrix field rendering", () => {
|
|
724
|
+
it("should pass matrix props to matrix component", () => {
|
|
725
|
+
let capturedProps: MatrixComponentProps["field"] | null = null;
|
|
726
|
+
|
|
727
|
+
const spec: Forma = {
|
|
728
|
+
version: "1.0",
|
|
729
|
+
meta: { id: "test", title: "Test" },
|
|
730
|
+
schema: {
|
|
731
|
+
type: "object",
|
|
732
|
+
properties: {
|
|
733
|
+
rating: {
|
|
734
|
+
type: "object",
|
|
735
|
+
properties: {
|
|
736
|
+
speed: { type: "integer", enum: [1, 2, 3] },
|
|
737
|
+
quality: { type: "integer", enum: [1, 2, 3] },
|
|
738
|
+
},
|
|
739
|
+
},
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
fields: {
|
|
743
|
+
rating: {
|
|
744
|
+
type: "matrix",
|
|
745
|
+
label: "Rating",
|
|
746
|
+
rows: [
|
|
747
|
+
{ id: "speed", label: "Speed" },
|
|
748
|
+
{ id: "quality", label: "Quality" },
|
|
749
|
+
],
|
|
750
|
+
columns: [
|
|
751
|
+
{ value: 1, label: "Poor" },
|
|
752
|
+
{ value: 2, label: "OK" },
|
|
753
|
+
{ value: 3, label: "Great" },
|
|
754
|
+
],
|
|
755
|
+
multiSelect: false,
|
|
756
|
+
},
|
|
757
|
+
},
|
|
758
|
+
fieldOrder: ["rating"],
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
const components: ComponentMap = {
|
|
762
|
+
...createTestComponentMap(),
|
|
763
|
+
matrix: ({ field: props }: MatrixComponentProps) => {
|
|
764
|
+
capturedProps = props;
|
|
765
|
+
return <div data-testid="matrix-field">matrix</div>;
|
|
766
|
+
},
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
render(
|
|
770
|
+
<FormRenderer
|
|
771
|
+
spec={spec}
|
|
772
|
+
initialData={{ rating: { speed: 2, quality: 3 } }}
|
|
773
|
+
components={components}
|
|
774
|
+
/>,
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
expect(capturedProps).not.toBeNull();
|
|
778
|
+
expect(capturedProps!.fieldType).toBe("matrix");
|
|
779
|
+
expect(capturedProps!.value).toEqual({ speed: 2, quality: 3 });
|
|
780
|
+
expect(capturedProps!.rows).toEqual([
|
|
781
|
+
{ id: "speed", label: "Speed", visible: true },
|
|
782
|
+
{ id: "quality", label: "Quality", visible: true },
|
|
783
|
+
]);
|
|
784
|
+
expect(capturedProps!.columns).toEqual([
|
|
785
|
+
{ value: 1, label: "Poor" },
|
|
786
|
+
{ value: 2, label: "OK" },
|
|
787
|
+
{ value: 3, label: "Great" },
|
|
788
|
+
]);
|
|
789
|
+
expect(capturedProps!.multiSelect).toBe(false);
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
it("should compute row visibility from visibleWhen expressions", () => {
|
|
793
|
+
let capturedProps: MatrixComponentProps["field"] | null = null;
|
|
794
|
+
|
|
795
|
+
const spec: Forma = {
|
|
796
|
+
version: "1.0",
|
|
797
|
+
meta: { id: "test", title: "Test" },
|
|
798
|
+
schema: {
|
|
799
|
+
type: "object",
|
|
800
|
+
properties: {
|
|
801
|
+
show_quality: { type: "boolean" },
|
|
802
|
+
rating: {
|
|
803
|
+
type: "object",
|
|
804
|
+
properties: {
|
|
805
|
+
speed: { type: "integer", enum: [1, 2, 3] },
|
|
806
|
+
quality: { type: "integer", enum: [1, 2, 3] },
|
|
807
|
+
},
|
|
808
|
+
},
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
fields: {
|
|
812
|
+
show_quality: { type: "boolean", label: "Show Quality" },
|
|
813
|
+
rating: {
|
|
814
|
+
type: "matrix",
|
|
815
|
+
label: "Rating",
|
|
816
|
+
rows: [
|
|
817
|
+
{ id: "speed", label: "Speed" },
|
|
818
|
+
{ id: "quality", label: "Quality", visibleWhen: "show_quality = true" },
|
|
819
|
+
],
|
|
820
|
+
columns: [
|
|
821
|
+
{ value: 1, label: "Poor" },
|
|
822
|
+
{ value: 2, label: "OK" },
|
|
823
|
+
{ value: 3, label: "Great" },
|
|
824
|
+
],
|
|
825
|
+
},
|
|
826
|
+
},
|
|
827
|
+
fieldOrder: ["show_quality", "rating"],
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
const components: ComponentMap = {
|
|
831
|
+
...createTestComponentMap(),
|
|
832
|
+
matrix: ({ field: props }: MatrixComponentProps) => {
|
|
833
|
+
capturedProps = props;
|
|
834
|
+
return <div data-testid="matrix-field">matrix</div>;
|
|
835
|
+
},
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
render(
|
|
839
|
+
<FormRenderer
|
|
840
|
+
spec={spec}
|
|
841
|
+
initialData={{ show_quality: false }}
|
|
842
|
+
components={components}
|
|
843
|
+
/>,
|
|
844
|
+
);
|
|
845
|
+
|
|
846
|
+
expect(capturedProps).not.toBeNull();
|
|
847
|
+
expect(capturedProps!.rows).toEqual([
|
|
848
|
+
{ id: "speed", label: "Speed", visible: true },
|
|
849
|
+
{ id: "quality", label: "Quality", visible: false },
|
|
850
|
+
]);
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
it("should default multiSelect to false when not specified", () => {
|
|
854
|
+
let capturedProps: MatrixComponentProps["field"] | null = null;
|
|
855
|
+
|
|
856
|
+
const spec: Forma = {
|
|
857
|
+
version: "1.0",
|
|
858
|
+
meta: { id: "test", title: "Test" },
|
|
859
|
+
schema: {
|
|
860
|
+
type: "object",
|
|
861
|
+
properties: {
|
|
862
|
+
rating: {
|
|
863
|
+
type: "object",
|
|
864
|
+
properties: {
|
|
865
|
+
a: { type: "integer", enum: [1, 2] },
|
|
866
|
+
},
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
},
|
|
870
|
+
fields: {
|
|
871
|
+
rating: {
|
|
872
|
+
type: "matrix",
|
|
873
|
+
label: "Rating",
|
|
874
|
+
rows: [{ id: "a", label: "A" }],
|
|
875
|
+
columns: [
|
|
876
|
+
{ value: 1, label: "1" },
|
|
877
|
+
{ value: 2, label: "2" },
|
|
878
|
+
],
|
|
879
|
+
// multiSelect not set
|
|
880
|
+
},
|
|
881
|
+
},
|
|
882
|
+
fieldOrder: ["rating"],
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
const components: ComponentMap = {
|
|
886
|
+
...createTestComponentMap(),
|
|
887
|
+
matrix: ({ field: props }: MatrixComponentProps) => {
|
|
888
|
+
capturedProps = props;
|
|
889
|
+
return <div data-testid="matrix-field">matrix</div>;
|
|
890
|
+
},
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
render(<FormRenderer spec={spec} components={components} />);
|
|
894
|
+
|
|
895
|
+
expect(capturedProps).not.toBeNull();
|
|
896
|
+
expect(capturedProps!.multiSelect).toBe(false);
|
|
897
|
+
});
|
|
898
|
+
});
|
|
717
899
|
});
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
Forma,
|
|
7
7
|
FieldDefinition,
|
|
8
8
|
FieldError,
|
|
9
|
+
MatrixColumn,
|
|
9
10
|
SelectOption,
|
|
10
11
|
} from "@fogpipe/forma-core";
|
|
11
12
|
|
|
@@ -61,7 +62,7 @@ export interface TextFieldProps extends Omit<
|
|
|
61
62
|
BaseFieldProps,
|
|
62
63
|
"value" | "onChange"
|
|
63
64
|
> {
|
|
64
|
-
fieldType: "text" | "email" | "password" | "url" | "textarea";
|
|
65
|
+
fieldType: "text" | "phone" | "email" | "password" | "url" | "textarea";
|
|
65
66
|
value: string;
|
|
66
67
|
onChange: (value: string) => void;
|
|
67
68
|
}
|
|
@@ -311,6 +312,25 @@ export interface DisplayFieldProps extends Omit<
|
|
|
311
312
|
value?: never;
|
|
312
313
|
}
|
|
313
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Props for matrix/grid fields
|
|
317
|
+
*/
|
|
318
|
+
export interface MatrixFieldProps extends Omit<
|
|
319
|
+
BaseFieldProps,
|
|
320
|
+
"value" | "onChange"
|
|
321
|
+
> {
|
|
322
|
+
fieldType: "matrix";
|
|
323
|
+
/** Current matrix value: row ID → selected column value(s) */
|
|
324
|
+
value: Record<string, string | number | string[] | number[]> | null;
|
|
325
|
+
onChange: (value: Record<string, string | number | string[] | number[]>) => void;
|
|
326
|
+
/** Row definitions with visibility state */
|
|
327
|
+
rows: Array<{ id: string; label: string; visible: boolean }>;
|
|
328
|
+
/** Column definitions (shared options for all rows) */
|
|
329
|
+
columns: MatrixColumn[];
|
|
330
|
+
/** Whether multiple selections per row are allowed */
|
|
331
|
+
multiSelect: boolean;
|
|
332
|
+
}
|
|
333
|
+
|
|
314
334
|
/**
|
|
315
335
|
* Union of all field prop types
|
|
316
336
|
*/
|
|
@@ -326,7 +346,8 @@ export type FieldProps =
|
|
|
326
346
|
| ArrayFieldProps
|
|
327
347
|
| ObjectFieldProps
|
|
328
348
|
| ComputedFieldProps
|
|
329
|
-
| DisplayFieldProps
|
|
349
|
+
| DisplayFieldProps
|
|
350
|
+
| MatrixFieldProps;
|
|
330
351
|
|
|
331
352
|
/**
|
|
332
353
|
* Map of field types to React components
|
|
@@ -334,6 +355,7 @@ export type FieldProps =
|
|
|
334
355
|
*/
|
|
335
356
|
export interface ComponentMap {
|
|
336
357
|
text?: React.ComponentType<TextComponentProps>;
|
|
358
|
+
phone?: React.ComponentType<TextComponentProps>;
|
|
337
359
|
email?: React.ComponentType<TextComponentProps>;
|
|
338
360
|
password?: React.ComponentType<TextComponentProps>;
|
|
339
361
|
url?: React.ComponentType<TextComponentProps>;
|
|
@@ -349,6 +371,7 @@ export interface ComponentMap {
|
|
|
349
371
|
object?: React.ComponentType<ObjectComponentProps>;
|
|
350
372
|
computed?: React.ComponentType<ComputedComponentProps>;
|
|
351
373
|
display?: React.ComponentType<DisplayComponentProps>;
|
|
374
|
+
matrix?: React.ComponentType<MatrixComponentProps>;
|
|
352
375
|
fallback?: React.ComponentType<FieldComponentProps>;
|
|
353
376
|
}
|
|
354
377
|
|
|
@@ -461,6 +484,11 @@ export interface DisplayComponentProps {
|
|
|
461
484
|
spec: Forma;
|
|
462
485
|
}
|
|
463
486
|
|
|
487
|
+
export interface MatrixComponentProps {
|
|
488
|
+
field: MatrixFieldProps;
|
|
489
|
+
spec: Forma;
|
|
490
|
+
}
|
|
491
|
+
|
|
464
492
|
/**
|
|
465
493
|
* Generic field component props (for fallback/dynamic components)
|
|
466
494
|
*/
|