@fogpipe/forma-react 0.10.4 → 0.11.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 CHANGED
@@ -1,4 +1,4 @@
1
- import { FieldError, SelectOption, FieldDefinition, Forma, ValidationResult } from '@fogpipe/forma-core';
1
+ import { FieldError, SelectOption, FieldDefinition, Forma, OptionsVisibilityResult, ValidationResult } from '@fogpipe/forma-core';
2
2
  export { ComputedField, FieldDefinition, FieldError, FieldType, Forma, PageDefinition, SelectOption, ValidationResult, ValidationRule } from '@fogpipe/forma-core';
3
3
  import * as React$1 from 'react';
4
4
  import React__default from 'react';
@@ -388,6 +388,8 @@ interface GetFieldPropsResult {
388
388
  "aria-describedby"?: string;
389
389
  /** ARIA: Indicates the field is required */
390
390
  "aria-required"?: boolean;
391
+ /** Options for select/multiselect fields (filtered by visibleWhen) */
392
+ options?: SelectOption[];
391
393
  }
392
394
  /**
393
395
  * Select field props returned by getSelectFieldProps()
@@ -499,6 +501,8 @@ interface UseFormaReturn {
499
501
  required: Record<string, boolean>;
500
502
  /** Field enabled state map */
501
503
  enabled: Record<string, boolean>;
504
+ /** Visible options for select/multiselect fields, keyed by field path */
505
+ optionsVisibility: OptionsVisibilityResult;
502
506
  /** Field touched state map */
503
507
  touched: Record<string, boolean>;
504
508
  /** Validation errors */
package/dist/index.js CHANGED
@@ -6,7 +6,8 @@ import {
6
6
  getEnabled,
7
7
  validate,
8
8
  calculate,
9
- getPageVisibility
9
+ getPageVisibility,
10
+ getOptionsVisibility
10
11
  } from "@fogpipe/forma-core";
11
12
  function formReducer(state, action) {
12
13
  switch (action.type) {
@@ -102,6 +103,10 @@ function useForma(options) {
102
103
  () => getEnabled(state.data, spec, { computed }),
103
104
  [state.data, spec, computed]
104
105
  );
106
+ const optionsVisibility = useMemo(
107
+ () => getOptionsVisibility(state.data, spec, { computed }),
108
+ [state.data, spec, computed]
109
+ );
105
110
  const immediateValidation = useMemo(
106
111
  () => validate(state.data, spec, { computed, onlyVisible: true }),
107
112
  [state.data, spec, computed]
@@ -374,12 +379,12 @@ function useForma(options) {
374
379
  }, [spec, state.touched, state.isSubmitted, visibility, enabled, required, validation.errors, validateOn, getValueAtPath, getFieldHandlers]);
375
380
  const getSelectFieldProps = useCallback((path) => {
376
381
  const baseProps = getFieldProps(path);
377
- const fieldDef = spec.fields[path];
382
+ const visibleOptions = optionsVisibility[path] ?? [];
378
383
  return {
379
384
  ...baseProps,
380
- options: (fieldDef == null ? void 0 : fieldDef.options) ?? []
385
+ options: visibleOptions
381
386
  };
382
- }, [getFieldProps, spec.fields]);
387
+ }, [getFieldProps, optionsVisibility]);
383
388
  const getArrayHelpers = useCallback((path) => {
384
389
  const fieldDef = spec.fields[path];
385
390
  const currentValue = getValueAtPath(path) ?? [];
@@ -392,11 +397,12 @@ function useForma(options) {
392
397
  const itemPath = `${path}[${index}].${fieldName}`;
393
398
  const itemFieldDef = (_a = fieldDef == null ? void 0 : fieldDef.itemFields) == null ? void 0 : _a[fieldName];
394
399
  const handlers = getFieldHandlers(itemPath);
395
- const item = currentValue[index];
396
- const itemValue = item == null ? void 0 : item[fieldName];
400
+ const item = currentValue[index] ?? {};
401
+ const itemValue = item[fieldName];
397
402
  const fieldErrors = validation.errors.filter((e) => e.field === itemPath);
398
403
  const isTouched = state.touched[itemPath] ?? false;
399
404
  const showErrors = validateOn === "change" || validateOn === "blur" && isTouched || state.isSubmitted;
405
+ const visibleOptions = optionsVisibility[itemPath];
400
406
  return {
401
407
  name: itemPath,
402
408
  value: itemValue,
@@ -413,7 +419,8 @@ function useForma(options) {
413
419
  touched: isTouched,
414
420
  errors: showErrors ? fieldErrors : [],
415
421
  onChange: handlers.onChange,
416
- onBlur: handlers.onBlur
422
+ onBlur: handlers.onBlur,
423
+ options: visibleOptions
417
424
  };
418
425
  };
419
426
  return {
@@ -454,13 +461,14 @@ function useForma(options) {
454
461
  canAdd,
455
462
  canRemove
456
463
  };
457
- }, [spec.fields, getValueAtPath, setValueAtPath, getFieldHandlers, enabled, state.touched, state.isSubmitted, validation.errors, validateOn]);
464
+ }, [spec.fields, getValueAtPath, setValueAtPath, getFieldHandlers, enabled, state.touched, state.isSubmitted, validation.errors, validateOn, optionsVisibility]);
458
465
  return {
459
466
  data: state.data,
460
467
  computed,
461
468
  visibility,
462
469
  required,
463
470
  enabled,
471
+ optionsVisibility,
464
472
  touched: state.touched,
465
473
  errors: validation.errors,
466
474
  isValid: validation.valid,
@@ -875,20 +883,22 @@ function FieldRenderer({ fieldPath, components, className }) {
875
883
  max: constraints.max
876
884
  };
877
885
  } else if (fieldType === "select") {
886
+ const visibleOptions = forma.optionsVisibility[fieldPath] ?? [];
878
887
  fieldProps = {
879
888
  ...baseProps,
880
889
  fieldType: "select",
881
890
  value: baseProps.value,
882
891
  onChange: baseProps.onChange,
883
- options: fieldDef.options ?? []
892
+ options: visibleOptions
884
893
  };
885
894
  } else if (fieldType === "multiselect") {
895
+ const visibleOptions = forma.optionsVisibility[fieldPath] ?? [];
886
896
  fieldProps = {
887
897
  ...baseProps,
888
898
  fieldType: "multiselect",
889
899
  value: baseProps.value ?? [],
890
900
  onChange: baseProps.onChange,
891
- options: fieldDef.options ?? []
901
+ options: visibleOptions
892
902
  };
893
903
  } else if (fieldType === "array" && fieldDef.itemFields) {
894
904
  const arrayValue = baseProps.value ?? [];
@@ -923,10 +933,11 @@ function FieldRenderer({ fieldPath, components, className }) {
923
933
  forma.setFieldValue(fieldPath, newArray);
924
934
  },
925
935
  getItemFieldProps: (index, fieldName) => {
926
- var _a;
927
936
  const itemFieldDef = itemFieldDefs[fieldName];
928
937
  const itemPath = `${fieldPath}[${index}].${fieldName}`;
929
- const itemValue = (_a = arrayValue[index]) == null ? void 0 : _a[fieldName];
938
+ const item = arrayValue[index] ?? {};
939
+ const itemValue = item[fieldName];
940
+ const visibleOptions = forma.optionsVisibility[itemPath];
930
941
  return {
931
942
  name: itemPath,
932
943
  value: itemValue,
@@ -941,14 +952,14 @@ function FieldRenderer({ fieldPath, components, className }) {
941
952
  errors: forma.errors.filter((e) => e.field === itemPath),
942
953
  onChange: (value) => {
943
954
  const newArray = [...arrayValue];
944
- const item = newArray[index] ?? {};
945
- newArray[index] = { ...item, [fieldName]: value };
955
+ const existingItem = newArray[index] ?? {};
956
+ newArray[index] = { ...existingItem, [fieldName]: value };
946
957
  forma.setFieldValue(fieldPath, newArray);
947
958
  },
948
959
  onBlur: () => forma.setFieldTouched(itemPath),
949
960
  itemIndex: index,
950
961
  fieldName,
951
- options: itemFieldDef == null ? void 0 : itemFieldDef.options
962
+ options: visibleOptions
952
963
  };
953
964
  },
954
965
  minItems,
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 * 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 // Keep a ref to current state.data to avoid stale closures in cached handlers\n const stateDataRef = useRef(state.data);\n stateDataRef.current = state.data;\n\n // Track if we've initialized (to avoid calling onChange on first render)\n const hasInitialized = useRef(false);\n\n // 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 // Uses stateDataRef to always access current state, avoiding stale closure issues\n const getValueAtPath = useCallback((path: string): unknown => {\n // Handle array index notation: \"items[0].name\" -> [\"items\", \"0\", \"name\"]\n const parts = path.replace(/\\[(\\d+)\\]/g, '.$1').split('.');\n let value: unknown = stateDataRef.current;\n for (const part of parts) {\n if (value === null || value === undefined) return undefined;\n value = (value as Record<string, unknown>)[part];\n }\n return value;\n }, []); // No dependencies - uses ref for current state\n\n // Helper to set value at nested path\n // Uses stateDataRef to always access current state, avoiding stale closure issues\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 from CURRENT state via ref (not stale closure)\n const newData = { ...stateDataRef.current };\n let current: Record<string, unknown> = newData;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n const nextPart = parts[i + 1];\n const isNextArrayIndex = /^\\d+$/.test(nextPart);\n\n if (current[part] === undefined) {\n current[part] = isNextArrayIndex ? [] : {};\n } else if (Array.isArray(current[part])) {\n current[part] = [...(current[part] as unknown[])];\n } else {\n current[part] = { ...(current[part] as Record<string, unknown>) };\n }\n current = current[part] as Record<string, unknown>;\n }\n\n current[parts[parts.length - 1]] = value;\n dispatch({ type: \"SET_VALUES\", values: newData });\n }, []); // No dependencies - uses ref for current state\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"]}
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, SelectOption } 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 getOptionsVisibility,\n} from \"@fogpipe/forma-core\";\nimport type { OptionsVisibilityResult } from \"@fogpipe/forma-core\";\n\n/**\n * Options for useForma hook\n */\nexport interface UseFormaOptions {\n /** The Forma specification */\n spec: Forma;\n /** Initial form data */\n initialData?: Record<string, unknown>;\n /** Submit handler */\n onSubmit?: (data: Record<string, unknown>) => void | Promise<void>;\n /** Change handler */\n onChange?: (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 /** Visible options for select/multiselect fields, keyed by field path */\n optionsVisibility: OptionsVisibilityResult;\n /** Field touched state map */\n touched: Record<string, boolean>;\n /** Validation errors */\n errors: FieldError[];\n /** Whether form is valid */\n isValid: boolean;\n /** Whether form is submitting */\n isSubmitting: boolean;\n /** Whether form has been submitted */\n isSubmitted: boolean;\n /** Whether any field has been modified */\n isDirty: boolean;\n /** The Forma spec */\n spec: Forma;\n /** Wizard helpers (if multi-page) */\n wizard: WizardHelpers | null;\n\n /** Set a field value */\n setFieldValue: (path: string, value: unknown) => void;\n /** Set a field as touched */\n setFieldTouched: (path: string, touched?: boolean) => void;\n /** Set multiple values */\n setValues: (values: Record<string, unknown>) => void;\n /** Validate a single field */\n validateField: (path: string) => FieldError[];\n /** Validate entire form */\n validateForm: () => ValidationResult;\n /** Submit the form */\n submitForm: () => Promise<void>;\n /** Reset the form */\n resetForm: () => void;\n\n // 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 // Keep a ref to current state.data to avoid stale closures in cached handlers\n const stateDataRef = useRef(state.data);\n stateDataRef.current = state.data;\n\n // Track if we've initialized (to avoid calling onChange on first render)\n const hasInitialized = useRef(false);\n\n // Calculate computed values\n const computed = useMemo(\n () => calculate(state.data, spec),\n [state.data, spec]\n );\n\n // Calculate visibility\n const visibility = useMemo(\n () => getVisibility(state.data, spec, { computed }),\n [state.data, spec, computed]\n );\n\n // Calculate required state\n const required = useMemo(\n () => getRequired(state.data, spec, { computed }),\n [state.data, spec, computed]\n );\n\n // Calculate enabled state\n const enabled = useMemo(\n () => getEnabled(state.data, spec, { computed }),\n [state.data, spec, computed]\n );\n\n // Calculate visible options for all select/multiselect fields (memoized)\n const optionsVisibility = useMemo(\n () => getOptionsVisibility(state.data, spec, { computed }),\n [state.data, spec, computed]\n );\n\n // Validate form - compute immediate result\n const immediateValidation = useMemo(\n () => validate(state.data, spec, { computed, onlyVisible: true }),\n [state.data, spec, computed]\n );\n\n // Debounced validation state (only used when validationDebounceMs > 0)\n const [debouncedValidation, setDebouncedValidation] = 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 // Uses stateDataRef to always access current state, avoiding stale closure issues\n const getValueAtPath = useCallback((path: string): unknown => {\n // Handle array index notation: \"items[0].name\" -> [\"items\", \"0\", \"name\"]\n const parts = path.replace(/\\[(\\d+)\\]/g, '.$1').split('.');\n let value: unknown = stateDataRef.current;\n for (const part of parts) {\n if (value === null || value === undefined) return undefined;\n value = (value as Record<string, unknown>)[part];\n }\n return value;\n }, []); // No dependencies - uses ref for current state\n\n // Helper to set value at nested path\n // Uses stateDataRef to always access current state, avoiding stale closure issues\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 from CURRENT state via ref (not stale closure)\n const newData = { ...stateDataRef.current };\n let current: Record<string, unknown> = newData;\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i];\n const nextPart = parts[i + 1];\n const isNextArrayIndex = /^\\d+$/.test(nextPart);\n\n if (current[part] === undefined) {\n current[part] = isNextArrayIndex ? [] : {};\n } else if (Array.isArray(current[part])) {\n current[part] = [...(current[part] as unknown[])];\n } else {\n current[part] = { ...(current[part] as Record<string, unknown>) };\n }\n current = current[part] as Record<string, unknown>;\n }\n\n current[parts[parts.length - 1]] = value;\n dispatch({ type: \"SET_VALUES\", values: newData });\n }, []); // No dependencies - uses ref for current state\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 - uses pre-computed optionsVisibility map\n const getSelectFieldProps = useCallback((path: string): GetSelectFieldPropsResult => {\n const baseProps = getFieldProps(path);\n\n // Look up pre-computed visible options from memoized map\n const visibleOptions = optionsVisibility[path] ?? [];\n\n return {\n ...baseProps,\n options: visibleOptions as SelectOption[],\n };\n }, [getFieldProps, optionsVisibility]);\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>) ?? {};\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 // Look up pre-computed visible options from memoized map\n const visibleOptions = optionsVisibility[itemPath] as SelectOption[] | undefined;\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 options: visibleOptions,\n };\n };\n\n return {\n items: currentValue,\n push: (item: unknown) => {\n if (canAdd) {\n setValueAtPath(path, [...currentValue, item]);\n }\n },\n remove: (index: number) => {\n if (canRemove) {\n const newArray = [...currentValue];\n newArray.splice(index, 1);\n setValueAtPath(path, newArray);\n }\n },\n move: (from: number, to: number) => {\n const newArray = [...currentValue];\n const [item] = newArray.splice(from, 1);\n newArray.splice(to, 0, item);\n setValueAtPath(path, newArray);\n },\n swap: (indexA: number, indexB: number) => {\n const newArray = [...currentValue];\n [newArray[indexA], newArray[indexB]] = [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, optionsVisibility]);\n\n return {\n data: state.data,\n computed,\n visibility,\n required,\n enabled,\n optionsVisibility,\n touched: state.touched,\n errors: validation.errors,\n isValid: validation.valid,\n isSubmitting: state.isSubmitting,\n isSubmitted: state.isSubmitted,\n isDirty: state.isDirty,\n spec,\n wizard,\n setFieldValue,\n setFieldTouched,\n setValues,\n validateField,\n validateForm,\n submitForm,\n resetForm,\n 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, SelectOption } 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 // Use pre-computed visible options from memoized map\n const visibleOptions = (forma.optionsVisibility[fieldPath] ?? []) as SelectOption[];\n fieldProps = {\n ...baseProps,\n fieldType: \"select\",\n value: baseProps.value as string | null,\n onChange: baseProps.onChange as (value: string | null) => void,\n options: visibleOptions,\n } as SelectFieldProps;\n } else if (fieldType === \"multiselect\") {\n // Use pre-computed visible options from memoized map\n const visibleOptions = (forma.optionsVisibility[fieldPath] ?? []) as SelectOption[];\n fieldProps = {\n ...baseProps,\n fieldType: \"multiselect\",\n value: (baseProps.value as string[] | undefined) ?? [],\n onChange: baseProps.onChange as (value: string[]) => void,\n options: visibleOptions,\n } as MultiSelectFieldProps;\n } else if (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 item = (arrayValue[index] as Record<string, unknown>) ?? {};\n const itemValue = item[fieldName];\n\n // Use pre-computed visible options from memoized map\n const visibleOptions = forma.optionsVisibility[itemPath] as SelectOption[] | undefined;\n\n return {\n name: itemPath,\n value: itemValue,\n type: itemFieldDef?.type ?? \"text\",\n label: itemFieldDef?.label ?? fieldName,\n description: itemFieldDef?.description,\n placeholder: itemFieldDef?.placeholder,\n visible: true,\n enabled: !disabled,\n required: itemFieldDef?.requiredWhen === \"true\",\n touched: forma.touched[itemPath] ?? false,\n errors: forma.errors.filter((e) => e.field === itemPath),\n onChange: (value: unknown) => {\n const newArray = [...arrayValue];\n const existingItem = (newArray[index] ?? {}) as Record<string, unknown>;\n newArray[index] = { ...existingItem, [fieldName]: value };\n forma.setFieldValue(fieldPath, newArray);\n },\n onBlur: () => forma.setFieldTouched(itemPath),\n itemIndex: index,\n fieldName,\n options: visibleOptions,\n };\n },\n minItems,\n maxItems,\n canAdd: arrayValue.length < maxItems,\n canRemove: arrayValue.length > minItems,\n };\n fieldProps = {\n ...baseProps,\n fieldType: \"array\",\n value: arrayValue,\n onChange: baseProps.onChange as (value: unknown[]) => void,\n helpers,\n itemFields: itemFieldDefs,\n 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.10.4",
3
+ "version": "0.11.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.10.4"
33
+ "@fogpipe/forma-core": "^0.11.1"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "react": "^18.0.0 || ^19.0.0"
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import React from "react";
9
- import type { FieldDefinition, JSONSchemaProperty } from "@fogpipe/forma-core";
9
+ import type { FieldDefinition, JSONSchemaProperty, SelectOption } from "@fogpipe/forma-core";
10
10
  import { useFormaContext } from "./context.js";
11
11
  import type {
12
12
  ComponentMap,
@@ -153,20 +153,24 @@ export function FieldRenderer({ fieldPath, components, className }: FieldRendere
153
153
  max: constraints.max,
154
154
  } as IntegerFieldProps;
155
155
  } else if (fieldType === "select") {
156
+ // Use pre-computed visible options from memoized map
157
+ const visibleOptions = (forma.optionsVisibility[fieldPath] ?? []) as SelectOption[];
156
158
  fieldProps = {
157
159
  ...baseProps,
158
160
  fieldType: "select",
159
161
  value: baseProps.value as string | null,
160
162
  onChange: baseProps.onChange as (value: string | null) => void,
161
- options: fieldDef.options ?? [],
163
+ options: visibleOptions,
162
164
  } as SelectFieldProps;
163
165
  } else if (fieldType === "multiselect") {
166
+ // Use pre-computed visible options from memoized map
167
+ const visibleOptions = (forma.optionsVisibility[fieldPath] ?? []) as SelectOption[];
164
168
  fieldProps = {
165
169
  ...baseProps,
166
170
  fieldType: "multiselect",
167
171
  value: (baseProps.value as string[] | undefined) ?? [],
168
172
  onChange: baseProps.onChange as (value: string[]) => void,
169
- options: fieldDef.options ?? [],
173
+ options: visibleOptions,
170
174
  } as MultiSelectFieldProps;
171
175
  } else if (fieldType === "array" && fieldDef.itemFields) {
172
176
  const arrayValue = (baseProps.value as unknown[] | undefined) ?? [];
@@ -204,7 +208,12 @@ export function FieldRenderer({ fieldPath, components, className }: FieldRendere
204
208
  getItemFieldProps: (index: number, fieldName: string) => {
205
209
  const itemFieldDef = itemFieldDefs[fieldName];
206
210
  const itemPath = `${fieldPath}[${index}].${fieldName}`;
207
- const itemValue = (arrayValue[index] as Record<string, unknown>)?.[fieldName];
211
+ const item = (arrayValue[index] as Record<string, unknown>) ?? {};
212
+ const itemValue = item[fieldName];
213
+
214
+ // Use pre-computed visible options from memoized map
215
+ const visibleOptions = forma.optionsVisibility[itemPath] as SelectOption[] | undefined;
216
+
208
217
  return {
209
218
  name: itemPath,
210
219
  value: itemValue,
@@ -219,14 +228,14 @@ export function FieldRenderer({ fieldPath, components, className }: FieldRendere
219
228
  errors: forma.errors.filter((e) => e.field === itemPath),
220
229
  onChange: (value: unknown) => {
221
230
  const newArray = [...arrayValue];
222
- const item = (newArray[index] ?? {}) as Record<string, unknown>;
223
- newArray[index] = { ...item, [fieldName]: value };
231
+ const existingItem = (newArray[index] ?? {}) as Record<string, unknown>;
232
+ newArray[index] = { ...existingItem, [fieldName]: value };
224
233
  forma.setFieldValue(fieldPath, newArray);
225
234
  },
226
235
  onBlur: () => forma.setFieldTouched(itemPath),
227
236
  itemIndex: index,
228
237
  fieldName,
229
- options: itemFieldDef?.options,
238
+ options: visibleOptions,
230
239
  };
231
240
  },
232
241
  minItems,
@@ -0,0 +1,511 @@
1
+ /**
2
+ * Integration tests for option visibility filtering
3
+ *
4
+ * Tests that visibleWhen on SelectOption works correctly through
5
+ * useForma and FieldRenderer.
6
+ */
7
+
8
+ import { describe, it, expect } from "vitest";
9
+ import { renderHook, act } from "@testing-library/react";
10
+ import { useForma } from "../useForma.js";
11
+ import { createTestSpec } from "./test-utils.js";
12
+
13
+ describe("Option Visibility", () => {
14
+ // ============================================================================
15
+ // getSelectFieldProps
16
+ // ============================================================================
17
+
18
+ describe("getSelectFieldProps option filtering", () => {
19
+ it("should return all options when none have visibleWhen", () => {
20
+ const spec = createTestSpec({
21
+ fields: {
22
+ department: {
23
+ type: "select",
24
+ options: [
25
+ { value: "eng", label: "Engineering" },
26
+ { value: "hr", label: "HR" },
27
+ { value: "sales", label: "Sales" },
28
+ ],
29
+ },
30
+ },
31
+ });
32
+
33
+ const { result } = renderHook(() => useForma({ spec }));
34
+ const props = result.current.getSelectFieldProps("department");
35
+
36
+ expect(props.options).toHaveLength(3);
37
+ expect(props.options.map(o => o.value)).toEqual(["eng", "hr", "sales"]);
38
+ });
39
+
40
+ it("should filter options based on visibleWhen expressions", () => {
41
+ const spec = createTestSpec({
42
+ fields: {
43
+ experienceYears: { type: "number" },
44
+ position: {
45
+ type: "select",
46
+ options: [
47
+ { value: "intern", label: "Intern" },
48
+ { value: "junior", label: "Junior Developer", visibleWhen: "experienceYears >= 1" },
49
+ { value: "senior", label: "Senior Developer", visibleWhen: "experienceYears >= 5" },
50
+ { value: "lead", label: "Tech Lead", visibleWhen: "experienceYears >= 8" },
51
+ ],
52
+ },
53
+ },
54
+ });
55
+
56
+ const { result } = renderHook(() =>
57
+ useForma({ spec, initialData: { experienceYears: 3 } })
58
+ );
59
+
60
+ // With 3 years: intern, junior should be visible
61
+ let props = result.current.getSelectFieldProps("position");
62
+ expect(props.options.map(o => o.value)).toEqual(["intern", "junior"]);
63
+
64
+ // Change to 6 years: intern, junior, senior should be visible
65
+ act(() => {
66
+ result.current.setFieldValue("experienceYears", 6);
67
+ });
68
+
69
+ props = result.current.getSelectFieldProps("position");
70
+ expect(props.options.map(o => o.value)).toEqual(["intern", "junior", "senior"]);
71
+
72
+ // Change to 10 years: all should be visible
73
+ act(() => {
74
+ result.current.setFieldValue("experienceYears", 10);
75
+ });
76
+
77
+ props = result.current.getSelectFieldProps("position");
78
+ expect(props.options.map(o => o.value)).toEqual(["intern", "junior", "senior", "lead"]);
79
+ });
80
+
81
+ it("should filter options based on another select field value", () => {
82
+ const spec = createTestSpec({
83
+ fields: {
84
+ department: {
85
+ type: "select",
86
+ options: [
87
+ { value: "eng", label: "Engineering" },
88
+ { value: "hr", label: "HR" },
89
+ ],
90
+ },
91
+ position: {
92
+ type: "select",
93
+ options: [
94
+ { value: "dev_frontend", label: "Frontend Developer", visibleWhen: 'department = "eng"' },
95
+ { value: "dev_backend", label: "Backend Developer", visibleWhen: 'department = "eng"' },
96
+ { value: "recruiter", label: "Recruiter", visibleWhen: 'department = "hr"' },
97
+ { value: "hr_manager", label: "HR Manager", visibleWhen: 'department = "hr"' },
98
+ ],
99
+ },
100
+ },
101
+ });
102
+
103
+ const { result } = renderHook(() =>
104
+ useForma({ spec, initialData: { department: "eng" } })
105
+ );
106
+
107
+ // With Engineering selected
108
+ let props = result.current.getSelectFieldProps("position");
109
+ expect(props.options.map(o => o.value)).toEqual(["dev_frontend", "dev_backend"]);
110
+
111
+ // Switch to HR
112
+ act(() => {
113
+ result.current.setFieldValue("department", "hr");
114
+ });
115
+
116
+ props = result.current.getSelectFieldProps("position");
117
+ expect(props.options.map(o => o.value)).toEqual(["recruiter", "hr_manager"]);
118
+ });
119
+
120
+ it("should return empty options when all are hidden", () => {
121
+ const spec = createTestSpec({
122
+ fields: {
123
+ isPremium: { type: "boolean" },
124
+ premiumFeature: {
125
+ type: "select",
126
+ options: [
127
+ { value: "feature_a", label: "Feature A", visibleWhen: "isPremium = true" },
128
+ { value: "feature_b", label: "Feature B", visibleWhen: "isPremium = true" },
129
+ ],
130
+ },
131
+ },
132
+ });
133
+
134
+ const { result } = renderHook(() =>
135
+ useForma({ spec, initialData: { isPremium: false } })
136
+ );
137
+
138
+ const props = result.current.getSelectFieldProps("premiumFeature");
139
+ expect(props.options).toEqual([]);
140
+ });
141
+
142
+ it("should use computed values in visibleWhen expressions", () => {
143
+ const spec = createTestSpec({
144
+ fields: {
145
+ quantity: { type: "number" },
146
+ unitPrice: { type: "number" },
147
+ shippingMethod: {
148
+ type: "select",
149
+ options: [
150
+ { value: "standard", label: "Standard Shipping" },
151
+ { value: "express", label: "Express Shipping", visibleWhen: "computed.orderTotal >= 50" },
152
+ { value: "overnight", label: "Overnight Shipping", visibleWhen: "computed.orderTotal >= 100" },
153
+ ],
154
+ },
155
+ },
156
+ computed: {
157
+ orderTotal: { expression: "quantity * unitPrice" },
158
+ },
159
+ });
160
+
161
+ const { result } = renderHook(() =>
162
+ useForma({ spec, initialData: { quantity: 2, unitPrice: 20 } })
163
+ );
164
+
165
+ // Total = 40: only standard
166
+ let props = result.current.getSelectFieldProps("shippingMethod");
167
+ expect(props.options.map(o => o.value)).toEqual(["standard"]);
168
+
169
+ // Total = 60: standard and express
170
+ act(() => {
171
+ result.current.setFieldValue("quantity", 3);
172
+ });
173
+
174
+ props = result.current.getSelectFieldProps("shippingMethod");
175
+ expect(props.options.map(o => o.value)).toEqual(["standard", "express"]);
176
+
177
+ // Total = 120: all options
178
+ act(() => {
179
+ result.current.setFieldValue("unitPrice", 40);
180
+ });
181
+
182
+ props = result.current.getSelectFieldProps("shippingMethod");
183
+ expect(props.options.map(o => o.value)).toEqual(["standard", "express", "overnight"]);
184
+ });
185
+ });
186
+
187
+ // ============================================================================
188
+ // Multiselect
189
+ // ============================================================================
190
+
191
+ describe("multiselect option filtering", () => {
192
+ it("should filter multiselect options based on visibleWhen", () => {
193
+ const spec = createTestSpec({
194
+ fields: {
195
+ accountType: {
196
+ type: "select",
197
+ options: [
198
+ { value: "free", label: "Free" },
199
+ { value: "paid", label: "Paid" },
200
+ ],
201
+ },
202
+ features: {
203
+ type: "multiselect",
204
+ options: [
205
+ { value: "basic", label: "Basic Features" },
206
+ { value: "advanced", label: "Advanced Features", visibleWhen: 'accountType = "paid"' },
207
+ { value: "enterprise", label: "Enterprise Features", visibleWhen: 'accountType = "paid"' },
208
+ ],
209
+ },
210
+ },
211
+ });
212
+
213
+ const { result } = renderHook(() =>
214
+ useForma({ spec, initialData: { accountType: "free" } })
215
+ );
216
+
217
+ // Free account: only basic
218
+ let props = result.current.getSelectFieldProps("features");
219
+ expect(props.options.map(o => o.value)).toEqual(["basic"]);
220
+
221
+ // Paid account: all features
222
+ act(() => {
223
+ result.current.setFieldValue("accountType", "paid");
224
+ });
225
+
226
+ props = result.current.getSelectFieldProps("features");
227
+ expect(props.options.map(o => o.value)).toEqual(["basic", "advanced", "enterprise"]);
228
+ });
229
+ });
230
+
231
+ // ============================================================================
232
+ // Array item select fields
233
+ // ============================================================================
234
+
235
+ describe("array item select field filtering", () => {
236
+ it("should filter options in array item select fields using item context", () => {
237
+ const spec = createTestSpec({
238
+ fields: {
239
+ orderItems: {
240
+ type: "array",
241
+ itemFields: {
242
+ category: {
243
+ type: "select",
244
+ options: [
245
+ { value: "electronics", label: "Electronics" },
246
+ { value: "clothing", label: "Clothing" },
247
+ ],
248
+ },
249
+ addon: {
250
+ type: "select",
251
+ options: [
252
+ { value: "warranty", label: "Extended Warranty", visibleWhen: 'item.category = "electronics"' },
253
+ { value: "insurance", label: "Shipping Insurance" },
254
+ { value: "giftWrap", label: "Gift Wrap", visibleWhen: 'item.category = "clothing"' },
255
+ ],
256
+ },
257
+ },
258
+ },
259
+ },
260
+ });
261
+
262
+ const { result } = renderHook(() =>
263
+ useForma({
264
+ spec,
265
+ initialData: {
266
+ orderItems: [
267
+ { category: "electronics", addon: null },
268
+ { category: "clothing", addon: null },
269
+ ],
270
+ },
271
+ })
272
+ );
273
+
274
+ const helpers = result.current.getArrayHelpers("orderItems");
275
+
276
+ // First item (electronics): warranty and insurance
277
+ const item0Props = helpers.getItemFieldProps(0, "addon");
278
+ expect(item0Props.options?.map(o => o.value)).toEqual(["warranty", "insurance"]);
279
+
280
+ // Second item (clothing): insurance and giftWrap
281
+ const item1Props = helpers.getItemFieldProps(1, "addon");
282
+ expect(item1Props.options?.map(o => o.value)).toEqual(["insurance", "giftWrap"]);
283
+ });
284
+
285
+ it("should update array item options when item data changes", () => {
286
+ const spec = createTestSpec({
287
+ fields: {
288
+ contacts: {
289
+ type: "array",
290
+ itemFields: {
291
+ type: {
292
+ type: "select",
293
+ options: [
294
+ { value: "personal", label: "Personal" },
295
+ { value: "business", label: "Business" },
296
+ ],
297
+ },
298
+ method: {
299
+ type: "select",
300
+ options: [
301
+ { value: "email", label: "Email" },
302
+ { value: "phone", label: "Phone" },
303
+ { value: "fax", label: "Fax", visibleWhen: 'item.type = "business"' },
304
+ ],
305
+ },
306
+ },
307
+ },
308
+ },
309
+ });
310
+
311
+ const { result } = renderHook(() =>
312
+ useForma({
313
+ spec,
314
+ initialData: {
315
+ contacts: [{ type: "personal", method: null }],
316
+ },
317
+ })
318
+ );
319
+
320
+ // Initially personal: email and phone only
321
+ let helpers = result.current.getArrayHelpers("contacts");
322
+ let methodProps = helpers.getItemFieldProps(0, "method");
323
+ expect(methodProps.options?.map(o => o.value)).toEqual(["email", "phone"]);
324
+
325
+ // Change to business: fax becomes available
326
+ act(() => {
327
+ helpers.getItemFieldProps(0, "type").onChange("business");
328
+ });
329
+
330
+ helpers = result.current.getArrayHelpers("contacts");
331
+ methodProps = helpers.getItemFieldProps(0, "method");
332
+ expect(methodProps.options?.map(o => o.value)).toEqual(["email", "phone", "fax"]);
333
+ });
334
+
335
+ it("should use itemIndex in option visibleWhen expressions", () => {
336
+ const spec = createTestSpec({
337
+ fields: {
338
+ teamMembers: {
339
+ type: "array",
340
+ itemFields: {
341
+ role: {
342
+ type: "select",
343
+ options: [
344
+ { value: "member", label: "Team Member" },
345
+ { value: "lead", label: "Team Lead", visibleWhen: "itemIndex = 0" },
346
+ ],
347
+ },
348
+ },
349
+ },
350
+ },
351
+ });
352
+
353
+ const { result } = renderHook(() =>
354
+ useForma({
355
+ spec,
356
+ initialData: {
357
+ teamMembers: [{ role: null }, { role: null }, { role: null }],
358
+ },
359
+ })
360
+ );
361
+
362
+ const helpers = result.current.getArrayHelpers("teamMembers");
363
+
364
+ // First item: can be member or lead
365
+ const item0Props = helpers.getItemFieldProps(0, "role");
366
+ expect(item0Props.options?.map(o => o.value)).toEqual(["member", "lead"]);
367
+
368
+ // Second item: can only be member
369
+ const item1Props = helpers.getItemFieldProps(1, "role");
370
+ expect(item1Props.options?.map(o => o.value)).toEqual(["member"]);
371
+
372
+ // Third item: can only be member
373
+ const item2Props = helpers.getItemFieldProps(2, "role");
374
+ expect(item2Props.options?.map(o => o.value)).toEqual(["member"]);
375
+ });
376
+
377
+ it("should combine form data, computed values, and item context", () => {
378
+ const spec = createTestSpec({
379
+ fields: {
380
+ isPremiumOrder: { type: "boolean" },
381
+ lineItems: {
382
+ type: "array",
383
+ itemFields: {
384
+ productType: {
385
+ type: "select",
386
+ options: [
387
+ { value: "standard", label: "Standard" },
388
+ { value: "premium", label: "Premium" },
389
+ ],
390
+ },
391
+ shipping: {
392
+ type: "select",
393
+ options: [
394
+ { value: "standard", label: "Standard" },
395
+ { value: "express", label: "Express", visibleWhen: "isPremiumOrder = true" },
396
+ { value: "priority", label: "Priority", visibleWhen: 'isPremiumOrder = true and item.productType = "premium"' },
397
+ ],
398
+ },
399
+ },
400
+ },
401
+ },
402
+ });
403
+
404
+ const { result } = renderHook(() =>
405
+ useForma({
406
+ spec,
407
+ initialData: {
408
+ isPremiumOrder: false,
409
+ lineItems: [
410
+ { productType: "standard", shipping: null },
411
+ { productType: "premium", shipping: null },
412
+ ],
413
+ },
414
+ })
415
+ );
416
+
417
+ let helpers = result.current.getArrayHelpers("lineItems");
418
+
419
+ // Not premium order: only standard shipping for both
420
+ expect(helpers.getItemFieldProps(0, "shipping").options?.map(o => o.value)).toEqual(["standard"]);
421
+ expect(helpers.getItemFieldProps(1, "shipping").options?.map(o => o.value)).toEqual(["standard"]);
422
+
423
+ // Upgrade to premium order
424
+ act(() => {
425
+ result.current.setFieldValue("isPremiumOrder", true);
426
+ });
427
+
428
+ helpers = result.current.getArrayHelpers("lineItems");
429
+
430
+ // Standard product: standard and express
431
+ expect(helpers.getItemFieldProps(0, "shipping").options?.map(o => o.value)).toEqual(["standard", "express"]);
432
+
433
+ // Premium product: standard, express, and priority
434
+ expect(helpers.getItemFieldProps(1, "shipping").options?.map(o => o.value)).toEqual(["standard", "express", "priority"]);
435
+ });
436
+ });
437
+
438
+ // ============================================================================
439
+ // Edge cases
440
+ // ============================================================================
441
+
442
+ describe("edge cases", () => {
443
+ it("should handle field without options gracefully", () => {
444
+ const spec = createTestSpec({
445
+ fields: {
446
+ textField: { type: "text" },
447
+ },
448
+ });
449
+
450
+ const { result } = renderHook(() => useForma({ spec }));
451
+ const props = result.current.getSelectFieldProps("textField");
452
+
453
+ expect(props.options).toEqual([]);
454
+ });
455
+
456
+ it("should handle invalid visibleWhen expressions gracefully", () => {
457
+ const spec = createTestSpec({
458
+ fields: {
459
+ status: {
460
+ type: "select",
461
+ options: [
462
+ { value: "valid", label: "Valid Option" },
463
+ { value: "invalid", label: "Invalid", visibleWhen: "this is not valid FEEL syntax !!!" },
464
+ ],
465
+ },
466
+ },
467
+ });
468
+
469
+ const { result } = renderHook(() => useForma({ spec }));
470
+ const props = result.current.getSelectFieldProps("status");
471
+
472
+ // Invalid expression should hide the option (treated as false)
473
+ expect(props.options.map(o => o.value)).toEqual(["valid"]);
474
+ });
475
+
476
+ it("should preserve selected value when option becomes hidden", () => {
477
+ const spec = createTestSpec({
478
+ fields: {
479
+ level: { type: "number" },
480
+ feature: {
481
+ type: "select",
482
+ options: [
483
+ { value: "basic", label: "Basic" },
484
+ { value: "advanced", label: "Advanced", visibleWhen: "level >= 5" },
485
+ ],
486
+ },
487
+ },
488
+ });
489
+
490
+ const { result } = renderHook(() =>
491
+ useForma({ spec, initialData: { level: 5, feature: "advanced" } })
492
+ );
493
+
494
+ // Initially advanced is selected and visible
495
+ expect(result.current.data.feature).toBe("advanced");
496
+ expect(result.current.getSelectFieldProps("feature").options.map(o => o.value))
497
+ .toEqual(["basic", "advanced"]);
498
+
499
+ // Reduce level - advanced option becomes hidden but value is preserved
500
+ act(() => {
501
+ result.current.setFieldValue("level", 3);
502
+ });
503
+
504
+ // Value is preserved (validation would catch this if needed)
505
+ expect(result.current.data.feature).toBe("advanced");
506
+ // But option is no longer visible
507
+ expect(result.current.getSelectFieldProps("feature").options.map(o => o.value))
508
+ .toEqual(["basic"]);
509
+ });
510
+ });
511
+ });
package/src/types.ts CHANGED
@@ -449,6 +449,8 @@ export interface GetFieldPropsResult {
449
449
  "aria-describedby"?: string;
450
450
  /** ARIA: Indicates the field is required */
451
451
  "aria-required"?: boolean;
452
+ /** Options for select/multiselect fields (filtered by visibleWhen) */
453
+ options?: SelectOption[];
452
454
  }
453
455
 
454
456
  /**
package/src/useForma.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
9
- import type { Forma, FieldError, ValidationResult } from "@fogpipe/forma-core";
9
+ import type { Forma, FieldError, ValidationResult, SelectOption } from "@fogpipe/forma-core";
10
10
  import type { GetFieldPropsResult, GetSelectFieldPropsResult, GetArrayHelpersResult } from "./types.js";
11
11
  import {
12
12
  getVisibility,
@@ -15,7 +15,9 @@ import {
15
15
  validate,
16
16
  calculate,
17
17
  getPageVisibility,
18
+ getOptionsVisibility,
18
19
  } from "@fogpipe/forma-core";
20
+ import type { OptionsVisibilityResult } from "@fogpipe/forma-core";
19
21
 
20
22
  /**
21
23
  * Options for useForma hook
@@ -108,6 +110,8 @@ export interface UseFormaReturn {
108
110
  required: Record<string, boolean>;
109
111
  /** Field enabled state map */
110
112
  enabled: Record<string, boolean>;
113
+ /** Visible options for select/multiselect fields, keyed by field path */
114
+ optionsVisibility: OptionsVisibilityResult;
111
115
  /** Field touched state map */
112
116
  touched: Record<string, boolean>;
113
117
  /** Validation errors */
@@ -268,6 +272,12 @@ export function useForma(options: UseFormaOptions): UseFormaReturn {
268
272
  [state.data, spec, computed]
269
273
  );
270
274
 
275
+ // Calculate visible options for all select/multiselect fields (memoized)
276
+ const optionsVisibility = useMemo(
277
+ () => getOptionsVisibility(state.data, spec, { computed }),
278
+ [state.data, spec, computed]
279
+ );
280
+
271
281
  // Validate form - compute immediate result
272
282
  const immediateValidation = useMemo(
273
283
  () => validate(state.data, spec, { computed, onlyVisible: true }),
@@ -622,16 +632,18 @@ export function useForma(options: UseFormaOptions): UseFormaReturn {
622
632
  };
623
633
  }, [spec, state.touched, state.isSubmitted, visibility, enabled, required, validation.errors, validateOn, getValueAtPath, getFieldHandlers]);
624
634
 
625
- // Get select field props
635
+ // Get select field props - uses pre-computed optionsVisibility map
626
636
  const getSelectFieldProps = useCallback((path: string): GetSelectFieldPropsResult => {
627
637
  const baseProps = getFieldProps(path);
628
- const fieldDef = spec.fields[path];
638
+
639
+ // Look up pre-computed visible options from memoized map
640
+ const visibleOptions = optionsVisibility[path] ?? [];
629
641
 
630
642
  return {
631
643
  ...baseProps,
632
- options: fieldDef?.options ?? [],
644
+ options: visibleOptions as SelectOption[],
633
645
  };
634
- }, [getFieldProps, spec.fields]);
646
+ }, [getFieldProps, optionsVisibility]);
635
647
 
636
648
  // Get array helpers
637
649
  const getArrayHelpers = useCallback((path: string): GetArrayHelpersResult => {
@@ -649,13 +661,16 @@ export function useForma(options: UseFormaOptions): UseFormaReturn {
649
661
  const handlers = getFieldHandlers(itemPath);
650
662
 
651
663
  // Get item value
652
- const item = currentValue[index] as Record<string, unknown> | undefined;
653
- const itemValue = item?.[fieldName];
664
+ const item = (currentValue[index] as Record<string, unknown>) ?? {};
665
+ const itemValue = item[fieldName];
654
666
 
655
667
  const fieldErrors = validation.errors.filter((e) => e.field === itemPath);
656
668
  const isTouched = state.touched[itemPath] ?? false;
657
669
  const showErrors = validateOn === "change" || (validateOn === "blur" && isTouched) || state.isSubmitted;
658
670
 
671
+ // Look up pre-computed visible options from memoized map
672
+ const visibleOptions = optionsVisibility[itemPath] as SelectOption[] | undefined;
673
+
659
674
  return {
660
675
  name: itemPath,
661
676
  value: itemValue,
@@ -671,6 +686,7 @@ export function useForma(options: UseFormaOptions): UseFormaReturn {
671
686
  errors: showErrors ? fieldErrors : [],
672
687
  onChange: handlers.onChange,
673
688
  onBlur: handlers.onBlur,
689
+ options: visibleOptions,
674
690
  };
675
691
  };
676
692
 
@@ -712,7 +728,7 @@ export function useForma(options: UseFormaOptions): UseFormaReturn {
712
728
  canAdd,
713
729
  canRemove,
714
730
  };
715
- }, [spec.fields, getValueAtPath, setValueAtPath, getFieldHandlers, enabled, state.touched, state.isSubmitted, validation.errors, validateOn]);
731
+ }, [spec.fields, getValueAtPath, setValueAtPath, getFieldHandlers, enabled, state.touched, state.isSubmitted, validation.errors, validateOn, optionsVisibility]);
716
732
 
717
733
  return {
718
734
  data: state.data,
@@ -720,6 +736,7 @@ export function useForma(options: UseFormaOptions): UseFormaReturn {
720
736
  visibility,
721
737
  required,
722
738
  enabled,
739
+ optionsVisibility,
723
740
  touched: state.touched,
724
741
  errors: validation.errors,
725
742
  isValid: validation.valid,