@buildnbuzz/buzzform 0.1.3 → 0.1.5

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/rhf.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapters/rhf.ts","../src/utils/array.ts","../src/lib/utils.ts"],"sourcesContent":["'use client';\n\nimport { useRef, useEffect } from 'react';\nimport { useForm, useWatch } from 'react-hook-form';\nimport type {\n Control,\n FieldValues,\n Path,\n PathValue,\n DefaultValues,\n FieldErrors,\n Resolver as RhfResolver\n} from 'react-hook-form';\nimport type {\n FormAdapter,\n AdapterOptions,\n FormState,\n FieldError,\n SetValueOptions,\n} from '../types';\nimport { createArrayHelpers } from '../utils';\nimport { getNestedValue, flattenNestedObject } from '../lib';\n\n// =============================================================================\n// RHF ADAPTER OPTIONS\n// =============================================================================\n\n/**\n * Options specific to the React Hook Form adapter.\n * Extends base AdapterOptions with RHF-specific features.\n */\nexport interface RhfAdapterOptions<TData extends FieldValues = FieldValues>\n extends AdapterOptions<TData> {\n /**\n * React Hook Form's reValidateMode.\n * When to re-validate after initial validation.\n * @default 'onChange'\n */\n reValidateMode?: 'onChange' | 'onBlur' | 'onSubmit';\n\n /**\n * Validation strategy before submit.\n * - 'firstError': Return first error only (faster)\n * - 'all': Return all errors (better UX for complex forms)\n * @default 'firstError'\n */\n criteriaMode?: 'firstError' | 'all';\n\n /**\n * Delay validation by specified ms (debounce).\n * Useful for expensive async validation.\n */\n delayError?: number;\n\n /**\n * Focus on the first field with an error after submit.\n * @default true\n */\n shouldFocusError?: boolean;\n}\n\n// =============================================================================\n// RHF ADAPTER\n// =============================================================================\n\n/**\n * React Hook Form adapter implementing the FormAdapter interface.\n * \n * This is the default adapter for BuzzForm. It provides full implementation\n * of all required and optional FormAdapter methods using React Hook Form.\n * \n * @example\n * // In FormProvider\n * import { useRhf } from '@buildnbuzz/buzzform/rhf';\n * \n * <FormProvider adapter={useRhf}>\n * <App />\n * </FormProvider>\n * \n * @example\n * // Direct usage\n * const form = useRhf({\n * defaultValues: { email: '', password: '' },\n * resolver: zodResolver(schema),\n * onSubmit: async (data) => {\n * await loginUser(data);\n * },\n * });\n */\nexport function useRhf<TData extends FieldValues = FieldValues>(\n options: RhfAdapterOptions<TData>\n): FormAdapter<TData> {\n const {\n defaultValues,\n values,\n resolver,\n mode = 'onChange',\n reValidateMode = 'onChange',\n criteriaMode,\n delayError,\n shouldFocusError = true,\n onSubmit,\n } = options;\n\n // -------------------------------------------------------------------------\n // Initialize React Hook Form\n // -------------------------------------------------------------------------\n\n const form = useForm<TData>({\n defaultValues: defaultValues as DefaultValues<TData>,\n values: values,\n resolver: resolver as unknown as RhfResolver<TData>,\n mode,\n reValidateMode,\n criteriaMode,\n delayError,\n shouldFocusError,\n });\n\n // -------------------------------------------------------------------------\n // Handle controlled values updates\n // -------------------------------------------------------------------------\n\n const prevValuesRef = useRef(values);\n\n useEffect(() => {\n if (values && JSON.stringify(values) !== JSON.stringify(prevValuesRef.current)) {\n prevValuesRef.current = values;\n }\n }, [values]);\n\n // -------------------------------------------------------------------------\n // Build submit handler\n // -------------------------------------------------------------------------\n\n const handleSubmit = form.handleSubmit(async (data) => {\n if (onSubmit) {\n await onSubmit(data as TData);\n }\n });\n\n // -------------------------------------------------------------------------\n // Build the adapter API\n // -------------------------------------------------------------------------\n\n const api: FormAdapter<TData> = {\n // ---------------------------------------------------------------------\n // CORE PROPERTIES\n // ---------------------------------------------------------------------\n\n control: form.control,\n\n get formState(): FormState {\n const state = form.formState;\n return {\n isSubmitting: state.isSubmitting,\n isValidating: state.isValidating,\n isDirty: state.isDirty,\n isValid: state.isValid,\n isLoading: state.isLoading,\n errors: normalizeErrors(state.errors),\n dirtyFields: flattenNestedObject(state.dirtyFields),\n touchedFields: flattenNestedObject(state.touchedFields),\n submitCount: state.submitCount,\n };\n },\n\n handleSubmit,\n\n // ---------------------------------------------------------------------\n // VALUE MANAGEMENT\n // ---------------------------------------------------------------------\n\n getValues: () => form.getValues(),\n\n setValue: (name: string, value: unknown, opts?: SetValueOptions) => {\n form.setValue(name as Path<TData>, value as PathValue<TData, Path<TData>>, {\n shouldValidate: opts?.shouldValidate,\n shouldDirty: opts?.shouldDirty ?? true,\n shouldTouch: opts?.shouldTouch,\n });\n },\n\n reset: (vals) => form.reset(vals as DefaultValues<TData>),\n\n watch: <T = unknown>(name?: string): T => {\n return form.watch(name as Path<TData>) as T;\n },\n\n // ---------------------------------------------------------------------\n // VALIDATION\n // ---------------------------------------------------------------------\n\n validate: async (name) => {\n if (name) {\n const names = Array.isArray(name) ? name : [name];\n return form.trigger(names as Path<TData>[]);\n }\n return form.trigger();\n },\n\n setError: (name: string, error: FieldError) => {\n form.setError(name as Path<TData>, {\n type: error.type || 'manual',\n message: error.message,\n });\n },\n\n clearErrors: (name) => {\n if (name) {\n const names = Array.isArray(name) ? name : [name];\n names.forEach(n => form.clearErrors(n as Path<TData>));\n } else {\n form.clearErrors();\n }\n },\n\n // ---------------------------------------------------------------------\n // ARRAYS\n // ---------------------------------------------------------------------\n\n array: createArrayHelpers(\n (path) => form.getValues(path as Path<TData>) as unknown[],\n (path, value) => form.setValue(\n path as Path<TData>,\n value as PathValue<TData, Path<TData>>,\n { shouldDirty: true }\n )\n ),\n\n // ---------------------------------------------------------------------\n // OPTIONAL ENHANCED FEATURES\n // ---------------------------------------------------------------------\n\n onBlur: (name: string) => {\n // Mark field as touched\n const hasError = !!getNestedValue(form.formState.errors, name);\n\n // Trigger validation based on mode\n if (mode === 'onBlur' || mode === 'all') {\n form.trigger(name as Path<TData>);\n } else if (hasError && reValidateMode === 'onBlur') {\n // Re-validate if field has error and reValidateMode is onBlur\n form.trigger(name as Path<TData>);\n }\n },\n\n getFieldState: (name: string) => {\n const state = form.getFieldState(name as Path<TData>, form.formState);\n return {\n isDirty: state.isDirty,\n isTouched: state.isTouched,\n invalid: state.invalid,\n error: state.error?.message,\n };\n },\n\n setFocus: (name: string, options?: { shouldSelect?: boolean }) => {\n form.setFocus(name as Path<TData>, options);\n },\n\n unregister: (name: string | string[]) => {\n const names = Array.isArray(name) ? name : [name];\n names.forEach(n => form.unregister(n as Path<TData>));\n },\n };\n\n return api;\n}\n\n// =============================================================================\n// HELPER: Watch hook for external use\n// =============================================================================\n\n/**\n * Hook to watch specific field values reactively.\n * Use this when you need to react to field changes outside of components.\n * \n * @param control - The control object from useRhf (form.control)\n * @param name - Field path(s) to watch\n */\nexport function useRhfWatch<TData extends FieldValues, TValue = unknown>(\n control: Control<TData>,\n name: string | string[]\n): TValue {\n if (Array.isArray(name)) {\n return useWatch({ control, name: name as unknown as Path<TData>[] }) as TValue;\n }\n return useWatch({ control, name: name as Path<TData> }) as TValue;\n}\n\n// =============================================================================\n// UTILITIES\n// =============================================================================\n\n/**\n * Normalize RHF's nested error structure to flat string map.\n */\nfunction normalizeErrors<TData extends FieldValues>(\n errors: FieldErrors<TData>\n): Record<string, string | string[] | undefined> {\n const result: Record<string, string | string[] | undefined> = {};\n\n function traverse(obj: Record<string, unknown>, prefix = '') {\n for (const key in obj) {\n const path = prefix ? `${prefix}.${key}` : key;\n const value = obj[key];\n\n if (isRecord(value) && 'message' in value && typeof value.message === 'string') {\n // Leaf error\n result[path] = value.message;\n } else if (\n isRecord(value) &&\n 'root' in value &&\n isRecord(value.root) &&\n 'message' in value.root &&\n typeof value.root.message === 'string'\n ) {\n // Array root error\n result[path] = value.root.message;\n } else if (isRecord(value)) {\n // Nested object, traverse deeper\n traverse(value, path);\n }\n }\n }\n\n traverse(errors as unknown as Record<string, unknown>);\n return result;\n}\n\n\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n// =============================================================================\n// RE-EXPORTS FOR CONVENIENCE\n// =============================================================================\n\nexport type { UseFormReturn as RhfForm } from 'react-hook-form';","import { nanoid } from 'nanoid';\nimport type { ArrayHelpers } from '../types';\n\n/**\n * Creates a standardized set of array field manipulation methods.\n * Abstracts the difference between getting/setting values in different form libraries.\n * \n * @param getArray - Function to get current array value at a path\n * @param setArray - Function to set array value at a path\n */\nexport function createArrayHelpers(\n getArray: (path: string) => unknown[],\n setArray: (path: string, value: unknown[]) => void\n): ArrayHelpers {\n return {\n fields: <T = unknown>(path: string): Array<T & { id: string }> => {\n const arr = getArray(path);\n if (!Array.isArray(arr)) return [];\n return arr.map((item, index) => ({\n id: (item as Record<string, unknown>)?.id as string || `${path}-${index}`,\n ...item as T,\n }));\n },\n\n append: (path: string, value: unknown) => {\n const current = getArray(path) || [];\n const itemWithId = ensureId(value);\n setArray(path, [...current, itemWithId]);\n },\n\n prepend: (path: string, value: unknown) => {\n const current = getArray(path) || [];\n const itemWithId = ensureId(value);\n setArray(path, [itemWithId, ...current]);\n },\n\n insert: (path: string, index: number, value: unknown) => {\n const current = [...(getArray(path) || [])];\n const itemWithId = ensureId(value);\n current.splice(index, 0, itemWithId);\n setArray(path, current);\n },\n\n remove: (path: string, index: number) => {\n const current = [...(getArray(path) || [])];\n current.splice(index, 1);\n setArray(path, current);\n },\n\n move: (path: string, from: number, to: number) => {\n const current = [...(getArray(path) || [])];\n const [item] = current.splice(from, 1);\n current.splice(to, 0, item);\n setArray(path, current);\n },\n\n swap: (path: string, indexA: number, indexB: number) => {\n const current = [...(getArray(path) || [])];\n const temp = current[indexA];\n current[indexA] = current[indexB];\n current[indexB] = temp;\n setArray(path, current);\n },\n\n replace: (path: string, values: unknown[]) => {\n const itemsWithIds = values.map(ensureId);\n setArray(path, itemsWithIds);\n },\n\n update: (path: string, index: number, value: unknown) => {\n const current = [...(getArray(path) || [])];\n // Preserve existing ID if present\n const existingId = (current[index] as Record<string, unknown>)?.id;\n current[index] = {\n ...(typeof value === 'object' && value !== null ? value : {}),\n id: existingId || nanoid(),\n };\n setArray(path, current);\n },\n };\n}\n\n/**\n * Ensures an item has a unique ID for React keys.\n */\nfunction ensureId(value: unknown): unknown {\n if (typeof value === 'object' && value !== null) {\n const obj = value as Record<string, unknown>;\n if (!obj.id) {\n return { ...obj, id: nanoid() };\n }\n return obj;\n }\n return { value, id: nanoid() };\n}\n","// =============================================================================\n// COMMON UTILITIES\n// These are used by both the core package and registry field components.\n// =============================================================================\n\n/**\n * Generate a unique field ID from the field path.\n * Converts dot notation to dashes and prefixes with 'field-'.\n * Used for accessibility (htmlFor, id attributes).\n * \n * @example\n * generateFieldId('user.profile.email') => 'field-user-profile-email'\n * generateFieldId('items[0].name') => 'field-items-0-name'\n */\nexport function generateFieldId(path: string): string {\n return `field-${path.replace(/\\./g, \"-\").replace(/\\[/g, \"-\").replace(/\\]/g, \"\")}`;\n}\n\n/**\n * Safely retrieve a nested value from an object using a dot-notation path.\n * \n * @example\n * getNestedValue({ user: { name: 'John' } }, 'user.name') => 'John'\n * getNestedValue({ items: [{ id: 1 }] }, 'items.0.id') => 1\n */\nexport function getNestedValue(obj: unknown, path: string): unknown {\n if (!obj || !path) return undefined;\n return path.split(\".\").reduce<unknown>((acc: unknown, key: string) => {\n if (acc && typeof acc === \"object\" && acc !== null) {\n return (acc as Record<string, unknown>)[key];\n }\n return undefined;\n }, obj);\n}\n\n/**\n * Set a nested value in an object using a dot-notation path.\n * Creates intermediate objects/arrays as needed.\n * \n * @example\n * setNestedValue({}, 'user.name', 'John') => { user: { name: 'John' } }\n */\nexport function setNestedValue<T extends Record<string, unknown>>(\n obj: T,\n path: string,\n value: unknown\n): T {\n const keys = path.split(\".\");\n const result = { ...obj } as Record<string, unknown>;\n let current = result;\n\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i];\n if (!(key in current) || typeof current[key] !== \"object\") {\n // Check if next key is numeric (array index)\n const nextKey = keys[i + 1];\n current[key] = /^\\d+$/.test(nextKey) ? [] : {};\n } else {\n current[key] = Array.isArray(current[key])\n ? [...(current[key] as unknown[])]\n : { ...(current[key] as Record<string, unknown>) };\n }\n current = current[key] as Record<string, unknown>;\n }\n\n current[keys[keys.length - 1]] = value;\n return result as T;\n}\n\n/**\n * Format bytes into a human-readable string.\n * \n * @example\n * formatBytes(1024) => '1 KB'\n * formatBytes(1234567) => '1.18 MB'\n */\nexport function formatBytes(bytes: number, decimals = 2): string {\n if (bytes === 0) return '0 Bytes';\n\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];\n\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;\n}\n\n/**\n * Flatten a nested object to dot-notation paths.\n * Useful for converting form library state (like dirtyFields, touchedFields)\n * to the flat format expected by FormState.\n * \n * @example\n * flattenNestedObject({ user: { name: true, email: true } })\n * // => { 'user.name': true, 'user.email': true }\n * \n * flattenNestedObject({ items: { 0: { title: true } } })\n * // => { 'items.0.title': true }\n */\nexport function flattenNestedObject(\n obj: Record<string, unknown>,\n prefix = ''\n): Record<string, boolean> {\n const result: Record<string, boolean> = {};\n\n for (const key in obj) {\n const path = prefix ? `${prefix}.${key}` : key;\n const value = obj[key];\n\n if (typeof value === 'boolean') {\n result[path] = value;\n } else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n Object.assign(result, flattenNestedObject(value as Record<string, unknown>, path));\n }\n }\n\n return result;\n}\n\n"],"mappings":"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","names":["options"]}
1
+ {"version":3,"sources":["../src/adapters/rhf.ts","../src/utils/array.ts","../src/lib/utils.ts"],"sourcesContent":["'use client';\n\nimport { useRef, useEffect } from 'react';\nimport { useForm, useWatch } from 'react-hook-form';\nimport type {\n Control,\n FieldValues,\n Path,\n PathValue,\n DefaultValues,\n FieldErrors,\n Resolver as RhfResolver\n} from 'react-hook-form';\nimport type {\n FormAdapter,\n AdapterOptions,\n FormState,\n FieldError,\n SetValueOptions,\n} from '../types';\nimport { createArrayHelpers } from '../utils';\nimport { getNestedValue, flattenNestedObject } from '../lib';\n\n// =============================================================================\n// RHF ADAPTER OPTIONS\n// =============================================================================\n\n/**\n * Options specific to the React Hook Form adapter.\n * Extends base AdapterOptions with RHF-specific features.\n */\nexport interface RhfAdapterOptions<TData extends FieldValues = FieldValues>\n extends AdapterOptions<TData> {\n /**\n * React Hook Form's reValidateMode.\n * When to re-validate after initial validation.\n * @default 'onChange'\n */\n reValidateMode?: 'onChange' | 'onBlur' | 'onSubmit';\n\n /**\n * Validation strategy before submit.\n * - 'firstError': Return first error only (faster)\n * - 'all': Return all errors (better UX for complex forms)\n * @default 'firstError'\n */\n criteriaMode?: 'firstError' | 'all';\n\n /**\n * Delay validation by specified ms (debounce).\n * Useful for expensive async validation.\n */\n delayError?: number;\n\n /**\n * Focus on the first field with an error after submit.\n * @default true\n */\n shouldFocusError?: boolean;\n}\n\n// =============================================================================\n// RHF ADAPTER\n// =============================================================================\n\n/**\n * React Hook Form adapter implementing the FormAdapter interface.\n * \n * This is the default adapter for BuzzForm. It provides full implementation\n * of all required and optional FormAdapter methods using React Hook Form.\n * \n * @example\n * // In FormProvider\n * import { useRhf } from '@buildnbuzz/buzzform/rhf';\n * \n * <FormProvider adapter={useRhf}>\n * <App />\n * </FormProvider>\n * \n * @example\n * // Direct usage\n * const form = useRhf({\n * defaultValues: { email: '', password: '' },\n * resolver: zodResolver(schema),\n * onSubmit: async (data) => {\n * await loginUser(data);\n * },\n * });\n */\nexport function useRhf<TData extends FieldValues = FieldValues>(\n options: RhfAdapterOptions<TData>\n): FormAdapter<TData> {\n const {\n defaultValues,\n values,\n resolver,\n mode = 'onChange',\n reValidateMode = 'onChange',\n criteriaMode,\n delayError,\n shouldFocusError = true,\n onSubmit,\n } = options;\n\n // -------------------------------------------------------------------------\n // Initialize React Hook Form\n // -------------------------------------------------------------------------\n\n const form = useForm<TData>({\n defaultValues: defaultValues as DefaultValues<TData>,\n values: values,\n resolver: resolver as unknown as RhfResolver<TData>,\n mode,\n reValidateMode,\n criteriaMode,\n delayError,\n shouldFocusError,\n });\n\n // -------------------------------------------------------------------------\n // Handle controlled values updates\n // -------------------------------------------------------------------------\n\n const prevValuesRef = useRef(values);\n\n useEffect(() => {\n if (values && JSON.stringify(values) !== JSON.stringify(prevValuesRef.current)) {\n prevValuesRef.current = values;\n }\n }, [values]);\n\n // -------------------------------------------------------------------------\n // Build submit handler\n // -------------------------------------------------------------------------\n\n const handleSubmit = form.handleSubmit(async (data) => {\n if (onSubmit) {\n await onSubmit(data as TData);\n }\n });\n\n // -------------------------------------------------------------------------\n // Build the adapter API\n // -------------------------------------------------------------------------\n\n const api: FormAdapter<TData> = {\n // ---------------------------------------------------------------------\n // CORE PROPERTIES\n // ---------------------------------------------------------------------\n\n control: form.control,\n\n get formState(): FormState {\n const state = form.formState;\n return {\n isSubmitting: state.isSubmitting,\n isValidating: state.isValidating,\n isDirty: state.isDirty,\n isValid: state.isValid,\n isLoading: state.isLoading,\n errors: normalizeErrors(state.errors),\n dirtyFields: flattenNestedObject(state.dirtyFields),\n touchedFields: flattenNestedObject(state.touchedFields),\n submitCount: state.submitCount,\n };\n },\n\n handleSubmit,\n\n // ---------------------------------------------------------------------\n // VALUE MANAGEMENT\n // ---------------------------------------------------------------------\n\n getValues: () => form.getValues(),\n\n setValue: (name: string, value: unknown, opts?: SetValueOptions) => {\n form.setValue(name as Path<TData>, value as PathValue<TData, Path<TData>>, {\n shouldValidate: opts?.shouldValidate,\n shouldDirty: opts?.shouldDirty ?? true,\n shouldTouch: opts?.shouldTouch,\n });\n },\n\n reset: (vals) => form.reset(vals as DefaultValues<TData>),\n\n watch: <T = unknown>(name?: string): T => {\n return form.watch(name as Path<TData>) as T;\n },\n\n // ---------------------------------------------------------------------\n // VALIDATION\n // ---------------------------------------------------------------------\n\n validate: async (name) => {\n if (name) {\n const names = Array.isArray(name) ? name : [name];\n return form.trigger(names as Path<TData>[]);\n }\n return form.trigger();\n },\n\n setError: (name: string, error: FieldError) => {\n form.setError(name as Path<TData>, {\n type: error.type || 'manual',\n message: error.message,\n });\n },\n\n clearErrors: (name) => {\n if (name) {\n const names = Array.isArray(name) ? name : [name];\n names.forEach(n => form.clearErrors(n as Path<TData>));\n } else {\n form.clearErrors();\n }\n },\n\n // ---------------------------------------------------------------------\n // ARRAYS\n // ---------------------------------------------------------------------\n\n array: createArrayHelpers(\n (path) => form.getValues(path as Path<TData>) as unknown[],\n (path, value) => form.setValue(\n path as Path<TData>,\n value as PathValue<TData, Path<TData>>,\n { shouldDirty: true }\n )\n ),\n\n // ---------------------------------------------------------------------\n // OPTIONAL ENHANCED FEATURES\n // ---------------------------------------------------------------------\n\n onBlur: (name: string) => {\n // Mark field as touched\n const hasError = !!getNestedValue(form.formState.errors, name);\n\n // Trigger validation based on mode\n if (mode === 'onBlur' || mode === 'all') {\n form.trigger(name as Path<TData>);\n } else if (hasError && reValidateMode === 'onBlur') {\n // Re-validate if field has error and reValidateMode is onBlur\n form.trigger(name as Path<TData>);\n }\n },\n\n getFieldState: (name: string) => {\n const state = form.getFieldState(name as Path<TData>, form.formState);\n return {\n isDirty: state.isDirty,\n isTouched: state.isTouched,\n invalid: state.invalid,\n error: state.error?.message,\n };\n },\n\n setFocus: (name: string, options?: { shouldSelect?: boolean }) => {\n form.setFocus(name as Path<TData>, options);\n },\n\n unregister: (name: string | string[]) => {\n const names = Array.isArray(name) ? name : [name];\n names.forEach(n => form.unregister(n as Path<TData>));\n },\n };\n\n return api;\n}\n\n// =============================================================================\n// HELPER: Watch hook for external use\n// =============================================================================\n\n/**\n * Hook to watch specific field values reactively.\n * Use this when you need to react to field changes outside of components.\n * \n * @param control - The control object from useRhf (form.control)\n * @param name - Field path(s) to watch\n */\nexport function useRhfWatch<TData extends FieldValues, TValue = unknown>(\n control: Control<TData>,\n name: string | string[]\n): TValue {\n if (Array.isArray(name)) {\n return useWatch({ control, name: name as unknown as Path<TData>[] }) as TValue;\n }\n return useWatch({ control, name: name as Path<TData> }) as TValue;\n}\n\n// =============================================================================\n// UTILITIES\n// =============================================================================\n\n/**\n * Normalize RHF's nested error structure to flat string map.\n */\nfunction normalizeErrors<TData extends FieldValues>(\n errors: FieldErrors<TData>\n): Record<string, string | string[] | undefined> {\n const result: Record<string, string | string[] | undefined> = {};\n\n function traverse(obj: Record<string, unknown>, prefix = '') {\n for (const key in obj) {\n const path = prefix ? `${prefix}.${key}` : key;\n const value = obj[key];\n\n if (isRecord(value) && 'message' in value && typeof value.message === 'string') {\n // Leaf error\n result[path] = value.message;\n } else if (\n isRecord(value) &&\n 'root' in value &&\n isRecord(value.root) &&\n 'message' in value.root &&\n typeof value.root.message === 'string'\n ) {\n // Array root error\n result[path] = value.root.message;\n } else if (isRecord(value)) {\n // Nested object, traverse deeper\n traverse(value, path);\n }\n }\n }\n\n traverse(errors as unknown as Record<string, unknown>);\n return result;\n}\n\n\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n// =============================================================================\n// RE-EXPORTS FOR CONVENIENCE\n// =============================================================================\n\nexport type { UseFormReturn as RhfForm } from 'react-hook-form';","import { nanoid } from 'nanoid';\nimport type { ArrayHelpers } from '../types';\n\n/**\n * Creates a standardized set of array field manipulation methods.\n * Abstracts the difference between getting/setting values in different form libraries.\n * \n * @param getArray - Function to get current array value at a path\n * @param setArray - Function to set array value at a path\n */\nexport function createArrayHelpers(\n getArray: (path: string) => unknown,\n setArray: (path: string, value: unknown[]) => void\n): ArrayHelpers {\n const readArray = (path: string): unknown[] => {\n const value = getArray(path);\n return Array.isArray(value) ? value : [];\n };\n\n return {\n fields: <T = unknown>(path: string): Array<T & { id: string }> => {\n const arr = readArray(path);\n return arr.map((item, index) => {\n const itemObject =\n typeof item === 'object' && item !== null\n ? (item as Record<string, unknown>)\n : ({ value: item } as Record<string, unknown>);\n\n return {\n id: (itemObject.id as string) || `${path}-${index}`,\n ...(itemObject as T),\n };\n });\n },\n\n append: (path: string, value: unknown) => {\n const current = readArray(path);\n const itemWithId = ensureId(value);\n setArray(path, [...current, itemWithId]);\n },\n\n prepend: (path: string, value: unknown) => {\n const current = readArray(path);\n const itemWithId = ensureId(value);\n setArray(path, [itemWithId, ...current]);\n },\n\n insert: (path: string, index: number, value: unknown) => {\n const current = [...readArray(path)];\n const itemWithId = ensureId(value);\n current.splice(index, 0, itemWithId);\n setArray(path, current);\n },\n\n remove: (path: string, index: number) => {\n const current = [...readArray(path)];\n current.splice(index, 1);\n setArray(path, current);\n },\n\n move: (path: string, from: number, to: number) => {\n const current = [...readArray(path)];\n const [item] = current.splice(from, 1);\n current.splice(to, 0, item);\n setArray(path, current);\n },\n\n swap: (path: string, indexA: number, indexB: number) => {\n const current = [...readArray(path)];\n const temp = current[indexA];\n current[indexA] = current[indexB];\n current[indexB] = temp;\n setArray(path, current);\n },\n\n replace: (path: string, values: unknown[]) => {\n const itemsWithIds = values.map(ensureId);\n setArray(path, itemsWithIds);\n },\n\n update: (path: string, index: number, value: unknown) => {\n const current = [...readArray(path)];\n // Preserve existing ID if present\n const existingId = (current[index] as Record<string, unknown>)?.id;\n current[index] = {\n ...(typeof value === 'object' && value !== null ? value : {}),\n id: existingId || nanoid(),\n };\n setArray(path, current);\n },\n };\n}\n\n/**\n * Ensures an item has a unique ID for React keys.\n */\nfunction ensureId(value: unknown): unknown {\n if (typeof value === 'object' && value !== null) {\n const obj = value as Record<string, unknown>;\n if (!obj.id) {\n return { ...obj, id: nanoid() };\n }\n return obj;\n }\n return { value, id: nanoid() };\n}\n","// =============================================================================\n// COMMON UTILITIES\n// These are used by both the core package and registry field components.\n// =============================================================================\n\n/**\n * Generate a unique field ID from the field path.\n * Converts dot notation to dashes and prefixes with 'field-'.\n * Used for accessibility (htmlFor, id attributes).\n * \n * @example\n * generateFieldId('user.profile.email') => 'field-user-profile-email'\n * generateFieldId('items[0].name') => 'field-items-0-name'\n */\nexport function generateFieldId(path: string): string {\n return `field-${path.replace(/\\./g, \"-\").replace(/\\[/g, \"-\").replace(/\\]/g, \"\")}`;\n}\n\n/**\n * Safely retrieve a nested value from an object using a dot-notation path.\n * \n * @example\n * getNestedValue({ user: { name: 'John' } }, 'user.name') => 'John'\n * getNestedValue({ items: [{ id: 1 }] }, 'items.0.id') => 1\n */\nexport function getNestedValue(obj: unknown, path: string): unknown {\n if (!obj || !path) return undefined;\n return path.split(\".\").reduce<unknown>((acc: unknown, key: string) => {\n if (acc && typeof acc === \"object\" && acc !== null) {\n return (acc as Record<string, unknown>)[key];\n }\n return undefined;\n }, obj);\n}\n\n/**\n * Set a nested value in an object using a dot-notation path.\n * Creates intermediate objects/arrays as needed.\n * \n * @example\n * setNestedValue({}, 'user.name', 'John') => { user: { name: 'John' } }\n */\nexport function setNestedValue<T extends Record<string, unknown>>(\n obj: T,\n path: string,\n value: unknown\n): T {\n const keys = path.split(\".\");\n const result = { ...obj } as Record<string, unknown>;\n let current = result;\n\n for (let i = 0; i < keys.length - 1; i++) {\n const key = keys[i];\n if (!(key in current) || typeof current[key] !== \"object\") {\n // Check if next key is numeric (array index)\n const nextKey = keys[i + 1];\n current[key] = /^\\d+$/.test(nextKey) ? [] : {};\n } else {\n current[key] = Array.isArray(current[key])\n ? [...(current[key] as unknown[])]\n : { ...(current[key] as Record<string, unknown>) };\n }\n current = current[key] as Record<string, unknown>;\n }\n\n current[keys[keys.length - 1]] = value;\n return result as T;\n}\n\n/**\n * Format bytes into a human-readable string.\n * \n * @example\n * formatBytes(1024) => '1 KB'\n * formatBytes(1234567) => '1.18 MB'\n */\nexport function formatBytes(bytes: number, decimals = 2): string {\n if (bytes === 0) return '0 Bytes';\n\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];\n\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;\n}\n\n/**\n * Flatten a nested object to dot-notation paths.\n * Useful for converting form library state (like dirtyFields, touchedFields)\n * to the flat format expected by FormState.\n * \n * @example\n * flattenNestedObject({ user: { name: true, email: true } })\n * // => { 'user.name': true, 'user.email': true }\n * \n * flattenNestedObject({ items: { 0: { title: true } } })\n * // => { 'items.0.title': true }\n */\nexport function flattenNestedObject(\n obj: Record<string, unknown>,\n prefix = ''\n): Record<string, boolean> {\n const result: Record<string, boolean> = {};\n\n for (const key in obj) {\n const path = prefix ? `${prefix}.${key}` : key;\n const value = obj[key];\n\n if (typeof value === 'boolean') {\n result[path] = value;\n } else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\n Object.assign(result, flattenNestedObject(value as Record<string, unknown>, path));\n }\n }\n\n return result;\n}\n\n"],"mappings":"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","names":["options"]}
package/dist/rhf.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createArrayHelpers,
4
4
  flattenNestedObject,
5
5
  getNestedValue
6
- } from "./chunk-IMJ5FRK5.mjs";
6
+ } from "./chunk-VXG7KGOZ.mjs";
7
7
 
8
8
  // src/adapters/rhf.ts
9
9
  import { useRef, useEffect } from "react";
package/dist/schema.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { T as TextField, o as TextareaField, E as EmailField, P as PasswordField, N as NumberField, D as DateField, p as DatetimeField, r as SelectField, u as RadioField, s as CheckboxField, t as SwitchField, x as UploadField, w as TagsField, y as ArrayField, F as Field, G as GroupField } from './adapter-nQW28cyO.mjs';
2
- export { g as AdapterFactory, f as AdapterOptions, A as ArrayHelpers, B as BaseField, O as BuzzFormSchema, J as CollapsibleField, C as ConditionContext, L as DataField, k as FieldComponentProps, j as FieldCondition, d as FieldError, l as FieldInputProps, m as FieldInputRenderFn, n as FieldStyle, K as FieldType, b as FormAdapter, a as FormConfig, Q as FormSettings, c as FormState, M as LayoutField, e as Resolver, R as ResolverResult, z as RowField, q as SelectOption, S as SetValueOptions, H as Tab, I as TabsField, U as UseFormOptions, V as ValidationContext, i as ValidationFn, h as ValidationResult } from './adapter-nQW28cyO.mjs';
3
- export { F as FieldToZod, u as FieldValidator, a as FieldsToShape, c as InferSchema, I as InferType, S as SchemaBuilder, b as SchemaBuilderMap, h as coerceToDate, g as coerceToNumber, q as collectFieldValidators, l as createArrayHelpers, d as createSchema, e as extractValidationConfig, f as fieldsToZodSchema, p as formatBytes, n as generateFieldId, o as getNestedValue, i as getPatternErrorMessage, r as getSiblingData, t as getValueByPath, j as isFileLike, k as isFileTypeAccepted, m as makeOptional, s as setNestedValue } from './utils-BRY27BLX.mjs';
1
+ import { T as TextField, o as TextareaField, E as EmailField, P as PasswordField, N as NumberField, D as DateField, p as DatetimeField, r as SelectField, w as RadioField, s as CheckboxGroupField, t as CheckboxField, u as SwitchField, y as UploadField, x as TagsField, z as ArrayField, F as Field, G as GroupField } from './adapter-u4raoNx9.mjs';
2
+ export { g as AdapterFactory, f as AdapterOptions, A as ArrayHelpers, B as BaseField, Q as BuzzFormSchema, K as CollapsibleField, C as ConditionContext, M as DataField, k as FieldComponentProps, j as FieldCondition, d as FieldError, l as FieldInputProps, m as FieldInputRenderFn, n as FieldStyle, L as FieldType, b as FormAdapter, a as FormConfig, W as FormSettings, c as FormState, O as LayoutField, e as Resolver, R as ResolverResult, H as RowField, q as SelectOption, S as SetValueOptions, I as Tab, J as TabsField, U as UseFormOptions, V as ValidationContext, i as ValidationFn, h as ValidationResult } from './adapter-u4raoNx9.mjs';
3
+ export { F as FieldToZod, u as FieldValidator, a as FieldsToShape, c as InferSchema, I as InferType, S as SchemaBuilder, b as SchemaBuilderMap, h as coerceToDate, g as coerceToNumber, q as collectFieldValidators, l as createArrayHelpers, d as createSchema, e as extractValidationConfig, f as fieldsToZodSchema, p as formatBytes, n as generateFieldId, o as getNestedValue, i as getPatternErrorMessage, r as getSiblingData, t as getValueByPath, j as isFileLike, k as isFileTypeAccepted, m as makeOptional, s as setNestedValue } from './utils-BksL2RlA.mjs';
4
4
  import { z } from 'zod';
5
5
  import 'react';
6
6
 
@@ -22,6 +22,7 @@ declare function createDateFieldSchema(field: DateField | DatetimeField): z.ZodT
22
22
 
23
23
  declare function createSelectFieldSchema(field: SelectField): z.ZodTypeAny;
24
24
  declare function createRadioFieldSchema(field: RadioField): z.ZodTypeAny;
25
+ declare function createCheckboxGroupFieldSchema(field: CheckboxGroupField): z.ZodTypeAny;
25
26
 
26
27
  declare function createCheckboxFieldSchema(field: CheckboxField): z.ZodTypeAny;
27
28
  declare function createSwitchFieldSchema(field: SwitchField): z.ZodTypeAny;
@@ -42,4 +43,4 @@ declare function createArrayFieldSchema(field: ArrayField, fieldsToZodSchema: Sc
42
43
  */
43
44
  declare function createGroupFieldSchema(field: GroupField, fieldsToZodSchema: SchemaGenerator): z.ZodTypeAny;
44
45
 
45
- export { ArrayField, CheckboxField, DateField, DatetimeField, EmailField, Field, GroupField, NumberField, PasswordField, RadioField, SelectField, SwitchField, TagsField, TextField, TextareaField, UploadField, createArrayFieldSchema, createCheckboxFieldSchema, createDateFieldSchema, createEmailFieldSchema, createGroupFieldSchema, createNumberFieldSchema, createPasswordFieldSchema, createRadioFieldSchema, createSelectFieldSchema, createSwitchFieldSchema, createTagsFieldSchema, createTextFieldSchema, createUploadFieldSchema };
46
+ export { ArrayField, CheckboxField, DateField, DatetimeField, EmailField, Field, GroupField, NumberField, PasswordField, RadioField, SelectField, SwitchField, TagsField, TextField, TextareaField, UploadField, createArrayFieldSchema, createCheckboxFieldSchema, createCheckboxGroupFieldSchema, createDateFieldSchema, createEmailFieldSchema, createGroupFieldSchema, createNumberFieldSchema, createPasswordFieldSchema, createRadioFieldSchema, createSelectFieldSchema, createSwitchFieldSchema, createTagsFieldSchema, createTextFieldSchema, createUploadFieldSchema };
package/dist/schema.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { T as TextField, o as TextareaField, E as EmailField, P as PasswordField, N as NumberField, D as DateField, p as DatetimeField, r as SelectField, u as RadioField, s as CheckboxField, t as SwitchField, x as UploadField, w as TagsField, y as ArrayField, F as Field, G as GroupField } from './adapter-nQW28cyO.js';
2
- export { g as AdapterFactory, f as AdapterOptions, A as ArrayHelpers, B as BaseField, O as BuzzFormSchema, J as CollapsibleField, C as ConditionContext, L as DataField, k as FieldComponentProps, j as FieldCondition, d as FieldError, l as FieldInputProps, m as FieldInputRenderFn, n as FieldStyle, K as FieldType, b as FormAdapter, a as FormConfig, Q as FormSettings, c as FormState, M as LayoutField, e as Resolver, R as ResolverResult, z as RowField, q as SelectOption, S as SetValueOptions, H as Tab, I as TabsField, U as UseFormOptions, V as ValidationContext, i as ValidationFn, h as ValidationResult } from './adapter-nQW28cyO.js';
3
- export { F as FieldToZod, u as FieldValidator, a as FieldsToShape, c as InferSchema, I as InferType, S as SchemaBuilder, b as SchemaBuilderMap, h as coerceToDate, g as coerceToNumber, q as collectFieldValidators, l as createArrayHelpers, d as createSchema, e as extractValidationConfig, f as fieldsToZodSchema, p as formatBytes, n as generateFieldId, o as getNestedValue, i as getPatternErrorMessage, r as getSiblingData, t as getValueByPath, j as isFileLike, k as isFileTypeAccepted, m as makeOptional, s as setNestedValue } from './utils-DgwUn6tN.js';
1
+ import { T as TextField, o as TextareaField, E as EmailField, P as PasswordField, N as NumberField, D as DateField, p as DatetimeField, r as SelectField, w as RadioField, s as CheckboxGroupField, t as CheckboxField, u as SwitchField, y as UploadField, x as TagsField, z as ArrayField, F as Field, G as GroupField } from './adapter-u4raoNx9.js';
2
+ export { g as AdapterFactory, f as AdapterOptions, A as ArrayHelpers, B as BaseField, Q as BuzzFormSchema, K as CollapsibleField, C as ConditionContext, M as DataField, k as FieldComponentProps, j as FieldCondition, d as FieldError, l as FieldInputProps, m as FieldInputRenderFn, n as FieldStyle, L as FieldType, b as FormAdapter, a as FormConfig, W as FormSettings, c as FormState, O as LayoutField, e as Resolver, R as ResolverResult, H as RowField, q as SelectOption, S as SetValueOptions, I as Tab, J as TabsField, U as UseFormOptions, V as ValidationContext, i as ValidationFn, h as ValidationResult } from './adapter-u4raoNx9.js';
3
+ export { F as FieldToZod, u as FieldValidator, a as FieldsToShape, c as InferSchema, I as InferType, S as SchemaBuilder, b as SchemaBuilderMap, h as coerceToDate, g as coerceToNumber, q as collectFieldValidators, l as createArrayHelpers, d as createSchema, e as extractValidationConfig, f as fieldsToZodSchema, p as formatBytes, n as generateFieldId, o as getNestedValue, i as getPatternErrorMessage, r as getSiblingData, t as getValueByPath, j as isFileLike, k as isFileTypeAccepted, m as makeOptional, s as setNestedValue } from './utils-oLqFuT9W.js';
4
4
  import { z } from 'zod';
5
5
  import 'react';
6
6
 
@@ -22,6 +22,7 @@ declare function createDateFieldSchema(field: DateField | DatetimeField): z.ZodT
22
22
 
23
23
  declare function createSelectFieldSchema(field: SelectField): z.ZodTypeAny;
24
24
  declare function createRadioFieldSchema(field: RadioField): z.ZodTypeAny;
25
+ declare function createCheckboxGroupFieldSchema(field: CheckboxGroupField): z.ZodTypeAny;
25
26
 
26
27
  declare function createCheckboxFieldSchema(field: CheckboxField): z.ZodTypeAny;
27
28
  declare function createSwitchFieldSchema(field: SwitchField): z.ZodTypeAny;
@@ -42,4 +43,4 @@ declare function createArrayFieldSchema(field: ArrayField, fieldsToZodSchema: Sc
42
43
  */
43
44
  declare function createGroupFieldSchema(field: GroupField, fieldsToZodSchema: SchemaGenerator): z.ZodTypeAny;
44
45
 
45
- export { ArrayField, CheckboxField, DateField, DatetimeField, EmailField, Field, GroupField, NumberField, PasswordField, RadioField, SelectField, SwitchField, TagsField, TextField, TextareaField, UploadField, createArrayFieldSchema, createCheckboxFieldSchema, createDateFieldSchema, createEmailFieldSchema, createGroupFieldSchema, createNumberFieldSchema, createPasswordFieldSchema, createRadioFieldSchema, createSelectFieldSchema, createSwitchFieldSchema, createTagsFieldSchema, createTextFieldSchema, createUploadFieldSchema };
46
+ export { ArrayField, CheckboxField, DateField, DatetimeField, EmailField, Field, GroupField, NumberField, PasswordField, RadioField, SelectField, SwitchField, TagsField, TextField, TextareaField, UploadField, createArrayFieldSchema, createCheckboxFieldSchema, createCheckboxGroupFieldSchema, createDateFieldSchema, createEmailFieldSchema, createGroupFieldSchema, createNumberFieldSchema, createPasswordFieldSchema, createRadioFieldSchema, createSelectFieldSchema, createSwitchFieldSchema, createTagsFieldSchema, createTextFieldSchema, createUploadFieldSchema };
package/dist/schema.js CHANGED
@@ -26,6 +26,7 @@ __export(schema_exports, {
26
26
  createArrayFieldSchema: () => createArrayFieldSchema,
27
27
  createArrayHelpers: () => createArrayHelpers,
28
28
  createCheckboxFieldSchema: () => createCheckboxFieldSchema,
29
+ createCheckboxGroupFieldSchema: () => createCheckboxGroupFieldSchema,
29
30
  createDateFieldSchema: () => createDateFieldSchema,
30
31
  createEmailFieldSchema: () => createEmailFieldSchema,
31
32
  createGroupFieldSchema: () => createGroupFieldSchema,
@@ -158,6 +159,8 @@ function makeOptional(schema, fieldType) {
158
159
  case "checkbox":
159
160
  case "switch":
160
161
  return schema;
162
+ case "checkbox-group":
163
+ return schema.optional().default([]);
161
164
  case "tags":
162
165
  case "array":
163
166
  return schema.optional().default([]);
@@ -345,17 +348,51 @@ function createDateFieldSchema(field) {
345
348
 
346
349
  // src/schema/builders/select.ts
347
350
  var import_zod5 = require("zod");
348
- var selectValueSchema = import_zod5.z.union([
349
- import_zod5.z.string({ error: "Please select an option" }),
350
- import_zod5.z.number({ error: "Please select an option" }),
351
- import_zod5.z.boolean({ error: "Please select an option" })
352
- ], { error: "Please select an option" });
351
+ var selectValueSchema = import_zod5.z.union(
352
+ [
353
+ import_zod5.z.string({ error: "Please select an option" }),
354
+ import_zod5.z.number({ error: "Please select an option" }),
355
+ import_zod5.z.boolean({ error: "Please select an option" })
356
+ ],
357
+ { error: "Please select an option" }
358
+ );
359
+ function applyMultiSelectConstraints(schema, config) {
360
+ const { minSelected, maxSelected, required } = config;
361
+ let next = schema;
362
+ if (minSelected !== void 0 && minSelected > 0) {
363
+ const minMsg = `Select at least ${minSelected} option${minSelected !== 1 ? "s" : ""}`;
364
+ if (required) {
365
+ next = next.min(minSelected, minMsg);
366
+ } else {
367
+ next = next.refine(
368
+ (val) => val.length === 0 || val.length >= minSelected,
369
+ {
370
+ message: minMsg
371
+ }
372
+ );
373
+ }
374
+ }
375
+ if (required && (minSelected === void 0 || minSelected === 0)) {
376
+ next = next.min(1, "Select at least one option");
377
+ }
378
+ if (maxSelected !== void 0) {
379
+ next = next.max(
380
+ maxSelected,
381
+ `Select at most ${maxSelected} option${maxSelected !== 1 ? "s" : ""}`
382
+ );
383
+ }
384
+ return next;
385
+ }
353
386
  function createSelectFieldSchema(field) {
354
387
  if (field.hasMany) {
355
- let arraySchema = import_zod5.z.array(selectValueSchema, { error: "Invalid selection" });
356
- if (field.required) {
357
- arraySchema = arraySchema.min(1, "Select at least one option");
358
- }
388
+ let arraySchema = import_zod5.z.array(selectValueSchema, {
389
+ error: "Invalid selection"
390
+ });
391
+ arraySchema = applyMultiSelectConstraints(arraySchema, {
392
+ minSelected: field.minSelected,
393
+ maxSelected: field.maxSelected,
394
+ required: field.required
395
+ });
359
396
  if (!field.required) {
360
397
  return arraySchema.optional().default([]);
361
398
  }
@@ -386,6 +423,18 @@ function createRadioFieldSchema(field) {
386
423
  }
387
424
  return schema;
388
425
  }
426
+ function createCheckboxGroupFieldSchema(field) {
427
+ let schema = import_zod5.z.array(selectValueSchema, { error: "Invalid selection" });
428
+ schema = applyMultiSelectConstraints(schema, {
429
+ minSelected: field.minSelected,
430
+ maxSelected: field.maxSelected,
431
+ required: field.required
432
+ });
433
+ if (!field.required) {
434
+ return makeOptional(schema, "checkbox-group");
435
+ }
436
+ return schema;
437
+ }
389
438
 
390
439
  // src/schema/builders/boolean.ts
391
440
  var import_zod6 = require("zod");
@@ -550,6 +599,8 @@ function fieldToZod(field) {
550
599
  return createDateFieldSchema(field);
551
600
  case "select":
552
601
  return createSelectFieldSchema(field);
602
+ case "checkbox-group":
603
+ return createCheckboxGroupFieldSchema(field);
553
604
  case "radio":
554
605
  return createRadioFieldSchema(field);
555
606
  case "checkbox":
@@ -614,44 +665,50 @@ function createSchema(fields) {
614
665
  // src/utils/array.ts
615
666
  var import_nanoid = require("nanoid");
616
667
  function createArrayHelpers(getArray, setArray) {
668
+ const readArray = (path) => {
669
+ const value = getArray(path);
670
+ return Array.isArray(value) ? value : [];
671
+ };
617
672
  return {
618
673
  fields: (path) => {
619
- const arr = getArray(path);
620
- if (!Array.isArray(arr)) return [];
621
- return arr.map((item, index) => ({
622
- id: item?.id || `${path}-${index}`,
623
- ...item
624
- }));
674
+ const arr = readArray(path);
675
+ return arr.map((item, index) => {
676
+ const itemObject = typeof item === "object" && item !== null ? item : { value: item };
677
+ return {
678
+ id: itemObject.id || `${path}-${index}`,
679
+ ...itemObject
680
+ };
681
+ });
625
682
  },
626
683
  append: (path, value) => {
627
- const current = getArray(path) || [];
684
+ const current = readArray(path);
628
685
  const itemWithId = ensureId(value);
629
686
  setArray(path, [...current, itemWithId]);
630
687
  },
631
688
  prepend: (path, value) => {
632
- const current = getArray(path) || [];
689
+ const current = readArray(path);
633
690
  const itemWithId = ensureId(value);
634
691
  setArray(path, [itemWithId, ...current]);
635
692
  },
636
693
  insert: (path, index, value) => {
637
- const current = [...getArray(path) || []];
694
+ const current = [...readArray(path)];
638
695
  const itemWithId = ensureId(value);
639
696
  current.splice(index, 0, itemWithId);
640
697
  setArray(path, current);
641
698
  },
642
699
  remove: (path, index) => {
643
- const current = [...getArray(path) || []];
700
+ const current = [...readArray(path)];
644
701
  current.splice(index, 1);
645
702
  setArray(path, current);
646
703
  },
647
704
  move: (path, from, to) => {
648
- const current = [...getArray(path) || []];
705
+ const current = [...readArray(path)];
649
706
  const [item] = current.splice(from, 1);
650
707
  current.splice(to, 0, item);
651
708
  setArray(path, current);
652
709
  },
653
710
  swap: (path, indexA, indexB) => {
654
- const current = [...getArray(path) || []];
711
+ const current = [...readArray(path)];
655
712
  const temp = current[indexA];
656
713
  current[indexA] = current[indexB];
657
714
  current[indexB] = temp;
@@ -662,7 +719,7 @@ function createArrayHelpers(getArray, setArray) {
662
719
  setArray(path, itemsWithIds);
663
720
  },
664
721
  update: (path, index, value) => {
665
- const current = [...getArray(path) || []];
722
+ const current = [...readArray(path)];
666
723
  const existingId = current[index]?.id;
667
724
  current[index] = {
668
725
  ...typeof value === "object" && value !== null ? value : {},
@@ -729,6 +786,7 @@ function formatBytes(bytes, decimals = 2) {
729
786
  createArrayFieldSchema,
730
787
  createArrayHelpers,
731
788
  createCheckboxFieldSchema,
789
+ createCheckboxGroupFieldSchema,
732
790
  createDateFieldSchema,
733
791
  createEmailFieldSchema,
734
792
  createGroupFieldSchema,