@fogpipe/forma-react 0.9.0 → 0.10.1
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 +11 -1
- package/dist/index.js +65 -89
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/FormRenderer.tsx +38 -84
- package/src/__tests__/FormRenderer.test.tsx +167 -0
- package/src/__tests__/canProceed.test.ts +11 -8
- package/src/__tests__/diabetes-trial-flow.test.ts +41 -24
- package/src/__tests__/null-handling.test.ts +53 -50
- package/src/__tests__/useForma.test.ts +129 -0
- package/src/types.ts +11 -1
- package/src/useForma.ts +28 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useForma.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 { useCallback, useEffect, useMemo, useReducer, useRef, useState } from \"react\";\nimport type { Forma, FieldError, ValidationResult } from \"@fogpipe/forma-core\";\nimport type { GetFieldPropsResult, GetSelectFieldPropsResult, GetArrayHelpersResult } from \"./types.js\";\nimport {\n getVisibility,\n getRequired,\n getEnabled,\n validate,\n calculate,\n getPageVisibility,\n} 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?: (data: Record<string, unknown>, computed?: Record<string, unknown>) => 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\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 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 // 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 * Main Forma hook\n */\nexport function useForma(options: UseFormaOptions): UseFormaReturn {\n const { spec: inputSpec, initialData = {}, onSubmit, onChange, validateOn = \"blur\", referenceData, validationDebounceMs = 0 } = 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: initialData,\n touched: {},\n isSubmitting: false,\n isSubmitted: false,\n isDirty: false,\n currentPage: 0,\n });\n\n // Track if we've initialized (to avoid calling onChange on first render)\n const hasInitialized = useRef(false);\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 // 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] = 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 = 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((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 = (data: Record<string, unknown>, pathParts: string[], val: unknown): 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({ type: \"SET_VALUES\", values: buildNestedObject(state.data, parts, value) });\n }, [state.data]);\n\n // Actions\n const setFieldValue = useCallback(\n (path: string, value: unknown) => {\n setNestedValue(path, value);\n if (validateOn === \"change\") {\n dispatch({ type: \"SET_FIELD_TOUCHED\", field: path, touched: true });\n }\n },\n [validateOn, setNestedValue]\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((values: Record<string, unknown>) => {\n dispatch({ type: \"SET_VALUES\", values });\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 try {\n // Always use immediate validation on submit to ensure accurate result\n if (immediateValidation.valid && onSubmit) {\n await onSubmit(state.data);\n }\n dispatch({ type: \"SET_SUBMITTED\", isSubmitted: true });\n } finally {\n dispatch({ type: \"SET_SUBMITTING\", isSubmitting: false });\n }\n }, [immediateValidation, onSubmit, state.data]);\n\n const resetForm = useCallback(() => {\n dispatch({ type: \"RESET\", initialData });\n }, [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(Math.max(0, state.currentPage), maxPageIndex);\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 // Clamp to valid range\n const validIndex = Math.min(Math.max(0, index), maxPageIndex);\n dispatch({ type: \"SET_PAGE\", page: validIndex });\n },\n nextPage: () => {\n if (hasNextPage) {\n dispatch({ type: \"SET_PAGE\", page: clampedPageIndex + 1 });\n }\n },\n previousPage: () => {\n if (hasPreviousPage) {\n dispatch({ type: \"SET_PAGE\", page: clampedPageIndex - 1 });\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 = 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]);\n\n // Helper to get value at nested path\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 = state.data;\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 }, [state.data]);\n\n // Helper to set value at nested path\n const setValueAtPath = useCallback((path: string, value: unknown): void => {\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\n const newData = { ...state.data };\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 }, [state.data]);\n\n // Memoized onChange/onBlur handlers for fields\n const fieldHandlers = useRef<Map<string, { onChange: (value: unknown) => void; onBlur: () => void }>>(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?.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((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 }, [setValueAtPath, setFieldTouched]);\n\n // Get field props for any field\n const getFieldProps = useCallback((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) fieldType = \"select\";\n else if (\"format\" in schemaProperty) {\n if (schemaProperty.format === \"date\") fieldType = \"date\";\n else if (schemaProperty.format === \"date-time\") 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 = validateOn === \"change\" || (validateOn === \"blur\" && isTouched) || state.isSubmitted;\n const displayedErrors = showErrors ? fieldErrors : [];\n const hasErrors = displayedErrors.length > 0;\n const isRequired = required[path] ?? false;\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 required: isRequired,\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 };\n }, [spec, state.touched, state.isSubmitted, visibility, enabled, required, validation.errors, validateOn, getValueAtPath, getFieldHandlers]);\n\n // Get select field props\n const getSelectFieldProps = useCallback((path: string): GetSelectFieldPropsResult => {\n const baseProps = getFieldProps(path);\n const fieldDef = spec.fields[path];\n\n return {\n ...baseProps,\n options: fieldDef?.options ?? [],\n };\n }, [getFieldProps, spec.fields]);\n\n // Get array helpers\n const getArrayHelpers = useCallback((path: string): GetArrayHelpersResult => {\n const fieldDef = spec.fields[path];\n const currentValue = (getValueAtPath(path) as unknown[]) ?? [];\n const minItems = fieldDef?.minItems ?? 0;\n const maxItems = fieldDef?.maxItems ?? Infinity;\n\n const canAdd = currentValue.length < maxItems;\n const canRemove = currentValue.length > minItems;\n\n const getItemFieldProps = (index: number, fieldName: string): GetFieldPropsResult => {\n const itemPath = `${path}[${index}].${fieldName}`;\n const itemFieldDef = fieldDef?.itemFields?.[fieldName];\n const handlers = getFieldHandlers(itemPath);\n\n // Get item value\n const item = currentValue[index] as Record<string, unknown> | undefined;\n const itemValue = item?.[fieldName];\n\n const fieldErrors = validation.errors.filter((e) => e.field === itemPath);\n const isTouched = state.touched[itemPath] ?? false;\n const showErrors = validateOn === \"change\" || (validateOn === \"blur\" && isTouched) || state.isSubmitted;\n\n return {\n name: itemPath,\n value: itemValue,\n type: itemFieldDef?.type || \"text\",\n label: itemFieldDef?.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1),\n description: itemFieldDef?.description,\n placeholder: itemFieldDef?.placeholder,\n visible: true,\n enabled: enabled[path] !== false,\n required: false, // TODO: Evaluate item field required\n touched: isTouched,\n errors: showErrors ? fieldErrors : [],\n onChange: handlers.onChange,\n onBlur: handlers.onBlur,\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]] = [newArray[indexB], newArray[indexA]];\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 }, [spec.fields, getValueAtPath, setValueAtPath, getFieldHandlers, enabled, state.touched, state.isSubmitted, validation.errors, validateOn]);\n\n return {\n data: state.data,\n computed,\n visibility,\n required,\n enabled,\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 getFieldProps,\n getSelectFieldProps,\n getArrayHelpers,\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, { forwardRef, useImperativeHandle, useRef, useMemo, useCallback } from \"react\";\nimport type { Forma, FieldDefinition, ValidationResult, JSONSchemaProperty } from \"@fogpipe/forma-core\";\nimport { useForma } from \"./useForma.js\";\nimport { FormaContext } from \"./context.js\";\nimport type { ComponentMap, LayoutProps, FieldWrapperProps, PageWrapperProps, BaseFieldProps, TextFieldProps, NumberFieldProps, SelectFieldProps, ArrayFieldProps, ArrayHelpers } 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?: (data: Record<string, unknown>, computed?: Record<string, unknown>) => 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({ fieldPath, field, children, errors, required, visible }: FieldWrapperProps) {\n if (!visible) return null;\n\n const errorId = `${fieldPath}-error`;\n const descriptionId = field.description ? `${fieldPath}-description` : 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 {required && <span className=\"required\" aria-hidden=\"true\">*</span>}\n {required && <span className=\"sr-only\"> (required)</span>}\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({ title, description, children }: 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): { min?: number; max?: number; step?: number } {\n if (!schema) return {};\n if (schema.type !== \"number\" && schema.type !== \"integer\") return {};\n\n // Extract min/max from schema\n const min = \"minimum\" in schema && typeof schema.minimum === \"number\" ? schema.minimum : undefined;\n const max = \"maximum\" in schema && typeof schema.maximum === \"number\" ? schema.maximum : 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(itemFields: Record<string, FieldDefinition>): Record<string, unknown> {\n const item: Record<string, unknown> = {};\n for (const [fieldName, fieldDef] of Object.entries(itemFields)) {\n 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 // Cache for array helper functions to prevent recreation on every render\n const arrayHelpersCache = useRef<Map<string, {\n push: (item?: unknown) => void;\n insert: (index: number, item: unknown) => void;\n remove: (index: number) => void;\n move: (from: number, to: number) => void;\n swap: (indexA: number, indexB: number) => void;\n }>>(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 // 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((fieldPath: string) => {\n const fieldDef = spec.fields[fieldPath];\n if (!fieldDef) return null;\n\n const isVisible = forma.visibility[fieldPath] !== false;\n if (!isVisible) return null;\n\n // Infer field type\n const fieldType = fieldDef.type || (fieldDef.itemFields ? \"array\" : \"text\");\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 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 label: fieldDef.label ?? fieldPath,\n description: fieldDef.description,\n placeholder: fieldDef.placeholder,\n };\n\n // Build type-specific props\n let fieldProps: BaseFieldProps | TextFieldProps | NumberFieldProps | SelectFieldProps | ArrayFieldProps = 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 fieldProps = {\n ...baseProps,\n fieldType,\n value: baseProps.value as string | string[] | null,\n onChange: baseProps.onChange as (value: string | string[] | null) => void,\n options: fieldDef.options ?? [],\n } as SelectFieldProps;\n } else if (fieldType === \"array\" && fieldDef.itemFields) {\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 // Get or create cached helper functions for this array field\n // These functions read current values when called, not when created\n if (!arrayHelpersCache.current.has(fieldPath)) {\n arrayHelpersCache.current.set(fieldPath, {\n push: (item?: unknown) => {\n const currentArray = (forma.data[fieldPath] as unknown[] | undefined) ?? [];\n const newItem = item ?? createDefaultItem(itemFieldDefs);\n forma.setFieldValue(fieldPath, [...currentArray, newItem]);\n },\n insert: (index: number, item: unknown) => {\n const currentArray = (forma.data[fieldPath] as unknown[] | undefined) ?? [];\n const newArray = [...currentArray];\n newArray.splice(index, 0, item);\n forma.setFieldValue(fieldPath, newArray);\n },\n remove: (index: number) => {\n const currentArray = (forma.data[fieldPath] as unknown[] | undefined) ?? [];\n const newArray = [...currentArray];\n newArray.splice(index, 1);\n forma.setFieldValue(fieldPath, newArray);\n },\n move: (from: number, to: number) => {\n const currentArray = (forma.data[fieldPath] as unknown[] | undefined) ?? [];\n const newArray = [...currentArray];\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 currentArray = (forma.data[fieldPath] as unknown[] | undefined) ?? [];\n const newArray = [...currentArray];\n [newArray[indexA], newArray[indexB]] = [newArray[indexB], newArray[indexA]];\n forma.setFieldValue(fieldPath, newArray);\n },\n });\n }\n const cachedHelpers = arrayHelpersCache.current.get(fieldPath)!;\n\n const helpers: ArrayHelpers = {\n items: arrayValue,\n push: cachedHelpers.push,\n insert: cachedHelpers.insert,\n remove: cachedHelpers.remove,\n move: cachedHelpers.move,\n swap: cachedHelpers.swap,\n getItemFieldProps: (index: number, fieldName: string) => {\n const itemFieldDef = itemFieldDefs[fieldName];\n const itemPath = `${fieldPath}[${index}].${fieldName}`;\n const itemValue = (arrayValue[index] as Record<string, unknown>)?.[fieldName];\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 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 currentArray = (forma.data[fieldPath] as unknown[] | undefined) ?? [];\n const newArray = [...currentArray];\n const item = (newArray[index] ?? {}) as Record<string, unknown>;\n newArray[index] = { ...item, [fieldName]: value };\n forma.setFieldValue(fieldPath, newArray);\n },\n onBlur: () => forma.setFieldTouched(itemPath),\n itemIndex: index,\n fieldName,\n options: itemFieldDef?.options,\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 minItems,\n maxItems,\n } as ArrayFieldProps;\n } else {\n // Text-based fields\n fieldProps = {\n ...baseProps,\n fieldType: fieldType as \"text\" | \"email\" | \"password\" | \"url\" | \"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 <FieldWrapper\n key={fieldPath}\n fieldPath={fieldPath}\n field={fieldDef}\n errors={errors}\n touched={touched}\n required={required}\n visible={isVisible}\n >\n {React.createElement(Component as React.ComponentType<typeof componentProps>, componentProps)}\n </FieldWrapper>\n );\n }, [spec, forma, components, FieldWrapper]);\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(\"useFormaContext must be used within a FormaContext.Provider\");\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 { FieldDefinition, JSONSchemaProperty } 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} 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): { min?: number; max?: number; step?: number } {\n if (!schema) return {};\n if (schema.type !== \"number\" && schema.type !== \"integer\") return {};\n\n // Extract min/max from schema\n const min = \"minimum\" in schema && typeof schema.minimum === \"number\" ? schema.minimum : undefined;\n const max = \"maximum\" in schema && typeof schema.maximum === \"number\" ? schema.maximum : 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(itemFields: Record<string, FieldDefinition>): Record<string, unknown> {\n const item: Record<string, unknown> = {};\n for (const [fieldName, fieldDef] of Object.entries(itemFields)) {\n 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({ fieldPath, components, className }: 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) return null;\n\n // Infer field type\n const fieldType = fieldDef.type || (fieldDef.itemFields ? \"array\" : \"text\");\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 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 label: fieldDef.label ?? fieldPath,\n description: fieldDef.description,\n placeholder: fieldDef.placeholder,\n };\n\n // Build type-specific props\n let fieldProps: BaseFieldProps | TextFieldProps | NumberFieldProps | IntegerFieldProps | SelectFieldProps | MultiSelectFieldProps | ArrayFieldProps = 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 fieldProps = {\n ...baseProps,\n fieldType: \"select\",\n value: baseProps.value as string | null,\n onChange: baseProps.onChange as (value: string | null) => void,\n options: fieldDef.options ?? [],\n } as SelectFieldProps;\n } else if (fieldType === \"multiselect\") {\n fieldProps = {\n ...baseProps,\n fieldType: \"multiselect\",\n value: (baseProps.value as string[] | undefined) ?? [],\n onChange: baseProps.onChange as (value: string[]) => void,\n options: fieldDef.options ?? [],\n } as MultiSelectFieldProps;\n } else if (fieldType === \"array\" && fieldDef.itemFields) {\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]] = [newArray[indexB], newArray[indexA]];\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 itemValue = (arrayValue[index] as Record<string, unknown>)?.[fieldName];\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 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 item = (newArray[index] ?? {}) as Record<string, unknown>;\n newArray[index] = { ...item, [fieldName]: value };\n forma.setFieldValue(fieldPath, newArray);\n },\n onBlur: () => forma.setFieldTouched(itemPath),\n itemIndex: index,\n fieldName,\n options: itemFieldDef?.options,\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 minItems,\n maxItems,\n } as ArrayFieldProps;\n } else {\n // Text-based fields\n fieldProps = {\n ...baseProps,\n fieldType: fieldType as \"text\" | \"email\" | \"password\" | \"url\" | \"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(Component as React.ComponentType<typeof componentProps>, componentProps);\n\n if (className) {\n return <div className={className}>{element}</div>;\n }\n\n return element;\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?: React.ReactNode | ((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({ error, onReset }: { error: Error; onReset: () => void }) {\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 (\n this.state.hasError &&\n prevProps.resetKey !== this.props.resetKey\n ) {\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 <DefaultErrorFallback error={this.state.error} onReset={this.reset} />;\n }\n\n return this.props.children;\n }\n}\n"],"mappings":"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","names":["useRef","useMemo","useCallback","FormRenderer","useRef","useCallback","useMemo","React","jsx","getNumberConstraints","createDefaultItem","React","React","jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../src/useForma.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 { useCallback, useEffect, useMemo, useReducer, useRef, useState } from \"react\";\nimport type { Forma, FieldError, ValidationResult } from \"@fogpipe/forma-core\";\nimport type { GetFieldPropsResult, GetSelectFieldPropsResult, GetArrayHelpersResult } from \"./types.js\";\nimport {\n getVisibility,\n getRequired,\n getEnabled,\n validate,\n calculate,\n getPageVisibility,\n} 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?: (data: Record<string, unknown>, computed?: Record<string, unknown>) => 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\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 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 // 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 * Main Forma hook\n */\nexport function useForma(options: UseFormaOptions): UseFormaReturn {\n const { spec: inputSpec, initialData = {}, onSubmit, onChange, validateOn = \"blur\", referenceData, validationDebounceMs = 0 } = 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: { ...getDefaultBooleanValues(spec), ...initialData }, // Boolean defaults merged UNDER initialData\n touched: {},\n isSubmitting: false,\n isSubmitted: false,\n isDirty: false,\n currentPage: 0,\n });\n\n // Track if we've initialized (to avoid calling onChange on first render)\n const hasInitialized = useRef(false);\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 // 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] = 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 = 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((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 = (data: Record<string, unknown>, pathParts: string[], val: unknown): 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({ type: \"SET_VALUES\", values: buildNestedObject(state.data, parts, value) });\n }, [state.data]);\n\n // Actions\n const setFieldValue = useCallback(\n (path: string, value: unknown) => {\n setNestedValue(path, value);\n if (validateOn === \"change\") {\n dispatch({ type: \"SET_FIELD_TOUCHED\", field: path, touched: true });\n }\n },\n [validateOn, setNestedValue]\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((values: Record<string, unknown>) => {\n dispatch({ type: \"SET_VALUES\", values });\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 try {\n // Always use immediate validation on submit to ensure accurate result\n if (immediateValidation.valid && onSubmit) {\n await onSubmit(state.data);\n }\n dispatch({ type: \"SET_SUBMITTED\", isSubmitted: true });\n } finally {\n dispatch({ type: \"SET_SUBMITTING\", isSubmitting: false });\n }\n }, [immediateValidation, onSubmit, state.data]);\n\n const resetForm = useCallback(() => {\n dispatch({ type: \"RESET\", initialData });\n }, [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(Math.max(0, state.currentPage), maxPageIndex);\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 // Clamp to valid range\n const validIndex = Math.min(Math.max(0, index), maxPageIndex);\n dispatch({ type: \"SET_PAGE\", page: validIndex });\n },\n nextPage: () => {\n if (hasNextPage) {\n dispatch({ type: \"SET_PAGE\", page: clampedPageIndex + 1 });\n }\n },\n previousPage: () => {\n if (hasPreviousPage) {\n dispatch({ type: \"SET_PAGE\", page: clampedPageIndex - 1 });\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 = 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]);\n\n // Helper to get value at nested path\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 = state.data;\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 }, [state.data]);\n\n // Helper to set value at nested path\n const setValueAtPath = useCallback((path: string, value: unknown): void => {\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\n const newData = { ...state.data };\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 }, [state.data]);\n\n // Memoized onChange/onBlur handlers for fields\n const fieldHandlers = useRef<Map<string, { onChange: (value: unknown) => void; onBlur: () => void }>>(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?.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((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 }, [setValueAtPath, setFieldTouched]);\n\n // Get field props for any field\n const getFieldProps = useCallback((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) fieldType = \"select\";\n else if (\"format\" in schemaProperty) {\n if (schemaProperty.format === \"date\") fieldType = \"date\";\n else if (schemaProperty.format === \"date-time\") 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 = validateOn === \"change\" || (validateOn === \"blur\" && isTouched) || 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 = schemaProperty?.type === \"boolean\" || fieldDef?.type === \"boolean\";\n const hasValidationRules = (fieldDef?.validations?.length ?? 0) > 0;\n const showRequiredIndicator = isRequired && (!isBooleanField || hasValidationRules);\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 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 };\n }, [spec, state.touched, state.isSubmitted, visibility, enabled, required, validation.errors, validateOn, getValueAtPath, getFieldHandlers]);\n\n // Get select field props\n const getSelectFieldProps = useCallback((path: string): GetSelectFieldPropsResult => {\n const baseProps = getFieldProps(path);\n const fieldDef = spec.fields[path];\n\n return {\n ...baseProps,\n options: fieldDef?.options ?? [],\n };\n }, [getFieldProps, spec.fields]);\n\n // Get array helpers\n const getArrayHelpers = useCallback((path: string): GetArrayHelpersResult => {\n const fieldDef = spec.fields[path];\n const currentValue = (getValueAtPath(path) as unknown[]) ?? [];\n const minItems = fieldDef?.minItems ?? 0;\n const maxItems = fieldDef?.maxItems ?? Infinity;\n\n const canAdd = currentValue.length < maxItems;\n const canRemove = currentValue.length > minItems;\n\n const getItemFieldProps = (index: number, fieldName: string): GetFieldPropsResult => {\n const itemPath = `${path}[${index}].${fieldName}`;\n const itemFieldDef = fieldDef?.itemFields?.[fieldName];\n const handlers = getFieldHandlers(itemPath);\n\n // Get item value\n const item = currentValue[index] as Record<string, unknown> | undefined;\n const itemValue = item?.[fieldName];\n\n const fieldErrors = validation.errors.filter((e) => e.field === itemPath);\n const isTouched = state.touched[itemPath] ?? false;\n const showErrors = validateOn === \"change\" || (validateOn === \"blur\" && isTouched) || state.isSubmitted;\n\n return {\n name: itemPath,\n value: itemValue,\n type: itemFieldDef?.type || \"text\",\n label: itemFieldDef?.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1),\n description: itemFieldDef?.description,\n placeholder: itemFieldDef?.placeholder,\n visible: true,\n enabled: enabled[path] !== 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 };\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]] = [newArray[indexB], newArray[indexA]];\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 }, [spec.fields, getValueAtPath, setValueAtPath, getFieldHandlers, enabled, state.touched, state.isSubmitted, validation.errors, validateOn]);\n\n return {\n data: state.data,\n computed,\n visibility,\n required,\n enabled,\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 getFieldProps,\n getSelectFieldProps,\n getArrayHelpers,\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, { forwardRef, useImperativeHandle, useRef, useMemo, useCallback } from \"react\";\nimport type { Forma, FieldDefinition, ValidationResult, JSONSchemaProperty } from \"@fogpipe/forma-core\";\nimport { useForma } from \"./useForma.js\";\nimport { FormaContext } from \"./context.js\";\nimport type { ComponentMap, LayoutProps, FieldWrapperProps, PageWrapperProps, BaseFieldProps, TextFieldProps, NumberFieldProps, SelectFieldProps, ArrayFieldProps, ArrayHelpers } 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?: (data: Record<string, unknown>, computed?: Record<string, unknown>) => 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({ fieldPath, field, children, errors, showRequiredIndicator, visible }: FieldWrapperProps) {\n if (!visible) return null;\n\n const errorId = `${fieldPath}-error`;\n const descriptionId = field.description ? `${fieldPath}-description` : 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 && <span className=\"required\" aria-hidden=\"true\">*</span>}\n {showRequiredIndicator && <span className=\"sr-only\"> (required)</span>}\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({ title, description, children }: 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): { min?: number; max?: number; step?: number } {\n if (!schema) return {};\n if (schema.type !== \"number\" && schema.type !== \"integer\") return {};\n\n // Extract min/max from schema\n const min = \"minimum\" in schema && typeof schema.minimum === \"number\" ? schema.minimum : undefined;\n const max = \"maximum\" in schema && typeof schema.maximum === \"number\" ? schema.maximum : 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(itemFields: Record<string, FieldDefinition>): Record<string, unknown> {\n const item: Record<string, unknown> = {};\n for (const [fieldName, fieldDef] of Object.entries(itemFields)) {\n 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 // 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((fieldPath: string) => {\n const fieldDef = spec.fields[fieldPath];\n if (!fieldDef) return null;\n\n const isVisible = forma.visibility[fieldPath] !== false;\n if (!isVisible) return null;\n\n // Infer field type\n const fieldType = fieldDef.type || (fieldDef.itemFields ? \"array\" : \"text\");\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 // 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 = schemaProperty?.type === \"boolean\" || fieldDef?.type === \"boolean\";\n const hasValidationRules = (fieldDef?.validations?.length ?? 0) > 0;\n const showRequiredIndicator = required && (!isBooleanField || hasValidationRules);\n\n // Base field props\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 label: fieldDef.label ?? fieldPath,\n description: fieldDef.description,\n placeholder: fieldDef.placeholder,\n };\n\n // Build type-specific props\n let fieldProps: BaseFieldProps | TextFieldProps | NumberFieldProps | SelectFieldProps | ArrayFieldProps = 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 fieldProps = {\n ...baseProps,\n fieldType,\n value: baseProps.value as string | string[] | null,\n onChange: baseProps.onChange as (value: string | string[] | null) => void,\n options: fieldDef.options ?? [],\n } as SelectFieldProps;\n } else if (fieldType === \"array\" && fieldDef.itemFields) {\n const arrayValue = Array.isArray(baseProps.value) ? baseProps.value : [];\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 = forma.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 = (index: number, fieldName: string) => {\n const baseProps = baseHelpers.getItemFieldProps(index, fieldName);\n const itemFieldDef = itemFieldDefs[fieldName];\n return {\n ...baseProps,\n itemIndex: index,\n fieldName,\n options: itemFieldDef?.options,\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 minItems,\n maxItems,\n } as ArrayFieldProps;\n } else {\n // Text-based fields\n fieldProps = {\n ...baseProps,\n fieldType: fieldType as \"text\" | \"email\" | \"password\" | \"url\" | \"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 <FieldWrapper\n key={fieldPath}\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(Component as React.ComponentType<typeof componentProps>, componentProps)}\n </FieldWrapper>\n );\n }, [spec, forma, components, FieldWrapper]);\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(\"useFormaContext must be used within a FormaContext.Provider\");\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 { FieldDefinition, JSONSchemaProperty } 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} 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): { min?: number; max?: number; step?: number } {\n if (!schema) return {};\n if (schema.type !== \"number\" && schema.type !== \"integer\") return {};\n\n // Extract min/max from schema\n const min = \"minimum\" in schema && typeof schema.minimum === \"number\" ? schema.minimum : undefined;\n const max = \"maximum\" in schema && typeof schema.maximum === \"number\" ? schema.maximum : 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(itemFields: Record<string, FieldDefinition>): Record<string, unknown> {\n const item: Record<string, unknown> = {};\n for (const [fieldName, fieldDef] of Object.entries(itemFields)) {\n 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({ fieldPath, components, className }: 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) return null;\n\n // Infer field type\n const fieldType = fieldDef.type || (fieldDef.itemFields ? \"array\" : \"text\");\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 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 label: fieldDef.label ?? fieldPath,\n description: fieldDef.description,\n placeholder: fieldDef.placeholder,\n };\n\n // Build type-specific props\n let fieldProps: BaseFieldProps | TextFieldProps | NumberFieldProps | IntegerFieldProps | SelectFieldProps | MultiSelectFieldProps | ArrayFieldProps = 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 fieldProps = {\n ...baseProps,\n fieldType: \"select\",\n value: baseProps.value as string | null,\n onChange: baseProps.onChange as (value: string | null) => void,\n options: fieldDef.options ?? [],\n } as SelectFieldProps;\n } else if (fieldType === \"multiselect\") {\n fieldProps = {\n ...baseProps,\n fieldType: \"multiselect\",\n value: (baseProps.value as string[] | undefined) ?? [],\n onChange: baseProps.onChange as (value: string[]) => void,\n options: fieldDef.options ?? [],\n } as MultiSelectFieldProps;\n } else if (fieldType === \"array\" && fieldDef.itemFields) {\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]] = [newArray[indexB], newArray[indexA]];\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 itemValue = (arrayValue[index] as Record<string, unknown>)?.[fieldName];\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 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 item = (newArray[index] ?? {}) as Record<string, unknown>;\n newArray[index] = { ...item, [fieldName]: value };\n forma.setFieldValue(fieldPath, newArray);\n },\n onBlur: () => forma.setFieldTouched(itemPath),\n itemIndex: index,\n fieldName,\n options: itemFieldDef?.options,\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 minItems,\n maxItems,\n } as ArrayFieldProps;\n } else {\n // Text-based fields\n fieldProps = {\n ...baseProps,\n fieldType: fieldType as \"text\" | \"email\" | \"password\" | \"url\" | \"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(Component as React.ComponentType<typeof componentProps>, componentProps);\n\n if (className) {\n return <div className={className}>{element}</div>;\n }\n\n return element;\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?: React.ReactNode | ((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({ error, onReset }: { error: Error; onReset: () => void }) {\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 (\n this.state.hasError &&\n prevProps.resetKey !== this.props.resetKey\n ) {\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 <DefaultErrorFallback error={this.state.error} onReset={this.reset} />;\n }\n\n return this.props.children;\n }\n}\n"],"mappings":"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","names":["schemaProperty","useRef","useMemo","useCallback","FormRenderer","useRef","useCallback","useMemo","baseProps","React","jsx","getNumberConstraints","createDefaultItem","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.10.1",
|
|
4
4
|
"description": "Headless React form renderer for Forma specifications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"test:coverage": "vitest run --coverage"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@fogpipe/forma-core": "^0.
|
|
33
|
+
"@fogpipe/forma-core": "^0.10.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"react": "^18.0.0 || ^19.0.0"
|
package/src/FormRenderer.tsx
CHANGED
|
@@ -74,7 +74,7 @@ function DefaultLayout({ children, onSubmit, isSubmitting }: LayoutProps) {
|
|
|
74
74
|
/**
|
|
75
75
|
* Default field wrapper component with accessibility support
|
|
76
76
|
*/
|
|
77
|
-
function DefaultFieldWrapper({ fieldPath, field, children, errors,
|
|
77
|
+
function DefaultFieldWrapper({ fieldPath, field, children, errors, showRequiredIndicator, visible }: FieldWrapperProps) {
|
|
78
78
|
if (!visible) return null;
|
|
79
79
|
|
|
80
80
|
const errorId = `${fieldPath}-error`;
|
|
@@ -86,8 +86,8 @@ function DefaultFieldWrapper({ fieldPath, field, children, errors, required, vis
|
|
|
86
86
|
{field.label && (
|
|
87
87
|
<label htmlFor={fieldPath}>
|
|
88
88
|
{field.label}
|
|
89
|
-
{
|
|
90
|
-
{
|
|
89
|
+
{showRequiredIndicator && <span className="required" aria-hidden="true">*</span>}
|
|
90
|
+
{showRequiredIndicator && <span className="sr-only"> (required)</span>}
|
|
91
91
|
</label>
|
|
92
92
|
)}
|
|
93
93
|
{children}
|
|
@@ -193,15 +193,6 @@ export const FormRenderer = forwardRef<FormRendererHandle, FormRendererProps>(
|
|
|
193
193
|
|
|
194
194
|
const fieldRefs = useRef<Map<string, HTMLElement>>(new Map());
|
|
195
195
|
|
|
196
|
-
// Cache for array helper functions to prevent recreation on every render
|
|
197
|
-
const arrayHelpersCache = useRef<Map<string, {
|
|
198
|
-
push: (item?: unknown) => void;
|
|
199
|
-
insert: (index: number, item: unknown) => void;
|
|
200
|
-
remove: (index: number) => void;
|
|
201
|
-
move: (from: number, to: number) => void;
|
|
202
|
-
swap: (indexA: number, indexB: number) => void;
|
|
203
|
-
}>>(new Map());
|
|
204
|
-
|
|
205
196
|
// Focus a specific field by path
|
|
206
197
|
const focusField = useCallback((path: string) => {
|
|
207
198
|
const element = fieldRefs.current.get(path);
|
|
@@ -274,6 +265,13 @@ export const FormRenderer = forwardRef<FormRendererHandle, FormRendererProps>(
|
|
|
274
265
|
// Get schema property for additional constraints
|
|
275
266
|
const schemaProperty = spec.schema.properties[fieldPath];
|
|
276
267
|
|
|
268
|
+
// Boolean fields: hide asterisk unless they have validation rules (consent pattern)
|
|
269
|
+
// - Binary question ("Do you smoke?"): no validation → false is valid → hide asterisk
|
|
270
|
+
// - Consent checkbox ("I accept terms"): has validation rule → show asterisk
|
|
271
|
+
const isBooleanField = schemaProperty?.type === "boolean" || fieldDef?.type === "boolean";
|
|
272
|
+
const hasValidationRules = (fieldDef?.validations?.length ?? 0) > 0;
|
|
273
|
+
const showRequiredIndicator = required && (!isBooleanField || hasValidationRules);
|
|
274
|
+
|
|
277
275
|
// Base field props
|
|
278
276
|
const baseProps: BaseFieldProps = {
|
|
279
277
|
name: fieldPath,
|
|
@@ -314,85 +312,40 @@ export const FormRenderer = forwardRef<FormRendererHandle, FormRendererProps>(
|
|
|
314
312
|
options: fieldDef.options ?? [],
|
|
315
313
|
} as SelectFieldProps;
|
|
316
314
|
} else if (fieldType === "array" && fieldDef.itemFields) {
|
|
317
|
-
const arrayValue = (baseProps.value
|
|
315
|
+
const arrayValue = Array.isArray(baseProps.value) ? baseProps.value : [];
|
|
318
316
|
const minItems = fieldDef.minItems ?? 0;
|
|
319
317
|
const maxItems = fieldDef.maxItems ?? Infinity;
|
|
320
318
|
const itemFieldDefs = fieldDef.itemFields;
|
|
321
319
|
|
|
322
|
-
// Get
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
},
|
|
343
|
-
move: (from: number, to: number) => {
|
|
344
|
-
const currentArray = (forma.data[fieldPath] as unknown[] | undefined) ?? [];
|
|
345
|
-
const newArray = [...currentArray];
|
|
346
|
-
const [item] = newArray.splice(from, 1);
|
|
347
|
-
newArray.splice(to, 0, item);
|
|
348
|
-
forma.setFieldValue(fieldPath, newArray);
|
|
349
|
-
},
|
|
350
|
-
swap: (indexA: number, indexB: number) => {
|
|
351
|
-
const currentArray = (forma.data[fieldPath] as unknown[] | undefined) ?? [];
|
|
352
|
-
const newArray = [...currentArray];
|
|
353
|
-
[newArray[indexA], newArray[indexB]] = [newArray[indexB], newArray[indexA]];
|
|
354
|
-
forma.setFieldValue(fieldPath, newArray);
|
|
355
|
-
},
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
const cachedHelpers = arrayHelpersCache.current.get(fieldPath)!;
|
|
320
|
+
// Get helpers from useForma - these are fresh on each render, avoiding stale closures
|
|
321
|
+
const baseHelpers = forma.getArrayHelpers(fieldPath);
|
|
322
|
+
|
|
323
|
+
// Wrap push to add default item creation when called without arguments
|
|
324
|
+
const pushWithDefault = (item?: unknown): void => {
|
|
325
|
+
const newItem = item ?? createDefaultItem(itemFieldDefs);
|
|
326
|
+
baseHelpers.push(newItem);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// Extend getItemFieldProps to include additional metadata (itemIndex, fieldName, options)
|
|
330
|
+
const getItemFieldPropsExtended = (index: number, fieldName: string) => {
|
|
331
|
+
const baseProps = baseHelpers.getItemFieldProps(index, fieldName);
|
|
332
|
+
const itemFieldDef = itemFieldDefs[fieldName];
|
|
333
|
+
return {
|
|
334
|
+
...baseProps,
|
|
335
|
+
itemIndex: index,
|
|
336
|
+
fieldName,
|
|
337
|
+
options: itemFieldDef?.options,
|
|
338
|
+
};
|
|
339
|
+
};
|
|
359
340
|
|
|
360
341
|
const helpers: ArrayHelpers = {
|
|
361
342
|
items: arrayValue,
|
|
362
|
-
push:
|
|
363
|
-
insert:
|
|
364
|
-
remove:
|
|
365
|
-
move:
|
|
366
|
-
swap:
|
|
367
|
-
getItemFieldProps:
|
|
368
|
-
const itemFieldDef = itemFieldDefs[fieldName];
|
|
369
|
-
const itemPath = `${fieldPath}[${index}].${fieldName}`;
|
|
370
|
-
const itemValue = (arrayValue[index] as Record<string, unknown>)?.[fieldName];
|
|
371
|
-
return {
|
|
372
|
-
name: itemPath,
|
|
373
|
-
value: itemValue,
|
|
374
|
-
type: itemFieldDef?.type ?? "text",
|
|
375
|
-
label: itemFieldDef?.label ?? fieldName,
|
|
376
|
-
description: itemFieldDef?.description,
|
|
377
|
-
placeholder: itemFieldDef?.placeholder,
|
|
378
|
-
visible: true,
|
|
379
|
-
enabled: !disabled,
|
|
380
|
-
required: itemFieldDef?.requiredWhen === "true",
|
|
381
|
-
touched: forma.touched[itemPath] ?? false,
|
|
382
|
-
errors: forma.errors.filter((e) => e.field === itemPath),
|
|
383
|
-
onChange: (value: unknown) => {
|
|
384
|
-
const currentArray = (forma.data[fieldPath] as unknown[] | undefined) ?? [];
|
|
385
|
-
const newArray = [...currentArray];
|
|
386
|
-
const item = (newArray[index] ?? {}) as Record<string, unknown>;
|
|
387
|
-
newArray[index] = { ...item, [fieldName]: value };
|
|
388
|
-
forma.setFieldValue(fieldPath, newArray);
|
|
389
|
-
},
|
|
390
|
-
onBlur: () => forma.setFieldTouched(itemPath),
|
|
391
|
-
itemIndex: index,
|
|
392
|
-
fieldName,
|
|
393
|
-
options: itemFieldDef?.options,
|
|
394
|
-
};
|
|
395
|
-
},
|
|
343
|
+
push: pushWithDefault,
|
|
344
|
+
insert: baseHelpers.insert,
|
|
345
|
+
remove: baseHelpers.remove,
|
|
346
|
+
move: baseHelpers.move,
|
|
347
|
+
swap: baseHelpers.swap,
|
|
348
|
+
getItemFieldProps: getItemFieldPropsExtended,
|
|
396
349
|
minItems,
|
|
397
350
|
maxItems,
|
|
398
351
|
canAdd: arrayValue.length < maxItems,
|
|
@@ -429,6 +382,7 @@ export const FormRenderer = forwardRef<FormRendererHandle, FormRendererProps>(
|
|
|
429
382
|
errors={errors}
|
|
430
383
|
touched={touched}
|
|
431
384
|
required={required}
|
|
385
|
+
showRequiredIndicator={showRequiredIndicator}
|
|
432
386
|
visible={isVisible}
|
|
433
387
|
>
|
|
434
388
|
{React.createElement(Component as React.ComponentType<typeof componentProps>, componentProps)}
|