@buildnbuzz/buzzform 0.1.0 → 0.1.2

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types/adapter.ts","../src/context/form-context.ts","../src/providers/form-provider.tsx","../src/hooks/use-form.ts"],"sourcesContent":["import type { FormEvent } from 'react';\r\nimport type { FormSettings } from './form';\r\n\r\n// =============================================================================\r\n// FORM STATE\r\n// =============================================================================\r\n\r\n/**\r\n * Represents the current reactive state of a form.\r\n * Adapters must ensure this triggers re-renders when values change.\r\n * \r\n * This is the MINIMUM state required for BuzzForm to work.\r\n * Custom adapters must provide all these properties.\r\n */\r\nexport interface FormState {\r\n /** True while the form is being submitted */\r\n isSubmitting: boolean;\r\n\r\n /** True while validation is running (async validators) */\r\n isValidating: boolean;\r\n\r\n /** True if any field has been modified from its default value */\r\n isDirty: boolean;\r\n\r\n /** True if all validations pass (no errors) */\r\n isValid: boolean;\r\n\r\n /** True while async default values are being resolved */\r\n isLoading: boolean;\r\n\r\n /** \r\n * Field-level errors.\r\n * Key is the field path (e.g., \"email\", \"address.city\", \"items.0.name\")\r\n * Value is the error message(s)\r\n * \r\n * NOTE: Path format uses dot notation. Array indices use numbers (items.0.name).\r\n */\r\n errors: Record<string, string | string[] | undefined>;\r\n\r\n /** Map of field paths to whether they've been modified */\r\n dirtyFields: Record<string, boolean>;\r\n\r\n /** Map of field paths to whether they've been touched (blurred) */\r\n touchedFields: Record<string, boolean>;\r\n\r\n /** Number of times the form has been submitted */\r\n submitCount: number;\r\n}\r\n\r\n// =============================================================================\r\n// VALUE MANAGEMENT\r\n// =============================================================================\r\n\r\n/**\r\n * Options when programmatically setting a field value.\r\n */\r\nexport interface SetValueOptions {\r\n /** Run validation after setting the value (default: adapter-specific) */\r\n shouldValidate?: boolean;\r\n\r\n /** Mark the field as dirty (default: true) */\r\n shouldDirty?: boolean;\r\n\r\n /** Mark the field as touched (default: false) */\r\n shouldTouch?: boolean;\r\n}\r\n\r\n// =============================================================================\r\n// VALIDATION\r\n// =============================================================================\r\n\r\n/**\r\n * Represents a field-level error.\r\n */\r\nexport interface FieldError {\r\n /** Error type (e.g., 'required', 'pattern', 'server', 'custom') */\r\n type?: string;\r\n\r\n /** Human-readable error message */\r\n message: string;\r\n}\r\n\r\n/**\r\n * Result from a validation resolver.\r\n */\r\nexport interface ResolverResult<TData> {\r\n /** Parsed/transformed values (if validation passes) */\r\n values?: TData;\r\n\r\n /** Field errors (if validation fails) */\r\n errors?: Record<string, FieldError>;\r\n}\r\n\r\n/**\r\n * A validation resolver function.\r\n * Adapters use this to validate form values against a schema.\r\n * \r\n * @example\r\n * // Zod resolver\r\n * const zodResolver = (schema) => async (values) => {\r\n * const result = schema.safeParse(values);\r\n * if (result.success) return { values: result.data };\r\n * return { errors: mapZodErrors(result.error) };\r\n * };\r\n */\r\nexport type Resolver<TData> = (\r\n values: TData\r\n) => Promise<ResolverResult<TData>> | ResolverResult<TData>;\r\n\r\n// =============================================================================\r\n// ARRAY HELPERS\r\n// =============================================================================\r\n\r\n/**\r\n * Helper methods for manipulating array fields.\r\n * All methods operate on a field path (e.g., \"items\", \"users.0.tags\").\r\n * \r\n * This is REQUIRED for ArrayField to work. If your custom adapter doesn't\r\n * support arrays, you can implement these as no-ops or throw errors.\r\n */\r\nexport interface ArrayHelpers {\r\n /**\r\n * Get array items with stable IDs for React keys.\r\n * @param path - Field path to the array\r\n * @returns Array of items, each with an `id` property for React keys\r\n */\r\n fields: <T = unknown>(path: string) => Array<T & { id: string }>;\r\n\r\n /**\r\n * Add an item to the end of the array.\r\n */\r\n append: (path: string, value: unknown) => void;\r\n\r\n /**\r\n * Add an item to the beginning of the array.\r\n */\r\n prepend: (path: string, value: unknown) => void;\r\n\r\n /**\r\n * Insert an item at a specific index.\r\n */\r\n insert: (path: string, index: number, value: unknown) => void;\r\n\r\n /**\r\n * Remove an item at a specific index.\r\n */\r\n remove: (path: string, index: number) => void;\r\n\r\n /**\r\n * Move an item from one index to another.\r\n * Used for drag-and-drop reordering.\r\n */\r\n move: (path: string, from: number, to: number) => void;\r\n\r\n /**\r\n * Swap two items by their indices.\r\n */\r\n swap: (path: string, indexA: number, indexB: number) => void;\r\n\r\n /**\r\n * Replace the entire array with new values.\r\n */\r\n replace: (path: string, values: unknown[]) => void;\r\n\r\n /**\r\n * Update an item at a specific index.\r\n */\r\n update: (path: string, index: number, value: unknown) => void;\r\n}\r\n\r\n// =============================================================================\r\n// ADAPTER OPTIONS\r\n// =============================================================================\r\n\r\n/**\r\n * Base options passed to any adapter when creating a form instance.\r\n * Adapters can extend this with library-specific options.\r\n * \r\n * @typeParam TData - The shape of form data\r\n */\r\nexport interface AdapterOptions<TData = Record<string, unknown>> {\r\n /**\r\n * Initial values for the form.\r\n * Can be:\r\n * - A static object\r\n * - A sync function returning values\r\n * - An async function returning values (form shows loading state)\r\n */\r\n defaultValues?: TData | (() => TData) | (() => Promise<TData>);\r\n\r\n /**\r\n * Controlled values - when provided, form becomes controlled.\r\n * Changes to this prop will update form values.\r\n */\r\n values?: TData;\r\n\r\n /**\r\n * Validation resolver.\r\n * Called to validate form values before submission and optionally on change/blur.\r\n */\r\n resolver?: Resolver<TData>;\r\n\r\n /**\r\n * When to run validation.\r\n * - 'onChange': Validate on every value change\r\n * - 'onBlur': Validate when fields lose focus\r\n * - 'onSubmit': Validate only on submit\r\n * - 'all': Validate on all events\r\n * \r\n * NOTE: Not all adapters support all modes. Check adapter documentation.\r\n */\r\n mode?: 'onChange' | 'onBlur' | 'onSubmit' | 'all';\r\n\r\n /**\r\n * When to re-validate after initial error.\r\n * NOTE: This is optional. Some form libraries don't have this concept.\r\n */\r\n reValidateMode?: 'onChange' | 'onBlur' | 'onSubmit';\r\n\r\n /**\r\n * Callback when form is submitted (after validation passes).\r\n */\r\n onSubmit?: (values: TData) => Promise<void> | void;\r\n}\r\n\r\n// =============================================================================\r\n// FORM ADAPTER INTERFACE\r\n// =============================================================================\r\n\r\n/**\r\n * The contract any form adapter must fulfill.\r\n * \r\n * ## Required vs Optional\r\n * \r\n * **Required methods** are the building blocks that BuzzForm needs to function.\r\n * If any are missing, forms will break.\r\n * \r\n * **Optional methods** (marked with `?`) provide enhanced functionality but\r\n * are not required. BuzzForm will gracefully degrade without them.\r\n * \r\n * ## Creating a Custom Adapter\r\n * \r\n * To create a custom adapter (e.g., for useActionState, Formik, or vanilla React):\r\n * \r\n * 1. Implement all required properties and methods\r\n * 2. Optionally implement enhanced features\r\n * 3. Return the adapter from a hook (factory function)\r\n * \r\n * @example\r\n * // Minimal custom adapter skeleton\r\n * function useMyAdapter<T>(options: AdapterOptions<T>): FormAdapter<T> {\r\n * const [values, setValues] = useState(options.defaultValues ?? {});\r\n * const [errors, setErrors] = useState({});\r\n * const [isSubmitting, setIsSubmitting] = useState(false);\r\n * \r\n * return {\r\n * control: null, // Your state/context\r\n * get formState() { return { ... } },\r\n * handleSubmit: async (e) => { ... },\r\n * getValues: () => values,\r\n * setValue: (name, value) => { ... },\r\n * reset: (vals) => setValues(vals ?? {}),\r\n * watch: (name) => name ? values[name] : values,\r\n * validate: async () => true,\r\n * setError: (name, error) => { ... },\r\n * clearErrors: () => setErrors({}),\r\n * array: createArrayHelpers(...),\r\n * };\r\n * }\r\n * \r\n * @typeParam TData - The shape of form data\r\n */\r\nexport interface FormAdapter<TData = Record<string, unknown>> {\r\n // =========================================================================\r\n // CORE PROPERTIES (Required)\r\n // =========================================================================\r\n\r\n /**\r\n * Form-level behavior settings.\r\n * Set by useForm after applying FormSettings.\r\n */\r\n settings?: FormSettings;\r\n\r\n /**\r\n * The underlying form library's control/instance.\r\n * \r\n * This is passed to field components that need direct access to the form\r\n * library (e.g., for React Hook Form's Controller).\r\n * \r\n * For custom adapters, this can be:\r\n * - Your state object\r\n * - A context value\r\n * - null (if not needed)\r\n */\r\n control: unknown;\r\n\r\n /**\r\n * Current form state.\r\n * MUST be implemented as a getter to ensure reactivity.\r\n * \r\n * @example\r\n * get formState() {\r\n * return {\r\n * isSubmitting: this._isSubmitting,\r\n * isValidating: false,\r\n * isDirty: Object.keys(this._touched).length > 0,\r\n * isValid: Object.keys(this._errors).length === 0,\r\n * isLoading: false,\r\n * errors: this._errors,\r\n * dirtyFields: this._dirty,\r\n * touchedFields: this._touched,\r\n * submitCount: this._submitCount,\r\n * };\r\n * }\r\n */\r\n formState: FormState;\r\n\r\n /**\r\n * Submit handler to attach to a form element.\r\n * Should prevent default, run validation, and call onSubmit if valid.\r\n * \r\n * @example\r\n * <form onSubmit={adapter.handleSubmit}>\r\n */\r\n handleSubmit: (e?: FormEvent) => Promise<void> | void;\r\n\r\n // =========================================================================\r\n // VALUE MANAGEMENT (Required)\r\n // =========================================================================\r\n\r\n /**\r\n * Get all current form values.\r\n */\r\n getValues: () => TData;\r\n\r\n /**\r\n * Set a single field's value.\r\n * \r\n * @param name - Field path (e.g., \"email\", \"address.city\", \"items.0.name\")\r\n * @param value - New value\r\n * @param options - Additional options\r\n */\r\n setValue: (\r\n name: string,\r\n value: unknown,\r\n options?: SetValueOptions\r\n ) => void;\r\n\r\n /**\r\n * Reset form to default values or provided values.\r\n * \r\n * @param values - Optional new values to reset to\r\n */\r\n reset: (values?: Partial<TData>) => void;\r\n\r\n /**\r\n * Watch one or more field values reactively.\r\n * Returns current value(s) and causes re-render when they change.\r\n * \r\n * @param name - Field path to watch, or undefined for all values\r\n * @returns Current value(s)\r\n */\r\n watch: <T = unknown>(name?: string) => T;\r\n\r\n // =========================================================================\r\n // VALIDATION (Required)\r\n // =========================================================================\r\n\r\n /**\r\n * Manually trigger validation.\r\n * \r\n * @param name - Field(s) to validate, or undefined for entire form\r\n * @returns True if validation passes\r\n */\r\n validate: (name?: string | string[]) => Promise<boolean>;\r\n\r\n /**\r\n * Set a field error programmatically.\r\n * Useful for server-side validation errors.\r\n * \r\n * @param name - Field path\r\n * @param error - Error details\r\n */\r\n setError: (name: string, error: FieldError) => void;\r\n\r\n /**\r\n * Clear validation errors.\r\n * \r\n * @param name - Field(s) to clear, or undefined for all errors\r\n */\r\n clearErrors: (name?: string | string[]) => void;\r\n\r\n // =========================================================================\r\n // ARRAYS (Required for ArrayField)\r\n // =========================================================================\r\n\r\n /**\r\n * Helpers for manipulating array fields.\r\n * Required if you use ArrayField component.\r\n */\r\n array: ArrayHelpers;\r\n\r\n // =========================================================================\r\n // OPTIONAL ENHANCED FEATURES\r\n // These provide better UX but are not required for basic functionality.\r\n // =========================================================================\r\n\r\n /**\r\n * Handle field blur event.\r\n * Triggers validation if mode is 'onBlur'.\r\n * \r\n * If not provided, BuzzForm will not trigger blur-based validation.\r\n * \r\n * @param name - Field path\r\n */\r\n onBlur?: (name: string) => void;\r\n\r\n /**\r\n * Get the state of a specific field.\r\n * More efficient than accessing the entire formState for single fields.\r\n * \r\n * @param name - Field path\r\n */\r\n getFieldState?: (name: string) => {\r\n isDirty: boolean;\r\n isTouched: boolean;\r\n invalid: boolean;\r\n error?: string;\r\n };\r\n\r\n /**\r\n * Programmatically focus a field.\r\n * Useful for accessibility and after adding array items.\r\n * \r\n * @param name - Field path\r\n * @param options - Focus options\r\n */\r\n setFocus?: (name: string, options?: { shouldSelect?: boolean }) => void;\r\n\r\n /**\r\n * Unregister a field from the form.\r\n * Called when conditional fields are hidden.\r\n * \r\n * If not provided, hidden fields will retain their values.\r\n * \r\n * @param name - Field path(s) to unregister\r\n */\r\n unregister?: (name: string | string[]) => void;\r\n}\r\n\r\n// =============================================================================\r\n// ADAPTER FACTORY TYPE\r\n// =============================================================================\r\n\r\n/**\r\n * Type for an adapter factory function (hook).\r\n * \r\n * @example\r\n * // Using the adapter\r\n * function MyApp() {\r\n * return (\r\n * <FormProvider adapter={useRhfAdapter}>\r\n * <MyForm />\r\n * </FormProvider>\r\n * );\r\n * }\r\n */\r\nexport type AdapterFactory<TData = Record<string, unknown>> = (\r\n options: AdapterOptions<TData>\r\n) => FormAdapter<TData>;\r\n\r\n// =============================================================================\r\n// VALIDATION HELPERS FOR CUSTOM ADAPTERS\r\n// =============================================================================\r\n\r\n/**\r\n * Validates that a FormAdapter has all required methods.\r\n * Call this in development to catch missing implementations early.\r\n * \r\n * @param adapter - The adapter to validate\r\n * @param adapterName - Name for error messages\r\n * @throws Error if required methods are missing\r\n */\r\nexport function validateAdapter(adapter: FormAdapter, adapterName = 'adapter'): void {\r\n const required = [\r\n 'control',\r\n 'formState',\r\n 'handleSubmit',\r\n 'getValues',\r\n 'setValue',\r\n 'reset',\r\n 'watch',\r\n 'validate',\r\n 'setError',\r\n 'clearErrors',\r\n 'array',\r\n ] as const;\r\n\r\n for (const key of required) {\r\n if (adapter[key] === undefined) {\r\n throw new Error(\r\n `Invalid FormAdapter: \"${adapterName}\" is missing required property \"${key}\". ` +\r\n `See FormAdapter interface for implementation requirements.`\r\n );\r\n }\r\n }\r\n\r\n // Validate array helpers\r\n const arrayMethods = [\r\n 'fields', 'append', 'prepend', 'insert',\r\n 'remove', 'move', 'swap', 'replace', 'update'\r\n ] as const;\r\n\r\n for (const method of arrayMethods) {\r\n if (typeof adapter.array[method] !== 'function') {\r\n throw new Error(\r\n `Invalid FormAdapter: \"${adapterName}.array.${method}\" must be a function.`\r\n );\r\n }\r\n }\r\n}\r\n","'use client';\r\n\r\nimport { createContext } from 'react';\r\nimport type { FormConfig } from '../types';\r\n\r\n/**\r\n * Context for global form configuration.\r\n * Set via FormProvider, consumed by useForm.\r\n */\r\nexport const FormConfigContext = createContext<FormConfig | null>(null);\r\n","\"use client\";\r\n\r\nimport React from \"react\";\r\nimport { FormConfigContext } from \"../context/form-context\";\r\nimport type { FormConfig } from \"../types\";\r\n\r\n/**\r\n * Provider for global form configuration.\r\n * Set the adapter, resolver, and default mode for all forms in the app.\r\n *\r\n * @example\r\n * import { FormProvider } from '@buildnbuzz/buzzform';\r\n * import { useRhfAdapter } from '@buildnbuzz/buzzform/rhf';\r\n * import { zodResolver } from '@buildnbuzz/buzzform/resolvers/zod';\r\n *\r\n * <FormProvider\r\n * adapter={useRhfAdapter}\r\n * resolver={zodResolver}\r\n * mode=\"onBlur\"\r\n * >\r\n * <App />\r\n * </FormProvider>\r\n */\r\nexport const FormProvider: React.FC<\r\n FormConfig & { children: React.ReactNode }\r\n> = ({ children, ...config }) => {\r\n return (\r\n <FormConfigContext.Provider value={config}>\r\n {children}\r\n </FormConfigContext.Provider>\r\n );\r\n};\r\n","'use client';\r\n\r\nimport { useContext, useMemo } from 'react';\r\nimport { FormConfigContext } from '../context/form-context';\r\nimport type { UseFormOptions, FormAdapter, AdapterOptions, Field, FormSettings } from '../types';\r\n\r\n/**\r\n * Extract default values from field definitions.\r\n */\r\nfunction extractDefaultsFromFields(fields?: Field[]): Record<string, unknown> | undefined {\r\n if (!fields || fields.length === 0) return undefined;\r\n\r\n const defaults: Record<string, unknown> = {};\r\n let hasDefaults = false;\r\n\r\n for (const field of fields) {\r\n if ('name' in field && 'defaultValue' in field && field.defaultValue !== undefined) {\r\n defaults[field.name] = field.defaultValue;\r\n hasDefaults = true;\r\n }\r\n // Handle nested fields (group, array)\r\n if ('fields' in field && Array.isArray(field.fields)) {\r\n const nestedDefaults = extractDefaultsFromFields(field.fields);\r\n if (nestedDefaults) {\r\n Object.assign(defaults, nestedDefaults);\r\n hasDefaults = true;\r\n }\r\n }\r\n }\r\n\r\n return hasDefaults ? defaults : undefined;\r\n}\r\n\r\n/**\r\n * Apply FormSettings to a FormAdapter.\r\n * Wraps handleSubmit to implement settings like submitOnlyWhenDirty.\r\n */\r\nfunction applySettings<TData>(\r\n form: FormAdapter<TData>,\r\n settings?: FormSettings\r\n): FormAdapter<TData> {\r\n if (!settings) return form;\r\n\r\n // Wrap handleSubmit if submitOnlyWhenDirty is enabled\r\n if (settings.submitOnlyWhenDirty) {\r\n const originalHandleSubmit = form.handleSubmit;\r\n\r\n form.handleSubmit = (e?: React.FormEvent) => {\r\n // Check if form is dirty before submitting\r\n if (!form.formState.isDirty) {\r\n e?.preventDefault?.();\r\n return;\r\n }\r\n return originalHandleSubmit(e);\r\n };\r\n }\r\n\r\n // Note: autoFocus is a hint for the renderer component\r\n // We attach it to the form for the renderer to read\r\n if (settings) {\r\n form.settings = settings;\r\n }\r\n\r\n return form;\r\n}\r\n\r\n/**\r\n * Create a form instance with the specified options.\r\n * Uses adapter and resolver from FormProvider context unless overridden.\r\n * \r\n * @example\r\n * const loginSchema = createSchema([\r\n * { type: 'email', name: 'email', required: true },\r\n * { type: 'password', name: 'password', required: true },\r\n * ]);\r\n * \r\n * const form = useForm({\r\n * schema: loginSchema,\r\n * onSubmit: async (data) => {\r\n * await auth.login(data);\r\n * },\r\n * settings: {\r\n * submitOnlyWhenDirty: true,\r\n * },\r\n * });\r\n * \r\n * return (\r\n * <form onSubmit={form.handleSubmit}>\r\n * ...\r\n * </form>\r\n * );\r\n */\r\nexport function useForm<TData extends Record<string, unknown> = Record<string, unknown>>(\r\n options: UseFormOptions<TData>\r\n): FormAdapter<TData> {\r\n const globalConfig = useContext(FormConfigContext);\r\n\r\n // Validate required options\r\n if (!options.schema) {\r\n throw new Error(\r\n 'useForm: schema is required. ' +\r\n 'Use createSchema([...]) to create a schema from fields, or pass a Zod schema directly.'\r\n );\r\n }\r\n\r\n // Merge global config with per-form overrides\r\n const adapter = options.adapter ?? globalConfig?.adapter;\r\n const resolverFn = globalConfig?.resolver;\r\n const mode = options.mode ?? globalConfig?.mode ?? 'onChange';\r\n const reValidateMode = options.reValidateMode ?? globalConfig?.reValidateMode ?? 'onChange';\r\n\r\n // Validate adapter is available\r\n if (!adapter) {\r\n throw new Error(\r\n 'useForm: No adapter configured. ' +\r\n 'Either wrap your app in <FormProvider adapter={...}> or pass adapter in options.'\r\n );\r\n }\r\n\r\n // Create resolver from schema\r\n const resolver = resolverFn ? resolverFn(options.schema) : undefined;\r\n\r\n // Extract default values\r\n // Priority: explicit defaultValues > field defaultValues\r\n const schemaWithFields = options.schema as typeof options.schema & { fields?: Field[] };\r\n const fieldDefaults = useMemo(\r\n () => extractDefaultsFromFields(schemaWithFields.fields),\r\n [schemaWithFields.fields]\r\n );\r\n const defaultValues = options.defaultValues ?? fieldDefaults;\r\n\r\n // Call the adapter\r\n const form = adapter({\r\n defaultValues,\r\n resolver,\r\n mode,\r\n reValidateMode,\r\n onSubmit: options.onSubmit,\r\n } as AdapterOptions) as FormAdapter<TData>;\r\n\r\n // Apply settings (submitOnlyWhenDirty, etc.)\r\n return applySettings(form, options.settings);\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/types/adapter.ts","../src/context/form-context.ts","../src/providers/form-provider.tsx","../src/hooks/use-form.ts"],"sourcesContent":["import type { FormEvent } from 'react';\nimport type { FormSettings } from './form';\n\n// =============================================================================\n// FORM STATE\n// =============================================================================\n\n/**\n * Represents the current reactive state of a form.\n * Adapters must ensure this triggers re-renders when values change.\n * \n * This is the MINIMUM state required for BuzzForm to work.\n * Custom adapters must provide all these properties.\n */\nexport interface FormState {\n /** True while the form is being submitted */\n isSubmitting: boolean;\n\n /** True while validation is running (async validators) */\n isValidating: boolean;\n\n /** True if any field has been modified from its default value */\n isDirty: boolean;\n\n /** True if all validations pass (no errors) */\n isValid: boolean;\n\n /** True while async default values are being resolved */\n isLoading: boolean;\n\n /** \n * Field-level errors.\n * Key is the field path (e.g., \"email\", \"address.city\", \"items.0.name\")\n * Value is the error message(s)\n * \n * NOTE: Path format uses dot notation. Array indices use numbers (items.0.name).\n */\n errors: Record<string, string | string[] | undefined>;\n\n /** Map of field paths to whether they've been modified */\n dirtyFields: Record<string, boolean>;\n\n /** Map of field paths to whether they've been touched (blurred) */\n touchedFields: Record<string, boolean>;\n\n /** Number of times the form has been submitted */\n submitCount: number;\n}\n\n// =============================================================================\n// VALUE MANAGEMENT\n// =============================================================================\n\n/**\n * Options when programmatically setting a field value.\n */\nexport interface SetValueOptions {\n /** Run validation after setting the value (default: adapter-specific) */\n shouldValidate?: boolean;\n\n /** Mark the field as dirty (default: true) */\n shouldDirty?: boolean;\n\n /** Mark the field as touched (default: false) */\n shouldTouch?: boolean;\n}\n\n// =============================================================================\n// VALIDATION\n// =============================================================================\n\n/**\n * Represents a field-level error.\n */\nexport interface FieldError {\n /** Error type (e.g., 'required', 'pattern', 'server', 'custom') */\n type?: string;\n\n /** Human-readable error message */\n message: string;\n}\n\n/**\n * Result from a validation resolver.\n */\nexport interface ResolverResult<TData> {\n /** Parsed/transformed values (if validation passes) */\n values?: TData;\n\n /** Field errors (if validation fails) */\n errors?: Record<string, FieldError>;\n}\n\n/**\n * A validation resolver function.\n * Adapters use this to validate form values against a schema.\n * \n * @example\n * // Zod resolver\n * const zodResolver = (schema) => async (values) => {\n * const result = schema.safeParse(values);\n * if (result.success) return { values: result.data };\n * return { errors: mapZodErrors(result.error) };\n * };\n */\nexport type Resolver<TData> = (\n values: TData\n) => Promise<ResolverResult<TData>> | ResolverResult<TData>;\n\n// =============================================================================\n// ARRAY HELPERS\n// =============================================================================\n\n/**\n * Helper methods for manipulating array fields.\n * All methods operate on a field path (e.g., \"items\", \"users.0.tags\").\n * \n * This is REQUIRED for ArrayField to work. If your custom adapter doesn't\n * support arrays, you can implement these as no-ops or throw errors.\n */\nexport interface ArrayHelpers {\n /**\n * Get array items with stable IDs for React keys.\n * @param path - Field path to the array\n * @returns Array of items, each with an `id` property for React keys\n */\n fields: <T = unknown>(path: string) => Array<T & { id: string }>;\n\n /**\n * Add an item to the end of the array.\n */\n append: (path: string, value: unknown) => void;\n\n /**\n * Add an item to the beginning of the array.\n */\n prepend: (path: string, value: unknown) => void;\n\n /**\n * Insert an item at a specific index.\n */\n insert: (path: string, index: number, value: unknown) => void;\n\n /**\n * Remove an item at a specific index.\n */\n remove: (path: string, index: number) => void;\n\n /**\n * Move an item from one index to another.\n * Used for drag-and-drop reordering.\n */\n move: (path: string, from: number, to: number) => void;\n\n /**\n * Swap two items by their indices.\n */\n swap: (path: string, indexA: number, indexB: number) => void;\n\n /**\n * Replace the entire array with new values.\n */\n replace: (path: string, values: unknown[]) => void;\n\n /**\n * Update an item at a specific index.\n */\n update: (path: string, index: number, value: unknown) => void;\n}\n\n// =============================================================================\n// ADAPTER OPTIONS\n// =============================================================================\n\n/**\n * Base options passed to any adapter when creating a form instance.\n * Adapters can extend this with library-specific options.\n * \n * @typeParam TData - The shape of form data\n */\nexport interface AdapterOptions<TData = Record<string, unknown>> {\n /**\n * Initial values for the form.\n * Can be:\n * - A static object\n * - A sync function returning values\n * - An async function returning values (form shows loading state)\n */\n defaultValues?: TData | (() => TData) | (() => Promise<TData>);\n\n /**\n * Controlled values - when provided, form becomes controlled.\n * Changes to this prop will update form values.\n */\n values?: TData;\n\n /**\n * Validation resolver.\n * Called to validate form values before submission and optionally on change/blur.\n */\n resolver?: Resolver<TData>;\n\n /**\n * When to run validation.\n * - 'onChange': Validate on every value change\n * - 'onBlur': Validate when fields lose focus\n * - 'onSubmit': Validate only on submit\n * - 'all': Validate on all events\n * \n * NOTE: Not all adapters support all modes. Check adapter documentation.\n */\n mode?: 'onChange' | 'onBlur' | 'onSubmit' | 'all';\n\n /**\n * When to re-validate after initial error.\n * NOTE: This is optional. Some form libraries don't have this concept.\n */\n reValidateMode?: 'onChange' | 'onBlur' | 'onSubmit';\n\n /**\n * Callback when form is submitted (after validation passes).\n */\n onSubmit?: (values: TData) => Promise<void> | void;\n}\n\n// =============================================================================\n// FORM ADAPTER INTERFACE\n// =============================================================================\n\n/**\n * The contract any form adapter must fulfill.\n * \n * ## Required vs Optional\n * \n * **Required methods** are the building blocks that BuzzForm needs to function.\n * If any are missing, forms will break.\n * \n * **Optional methods** (marked with `?`) provide enhanced functionality but\n * are not required. BuzzForm will gracefully degrade without them.\n * \n * ## Creating a Custom Adapter\n * \n * To create a custom adapter (e.g., for useActionState, Formik, or vanilla React):\n * \n * 1. Implement all required properties and methods\n * 2. Optionally implement enhanced features\n * 3. Return the adapter from a hook (factory function)\n * \n * @example\n * // Minimal custom adapter skeleton\n * function useMyAdapter<T>(options: AdapterOptions<T>): FormAdapter<T> {\n * const [values, setValues] = useState(options.defaultValues ?? {});\n * const [errors, setErrors] = useState({});\n * const [isSubmitting, setIsSubmitting] = useState(false);\n * \n * return {\n * control: null, // Your state/context\n * get formState() { return { ... } },\n * handleSubmit: async (e) => { ... },\n * getValues: () => values,\n * setValue: (name, value) => { ... },\n * reset: (vals) => setValues(vals ?? {}),\n * watch: (name) => name ? values[name] : values,\n * validate: async () => true,\n * setError: (name, error) => { ... },\n * clearErrors: () => setErrors({}),\n * array: createArrayHelpers(...),\n * };\n * }\n * \n * @typeParam TData - The shape of form data\n */\nexport interface FormAdapter<TData = Record<string, unknown>> {\n // =========================================================================\n // CORE PROPERTIES (Required)\n // =========================================================================\n\n /**\n * Form-level behavior settings.\n * Set by useForm after applying FormSettings.\n */\n settings?: FormSettings;\n\n /**\n * The underlying form library's control/instance.\n * \n * This is passed to field components that need direct access to the form\n * library (e.g., for React Hook Form's Controller).\n * \n * For custom adapters, this can be:\n * - Your state object\n * - A context value\n * - null (if not needed)\n */\n control: unknown;\n\n /**\n * Current form state.\n * MUST be implemented as a getter to ensure reactivity.\n * \n * @example\n * get formState() {\n * return {\n * isSubmitting: this._isSubmitting,\n * isValidating: false,\n * isDirty: Object.keys(this._touched).length > 0,\n * isValid: Object.keys(this._errors).length === 0,\n * isLoading: false,\n * errors: this._errors,\n * dirtyFields: this._dirty,\n * touchedFields: this._touched,\n * submitCount: this._submitCount,\n * };\n * }\n */\n formState: FormState;\n\n /**\n * Submit handler to attach to a form element.\n * Should prevent default, run validation, and call onSubmit if valid.\n * \n * @example\n * <form onSubmit={adapter.handleSubmit}>\n */\n handleSubmit: (e?: FormEvent) => Promise<void> | void;\n\n // =========================================================================\n // VALUE MANAGEMENT (Required)\n // =========================================================================\n\n /**\n * Get all current form values.\n */\n getValues: () => TData;\n\n /**\n * Set a single field's value.\n * \n * @param name - Field path (e.g., \"email\", \"address.city\", \"items.0.name\")\n * @param value - New value\n * @param options - Additional options\n */\n setValue: (\n name: string,\n value: unknown,\n options?: SetValueOptions\n ) => void;\n\n /**\n * Reset form to default values or provided values.\n * \n * @param values - Optional new values to reset to\n */\n reset: (values?: Partial<TData>) => void;\n\n /**\n * Watch one or more field values reactively.\n * Returns current value(s) and causes re-render when they change.\n * \n * @param name - Field path to watch, or undefined for all values\n * @returns Current value(s)\n */\n watch: <T = unknown>(name?: string) => T;\n\n // =========================================================================\n // VALIDATION (Required)\n // =========================================================================\n\n /**\n * Manually trigger validation.\n * \n * @param name - Field(s) to validate, or undefined for entire form\n * @returns True if validation passes\n */\n validate: (name?: string | string[]) => Promise<boolean>;\n\n /**\n * Set a field error programmatically.\n * Useful for server-side validation errors.\n * \n * @param name - Field path\n * @param error - Error details\n */\n setError: (name: string, error: FieldError) => void;\n\n /**\n * Clear validation errors.\n * \n * @param name - Field(s) to clear, or undefined for all errors\n */\n clearErrors: (name?: string | string[]) => void;\n\n // =========================================================================\n // ARRAYS (Required for ArrayField)\n // =========================================================================\n\n /**\n * Helpers for manipulating array fields.\n * Required if you use ArrayField component.\n */\n array: ArrayHelpers;\n\n // =========================================================================\n // OPTIONAL ENHANCED FEATURES\n // These provide better UX but are not required for basic functionality.\n // =========================================================================\n\n /**\n * Handle field blur event.\n * Triggers validation if mode is 'onBlur'.\n * \n * If not provided, BuzzForm will not trigger blur-based validation.\n * \n * @param name - Field path\n */\n onBlur?: (name: string) => void;\n\n /**\n * Get the state of a specific field.\n * More efficient than accessing the entire formState for single fields.\n * \n * @param name - Field path\n */\n getFieldState?: (name: string) => {\n isDirty: boolean;\n isTouched: boolean;\n invalid: boolean;\n error?: string;\n };\n\n /**\n * Programmatically focus a field.\n * Useful for accessibility and after adding array items.\n * \n * @param name - Field path\n * @param options - Focus options\n */\n setFocus?: (name: string, options?: { shouldSelect?: boolean }) => void;\n\n /**\n * Unregister a field from the form.\n * Called when conditional fields are hidden.\n * \n * If not provided, hidden fields will retain their values.\n * \n * @param name - Field path(s) to unregister\n */\n unregister?: (name: string | string[]) => void;\n}\n\n// =============================================================================\n// ADAPTER FACTORY TYPE\n// =============================================================================\n\n/**\n * Type for an adapter factory function (hook).\n * \n * @example\n * // Using the adapter\n * function MyApp() {\n * return (\n * <FormProvider adapter={useRhfAdapter}>\n * <MyForm />\n * </FormProvider>\n * );\n * }\n */\nexport type AdapterFactory<TData = Record<string, unknown>> = (\n options: AdapterOptions<TData>\n) => FormAdapter<TData>;\n\n// =============================================================================\n// VALIDATION HELPERS FOR CUSTOM ADAPTERS\n// =============================================================================\n\n/**\n * Validates that a FormAdapter has all required methods.\n * Call this in development to catch missing implementations early.\n * \n * @param adapter - The adapter to validate\n * @param adapterName - Name for error messages\n * @throws Error if required methods are missing\n */\nexport function validateAdapter(adapter: FormAdapter, adapterName = 'adapter'): void {\n const required = [\n 'control',\n 'formState',\n 'handleSubmit',\n 'getValues',\n 'setValue',\n 'reset',\n 'watch',\n 'validate',\n 'setError',\n 'clearErrors',\n 'array',\n ] as const;\n\n for (const key of required) {\n if (adapter[key] === undefined) {\n throw new Error(\n `Invalid FormAdapter: \"${adapterName}\" is missing required property \"${key}\". ` +\n `See FormAdapter interface for implementation requirements.`\n );\n }\n }\n\n // Validate array helpers\n const arrayMethods = [\n 'fields', 'append', 'prepend', 'insert',\n 'remove', 'move', 'swap', 'replace', 'update'\n ] as const;\n\n for (const method of arrayMethods) {\n if (typeof adapter.array[method] !== 'function') {\n throw new Error(\n `Invalid FormAdapter: \"${adapterName}.array.${method}\" must be a function.`\n );\n }\n }\n}\n","'use client';\n\nimport { createContext } from 'react';\nimport type { FormConfig } from '../types';\n\n/**\n * Context for global form configuration.\n * Set via FormProvider, consumed by useForm.\n */\nexport const FormConfigContext = createContext<FormConfig | null>(null);\n","\"use client\";\n\nimport React from \"react\";\nimport { FormConfigContext } from \"../context/form-context\";\nimport type { FormConfig } from \"../types\";\n\n/**\n * Provider for global form configuration.\n * Set the adapter, resolver, and default mode for all forms in the app.\n *\n * @example\n * import { FormProvider } from '@buildnbuzz/buzzform';\n * import { useRhfAdapter } from '@buildnbuzz/buzzform/rhf';\n * import { zodResolver } from '@buildnbuzz/buzzform/resolvers/zod';\n *\n * <FormProvider\n * adapter={useRhfAdapter}\n * resolver={zodResolver}\n * mode=\"onBlur\"\n * >\n * <App />\n * </FormProvider>\n */\nexport const FormProvider: React.FC<\n FormConfig & { children: React.ReactNode }\n> = ({ children, ...config }) => {\n return (\n <FormConfigContext.Provider value={config}>\n {children}\n </FormConfigContext.Provider>\n );\n};\n","'use client';\n\nimport { useContext, useMemo } from 'react';\nimport { FormConfigContext } from '../context/form-context';\nimport type { UseFormOptions, FormAdapter, AdapterOptions, Field, FormSettings } from '../types';\n\n/**\n * Extract default values from field definitions.\n */\nfunction extractDefaultsFromFields(fields?: Field[]): Record<string, unknown> | undefined {\n if (!fields || fields.length === 0) return undefined;\n\n const defaults: Record<string, unknown> = {};\n let hasDefaults = false;\n\n for (const field of fields) {\n if ('name' in field && 'defaultValue' in field && field.defaultValue !== undefined) {\n defaults[field.name] = field.defaultValue;\n hasDefaults = true;\n }\n // Handle nested fields (group, array)\n if ('fields' in field && Array.isArray(field.fields)) {\n const nestedDefaults = extractDefaultsFromFields(field.fields);\n if (nestedDefaults) {\n Object.assign(defaults, nestedDefaults);\n hasDefaults = true;\n }\n }\n }\n\n return hasDefaults ? defaults : undefined;\n}\n\n/**\n * Apply FormSettings to a FormAdapter.\n * Wraps handleSubmit to implement settings like submitOnlyWhenDirty.\n */\nfunction applySettings<TData>(\n form: FormAdapter<TData>,\n settings?: FormSettings\n): FormAdapter<TData> {\n if (!settings) return form;\n\n // Wrap handleSubmit if submitOnlyWhenDirty is enabled\n if (settings.submitOnlyWhenDirty) {\n const originalHandleSubmit = form.handleSubmit;\n\n form.handleSubmit = (e?: React.FormEvent) => {\n // Check if form is dirty before submitting\n if (!form.formState.isDirty) {\n e?.preventDefault?.();\n return;\n }\n return originalHandleSubmit(e);\n };\n }\n\n // Note: autoFocus is a hint for the renderer component\n // We attach it to the form for the renderer to read\n if (settings) {\n form.settings = settings;\n }\n\n return form;\n}\n\n/**\n * Create a form instance with the specified options.\n * Uses adapter and resolver from FormProvider context unless overridden.\n * \n * @example\n * const loginSchema = createSchema([\n * { type: 'email', name: 'email', required: true },\n * { type: 'password', name: 'password', required: true },\n * ]);\n * \n * const form = useForm({\n * schema: loginSchema,\n * onSubmit: async (data) => {\n * await auth.login(data);\n * },\n * settings: {\n * submitOnlyWhenDirty: true,\n * },\n * });\n * \n * return (\n * <form onSubmit={form.handleSubmit}>\n * ...\n * </form>\n * );\n */\nexport function useForm<TData extends Record<string, unknown> = Record<string, unknown>>(\n options: UseFormOptions<TData>\n): FormAdapter<TData> {\n const globalConfig = useContext(FormConfigContext);\n\n // Validate required options\n if (!options.schema) {\n throw new Error(\n 'useForm: schema is required. ' +\n 'Use createSchema([...]) to create a schema from fields, or pass a Zod schema directly.'\n );\n }\n\n // Merge global config with per-form overrides\n const adapter = options.adapter ?? globalConfig?.adapter;\n const resolverFn = globalConfig?.resolver;\n const mode = options.mode ?? globalConfig?.mode ?? 'onChange';\n const reValidateMode = options.reValidateMode ?? globalConfig?.reValidateMode ?? 'onChange';\n\n // Validate adapter is available\n if (!adapter) {\n throw new Error(\n 'useForm: No adapter configured. ' +\n 'Either wrap your app in <FormProvider adapter={...}> or pass adapter in options.'\n );\n }\n\n // Create resolver from schema\n const resolver = resolverFn ? resolverFn(options.schema) : undefined;\n\n // Extract default values\n // Priority: explicit defaultValues > field defaultValues\n const schemaWithFields = options.schema as typeof options.schema & { fields?: Field[] };\n const fieldDefaults = useMemo(\n () => extractDefaultsFromFields(schemaWithFields.fields),\n [schemaWithFields.fields]\n );\n const defaultValues = options.defaultValues ?? fieldDefaults;\n\n // Call the adapter\n const form = adapter({\n defaultValues,\n resolver,\n mode,\n reValidateMode,\n onSubmit: options.onSubmit,\n } as AdapterOptions) as FormAdapter<TData>;\n\n // Apply settings (submitOnlyWhenDirty, etc.)\n return applySettings(form, options.settings);\n}\n"],"mappings":"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","names":[]}
package/dist/rhf.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { FieldValues, Control } from 'react-hook-form';
2
2
  export { UseFormReturn as RhfForm } from 'react-hook-form';
3
- import { f as AdapterOptions, b as FormAdapter } from './adapter-BT9v2OVg.mjs';
3
+ import { f as AdapterOptions, b as FormAdapter } from './adapter-nQW28cyO.mjs';
4
4
  import 'react';
5
5
  import 'zod';
6
6
 
package/dist/rhf.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { FieldValues, Control } from 'react-hook-form';
2
2
  export { UseFormReturn as RhfForm } from 'react-hook-form';
3
- import { f as AdapterOptions, b as FormAdapter } from './adapter-BT9v2OVg.js';
3
+ import { f as AdapterOptions, b as FormAdapter } from './adapter-nQW28cyO.js';
4
4
  import 'react';
5
5
  import 'zod';
6
6
 
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';\r\n\r\nimport { useRef, useEffect } from 'react';\r\nimport { useForm, useWatch } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n PathValue,\r\n DefaultValues,\r\n FieldErrors,\r\n Resolver as RhfResolver\r\n} from 'react-hook-form';\r\nimport type {\r\n FormAdapter,\r\n AdapterOptions,\r\n FormState,\r\n FieldError,\r\n SetValueOptions,\r\n} from '../types';\r\nimport { createArrayHelpers } from '../utils';\r\nimport { getNestedValue, flattenNestedObject } from '../lib';\r\n\r\n// =============================================================================\r\n// RHF ADAPTER OPTIONS\r\n// =============================================================================\r\n\r\n/**\r\n * Options specific to the React Hook Form adapter.\r\n * Extends base AdapterOptions with RHF-specific features.\r\n */\r\nexport interface RhfAdapterOptions<TData extends FieldValues = FieldValues>\r\n extends AdapterOptions<TData> {\r\n /**\r\n * React Hook Form's reValidateMode.\r\n * When to re-validate after initial validation.\r\n * @default 'onChange'\r\n */\r\n reValidateMode?: 'onChange' | 'onBlur' | 'onSubmit';\r\n\r\n /**\r\n * Validation strategy before submit.\r\n * - 'firstError': Return first error only (faster)\r\n * - 'all': Return all errors (better UX for complex forms)\r\n * @default 'firstError'\r\n */\r\n criteriaMode?: 'firstError' | 'all';\r\n\r\n /**\r\n * Delay validation by specified ms (debounce).\r\n * Useful for expensive async validation.\r\n */\r\n delayError?: number;\r\n\r\n /**\r\n * Focus on the first field with an error after submit.\r\n * @default true\r\n */\r\n shouldFocusError?: boolean;\r\n}\r\n\r\n// =============================================================================\r\n// RHF ADAPTER\r\n// =============================================================================\r\n\r\n/**\r\n * React Hook Form adapter implementing the FormAdapter interface.\r\n * \r\n * This is the default adapter for BuzzForm. It provides full implementation\r\n * of all required and optional FormAdapter methods using React Hook Form.\r\n * \r\n * @example\r\n * // In FormProvider\r\n * import { useRhf } from '@buildnbuzz/buzzform/rhf';\r\n * \r\n * <FormProvider adapter={useRhf}>\r\n * <App />\r\n * </FormProvider>\r\n * \r\n * @example\r\n * // Direct usage\r\n * const form = useRhf({\r\n * defaultValues: { email: '', password: '' },\r\n * resolver: zodResolver(schema),\r\n * onSubmit: async (data) => {\r\n * await loginUser(data);\r\n * },\r\n * });\r\n */\r\nexport function useRhf<TData extends FieldValues = FieldValues>(\r\n options: RhfAdapterOptions<TData>\r\n): FormAdapter<TData> {\r\n const {\r\n defaultValues,\r\n values,\r\n resolver,\r\n mode = 'onChange',\r\n reValidateMode = 'onChange',\r\n criteriaMode,\r\n delayError,\r\n shouldFocusError = true,\r\n onSubmit,\r\n } = options;\r\n\r\n // -------------------------------------------------------------------------\r\n // Initialize React Hook Form\r\n // -------------------------------------------------------------------------\r\n\r\n const form = useForm<TData>({\r\n defaultValues: defaultValues as DefaultValues<TData>,\r\n values: values,\r\n resolver: resolver as unknown as RhfResolver<TData>,\r\n mode,\r\n reValidateMode,\r\n criteriaMode,\r\n delayError,\r\n shouldFocusError,\r\n });\r\n\r\n // -------------------------------------------------------------------------\r\n // Handle controlled values updates\r\n // -------------------------------------------------------------------------\r\n\r\n const prevValuesRef = useRef(values);\r\n\r\n useEffect(() => {\r\n if (values && JSON.stringify(values) !== JSON.stringify(prevValuesRef.current)) {\r\n prevValuesRef.current = values;\r\n }\r\n }, [values]);\r\n\r\n // -------------------------------------------------------------------------\r\n // Build submit handler\r\n // -------------------------------------------------------------------------\r\n\r\n const handleSubmit = form.handleSubmit(async (data) => {\r\n if (onSubmit) {\r\n await onSubmit(data as TData);\r\n }\r\n });\r\n\r\n // -------------------------------------------------------------------------\r\n // Build the adapter API\r\n // -------------------------------------------------------------------------\r\n\r\n const api: FormAdapter<TData> = {\r\n // ---------------------------------------------------------------------\r\n // CORE PROPERTIES\r\n // ---------------------------------------------------------------------\r\n\r\n control: form.control,\r\n\r\n get formState(): FormState {\r\n const state = form.formState;\r\n return {\r\n isSubmitting: state.isSubmitting,\r\n isValidating: state.isValidating,\r\n isDirty: state.isDirty,\r\n isValid: state.isValid,\r\n isLoading: state.isLoading,\r\n errors: normalizeErrors(state.errors),\r\n dirtyFields: flattenNestedObject(state.dirtyFields),\r\n touchedFields: flattenNestedObject(state.touchedFields),\r\n submitCount: state.submitCount,\r\n };\r\n },\r\n\r\n handleSubmit,\r\n\r\n // ---------------------------------------------------------------------\r\n // VALUE MANAGEMENT\r\n // ---------------------------------------------------------------------\r\n\r\n getValues: () => form.getValues(),\r\n\r\n setValue: (name: string, value: unknown, opts?: SetValueOptions) => {\r\n form.setValue(name as Path<TData>, value as PathValue<TData, Path<TData>>, {\r\n shouldValidate: opts?.shouldValidate,\r\n shouldDirty: opts?.shouldDirty ?? true,\r\n shouldTouch: opts?.shouldTouch,\r\n });\r\n },\r\n\r\n reset: (vals) => form.reset(vals as DefaultValues<TData>),\r\n\r\n watch: <T = unknown>(name?: string): T => {\r\n return form.watch(name as Path<TData>) as T;\r\n },\r\n\r\n // ---------------------------------------------------------------------\r\n // VALIDATION\r\n // ---------------------------------------------------------------------\r\n\r\n validate: async (name) => {\r\n if (name) {\r\n const names = Array.isArray(name) ? name : [name];\r\n return form.trigger(names as Path<TData>[]);\r\n }\r\n return form.trigger();\r\n },\r\n\r\n setError: (name: string, error: FieldError) => {\r\n form.setError(name as Path<TData>, {\r\n type: error.type || 'manual',\r\n message: error.message,\r\n });\r\n },\r\n\r\n clearErrors: (name) => {\r\n if (name) {\r\n const names = Array.isArray(name) ? name : [name];\r\n names.forEach(n => form.clearErrors(n as Path<TData>));\r\n } else {\r\n form.clearErrors();\r\n }\r\n },\r\n\r\n // ---------------------------------------------------------------------\r\n // ARRAYS\r\n // ---------------------------------------------------------------------\r\n\r\n array: createArrayHelpers(\r\n (path) => form.getValues(path as Path<TData>) as unknown[],\r\n (path, value) => form.setValue(\r\n path as Path<TData>,\r\n value as PathValue<TData, Path<TData>>,\r\n { shouldDirty: true }\r\n )\r\n ),\r\n\r\n // ---------------------------------------------------------------------\r\n // OPTIONAL ENHANCED FEATURES\r\n // ---------------------------------------------------------------------\r\n\r\n onBlur: (name: string) => {\r\n // Mark field as touched\r\n const hasError = !!getNestedValue(form.formState.errors, name);\r\n\r\n // Trigger validation based on mode\r\n if (mode === 'onBlur' || mode === 'all') {\r\n form.trigger(name as Path<TData>);\r\n } else if (hasError && reValidateMode === 'onBlur') {\r\n // Re-validate if field has error and reValidateMode is onBlur\r\n form.trigger(name as Path<TData>);\r\n }\r\n },\r\n\r\n getFieldState: (name: string) => {\r\n const state = form.getFieldState(name as Path<TData>, form.formState);\r\n return {\r\n isDirty: state.isDirty,\r\n isTouched: state.isTouched,\r\n invalid: state.invalid,\r\n error: state.error?.message,\r\n };\r\n },\r\n\r\n setFocus: (name: string, options?: { shouldSelect?: boolean }) => {\r\n form.setFocus(name as Path<TData>, options);\r\n },\r\n\r\n unregister: (name: string | string[]) => {\r\n const names = Array.isArray(name) ? name : [name];\r\n names.forEach(n => form.unregister(n as Path<TData>));\r\n },\r\n };\r\n\r\n return api;\r\n}\r\n\r\n// =============================================================================\r\n// HELPER: Watch hook for external use\r\n// =============================================================================\r\n\r\n/**\r\n * Hook to watch specific field values reactively.\r\n * Use this when you need to react to field changes outside of components.\r\n * \r\n * @param control - The control object from useRhf (form.control)\r\n * @param name - Field path(s) to watch\r\n */\r\nexport function useRhfWatch<TData extends FieldValues, TValue = unknown>(\r\n control: Control<TData>,\r\n name: string | string[]\r\n): TValue {\r\n if (Array.isArray(name)) {\r\n return useWatch({ control, name: name as unknown as Path<TData>[] }) as TValue;\r\n }\r\n return useWatch({ control, name: name as Path<TData> }) as TValue;\r\n}\r\n\r\n// =============================================================================\r\n// UTILITIES\r\n// =============================================================================\r\n\r\n/**\r\n * Normalize RHF's nested error structure to flat string map.\r\n */\r\nfunction normalizeErrors<TData extends FieldValues>(\r\n errors: FieldErrors<TData>\r\n): Record<string, string | string[] | undefined> {\r\n const result: Record<string, string | string[] | undefined> = {};\r\n\r\n function traverse(obj: Record<string, unknown>, prefix = '') {\r\n for (const key in obj) {\r\n const path = prefix ? `${prefix}.${key}` : key;\r\n const value = obj[key];\r\n\r\n if (isRecord(value) && 'message' in value && typeof value.message === 'string') {\r\n // Leaf error\r\n result[path] = value.message;\r\n } else if (\r\n isRecord(value) &&\r\n 'root' in value &&\r\n isRecord(value.root) &&\r\n 'message' in value.root &&\r\n typeof value.root.message === 'string'\r\n ) {\r\n // Array root error\r\n result[path] = value.root.message;\r\n } else if (isRecord(value)) {\r\n // Nested object, traverse deeper\r\n traverse(value, path);\r\n }\r\n }\r\n }\r\n\r\n traverse(errors as unknown as Record<string, unknown>);\r\n return result;\r\n}\r\n\r\n\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null;\r\n}\r\n\r\n// =============================================================================\r\n// RE-EXPORTS FOR CONVENIENCE\r\n// =============================================================================\r\n\r\nexport type { UseFormReturn as RhfForm } from 'react-hook-form';","import { nanoid } from 'nanoid';\r\nimport type { ArrayHelpers } from '../types';\r\n\r\n/**\r\n * Creates a standardized set of array field manipulation methods.\r\n * Abstracts the difference between getting/setting values in different form libraries.\r\n * \r\n * @param getArray - Function to get current array value at a path\r\n * @param setArray - Function to set array value at a path\r\n */\r\nexport function createArrayHelpers(\r\n getArray: (path: string) => unknown[],\r\n setArray: (path: string, value: unknown[]) => void\r\n): ArrayHelpers {\r\n return {\r\n fields: <T = unknown>(path: string): Array<T & { id: string }> => {\r\n const arr = getArray(path);\r\n if (!Array.isArray(arr)) return [];\r\n return arr.map((item, index) => ({\r\n id: (item as Record<string, unknown>)?.id as string || `${path}-${index}`,\r\n ...item as T,\r\n }));\r\n },\r\n\r\n append: (path: string, value: unknown) => {\r\n const current = getArray(path) || [];\r\n const itemWithId = ensureId(value);\r\n setArray(path, [...current, itemWithId]);\r\n },\r\n\r\n prepend: (path: string, value: unknown) => {\r\n const current = getArray(path) || [];\r\n const itemWithId = ensureId(value);\r\n setArray(path, [itemWithId, ...current]);\r\n },\r\n\r\n insert: (path: string, index: number, value: unknown) => {\r\n const current = [...(getArray(path) || [])];\r\n const itemWithId = ensureId(value);\r\n current.splice(index, 0, itemWithId);\r\n setArray(path, current);\r\n },\r\n\r\n remove: (path: string, index: number) => {\r\n const current = [...(getArray(path) || [])];\r\n current.splice(index, 1);\r\n setArray(path, current);\r\n },\r\n\r\n move: (path: string, from: number, to: number) => {\r\n const current = [...(getArray(path) || [])];\r\n const [item] = current.splice(from, 1);\r\n current.splice(to, 0, item);\r\n setArray(path, current);\r\n },\r\n\r\n swap: (path: string, indexA: number, indexB: number) => {\r\n const current = [...(getArray(path) || [])];\r\n const temp = current[indexA];\r\n current[indexA] = current[indexB];\r\n current[indexB] = temp;\r\n setArray(path, current);\r\n },\r\n\r\n replace: (path: string, values: unknown[]) => {\r\n const itemsWithIds = values.map(ensureId);\r\n setArray(path, itemsWithIds);\r\n },\r\n\r\n update: (path: string, index: number, value: unknown) => {\r\n const current = [...(getArray(path) || [])];\r\n // Preserve existing ID if present\r\n const existingId = (current[index] as Record<string, unknown>)?.id;\r\n current[index] = {\r\n ...(typeof value === 'object' && value !== null ? value : {}),\r\n id: existingId || nanoid(),\r\n };\r\n setArray(path, current);\r\n },\r\n };\r\n}\r\n\r\n/**\r\n * Ensures an item has a unique ID for React keys.\r\n */\r\nfunction ensureId(value: unknown): unknown {\r\n if (typeof value === 'object' && value !== null) {\r\n const obj = value as Record<string, unknown>;\r\n if (!obj.id) {\r\n return { ...obj, id: nanoid() };\r\n }\r\n return obj;\r\n }\r\n return { value, id: nanoid() };\r\n}\r\n","// =============================================================================\r\n// COMMON UTILITIES\r\n// These are used by both the core package and registry field components.\r\n// =============================================================================\r\n\r\n/**\r\n * Generate a unique field ID from the field path.\r\n * Converts dot notation to dashes and prefixes with 'field-'.\r\n * Used for accessibility (htmlFor, id attributes).\r\n * \r\n * @example\r\n * generateFieldId('user.profile.email') => 'field-user-profile-email'\r\n * generateFieldId('items[0].name') => 'field-items-0-name'\r\n */\r\nexport function generateFieldId(path: string): string {\r\n return `field-${path.replace(/\\./g, \"-\").replace(/\\[/g, \"-\").replace(/\\]/g, \"\")}`;\r\n}\r\n\r\n/**\r\n * Safely retrieve a nested value from an object using a dot-notation path.\r\n * \r\n * @example\r\n * getNestedValue({ user: { name: 'John' } }, 'user.name') => 'John'\r\n * getNestedValue({ items: [{ id: 1 }] }, 'items.0.id') => 1\r\n */\r\nexport function getNestedValue(obj: unknown, path: string): unknown {\r\n if (!obj || !path) return undefined;\r\n return path.split(\".\").reduce<unknown>((acc: unknown, key: string) => {\r\n if (acc && typeof acc === \"object\" && acc !== null) {\r\n return (acc as Record<string, unknown>)[key];\r\n }\r\n return undefined;\r\n }, obj);\r\n}\r\n\r\n/**\r\n * Set a nested value in an object using a dot-notation path.\r\n * Creates intermediate objects/arrays as needed.\r\n * \r\n * @example\r\n * setNestedValue({}, 'user.name', 'John') => { user: { name: 'John' } }\r\n */\r\nexport function setNestedValue<T extends Record<string, unknown>>(\r\n obj: T,\r\n path: string,\r\n value: unknown\r\n): T {\r\n const keys = path.split(\".\");\r\n const result = { ...obj } as Record<string, unknown>;\r\n let current = result;\r\n\r\n for (let i = 0; i < keys.length - 1; i++) {\r\n const key = keys[i];\r\n if (!(key in current) || typeof current[key] !== \"object\") {\r\n // Check if next key is numeric (array index)\r\n const nextKey = keys[i + 1];\r\n current[key] = /^\\d+$/.test(nextKey) ? [] : {};\r\n } else {\r\n current[key] = Array.isArray(current[key])\r\n ? [...(current[key] as unknown[])]\r\n : { ...(current[key] as Record<string, unknown>) };\r\n }\r\n current = current[key] as Record<string, unknown>;\r\n }\r\n\r\n current[keys[keys.length - 1]] = value;\r\n return result as T;\r\n}\r\n\r\n/**\r\n * Format bytes into a human-readable string.\r\n * \r\n * @example\r\n * formatBytes(1024) => '1 KB'\r\n * formatBytes(1234567) => '1.18 MB'\r\n */\r\nexport function formatBytes(bytes: number, decimals = 2): string {\r\n if (bytes === 0) return '0 Bytes';\r\n\r\n const k = 1024;\r\n const dm = decimals < 0 ? 0 : decimals;\r\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];\r\n\r\n const i = Math.floor(Math.log(bytes) / Math.log(k));\r\n\r\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;\r\n}\r\n\r\n/**\r\n * Flatten a nested object to dot-notation paths.\r\n * Useful for converting form library state (like dirtyFields, touchedFields)\r\n * to the flat format expected by FormState.\r\n * \r\n * @example\r\n * flattenNestedObject({ user: { name: true, email: true } })\r\n * // => { 'user.name': true, 'user.email': true }\r\n * \r\n * flattenNestedObject({ items: { 0: { title: true } } })\r\n * // => { 'items.0.title': true }\r\n */\r\nexport function flattenNestedObject(\r\n obj: Record<string, unknown>,\r\n prefix = ''\r\n): Record<string, boolean> {\r\n const result: Record<string, boolean> = {};\r\n\r\n for (const key in obj) {\r\n const path = prefix ? `${prefix}.${key}` : key;\r\n const value = obj[key];\r\n\r\n if (typeof value === 'boolean') {\r\n result[path] = value;\r\n } else if (value !== null && typeof value === 'object' && !Array.isArray(value)) {\r\n Object.assign(result, flattenNestedObject(value as Record<string, unknown>, path));\r\n }\r\n }\r\n\r\n return result;\r\n}\r\n\r\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 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"]}
package/dist/rhf.mjs CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createArrayHelpers,
4
4
  flattenNestedObject,
5
5
  getNestedValue
6
- } from "./chunk-DDDGBPVU.mjs";
6
+ } from "./chunk-IMJ5FRK5.mjs";
7
7
 
8
8
  // src/adapters/rhf.ts
9
9
  import { useRef, useEffect } from "react";
package/dist/rhf.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapters/rhf.ts"],"sourcesContent":["'use client';\r\n\r\nimport { useRef, useEffect } from 'react';\r\nimport { useForm, useWatch } from 'react-hook-form';\r\nimport type {\r\n Control,\r\n FieldValues,\r\n Path,\r\n PathValue,\r\n DefaultValues,\r\n FieldErrors,\r\n Resolver as RhfResolver\r\n} from 'react-hook-form';\r\nimport type {\r\n FormAdapter,\r\n AdapterOptions,\r\n FormState,\r\n FieldError,\r\n SetValueOptions,\r\n} from '../types';\r\nimport { createArrayHelpers } from '../utils';\r\nimport { getNestedValue, flattenNestedObject } from '../lib';\r\n\r\n// =============================================================================\r\n// RHF ADAPTER OPTIONS\r\n// =============================================================================\r\n\r\n/**\r\n * Options specific to the React Hook Form adapter.\r\n * Extends base AdapterOptions with RHF-specific features.\r\n */\r\nexport interface RhfAdapterOptions<TData extends FieldValues = FieldValues>\r\n extends AdapterOptions<TData> {\r\n /**\r\n * React Hook Form's reValidateMode.\r\n * When to re-validate after initial validation.\r\n * @default 'onChange'\r\n */\r\n reValidateMode?: 'onChange' | 'onBlur' | 'onSubmit';\r\n\r\n /**\r\n * Validation strategy before submit.\r\n * - 'firstError': Return first error only (faster)\r\n * - 'all': Return all errors (better UX for complex forms)\r\n * @default 'firstError'\r\n */\r\n criteriaMode?: 'firstError' | 'all';\r\n\r\n /**\r\n * Delay validation by specified ms (debounce).\r\n * Useful for expensive async validation.\r\n */\r\n delayError?: number;\r\n\r\n /**\r\n * Focus on the first field with an error after submit.\r\n * @default true\r\n */\r\n shouldFocusError?: boolean;\r\n}\r\n\r\n// =============================================================================\r\n// RHF ADAPTER\r\n// =============================================================================\r\n\r\n/**\r\n * React Hook Form adapter implementing the FormAdapter interface.\r\n * \r\n * This is the default adapter for BuzzForm. It provides full implementation\r\n * of all required and optional FormAdapter methods using React Hook Form.\r\n * \r\n * @example\r\n * // In FormProvider\r\n * import { useRhf } from '@buildnbuzz/buzzform/rhf';\r\n * \r\n * <FormProvider adapter={useRhf}>\r\n * <App />\r\n * </FormProvider>\r\n * \r\n * @example\r\n * // Direct usage\r\n * const form = useRhf({\r\n * defaultValues: { email: '', password: '' },\r\n * resolver: zodResolver(schema),\r\n * onSubmit: async (data) => {\r\n * await loginUser(data);\r\n * },\r\n * });\r\n */\r\nexport function useRhf<TData extends FieldValues = FieldValues>(\r\n options: RhfAdapterOptions<TData>\r\n): FormAdapter<TData> {\r\n const {\r\n defaultValues,\r\n values,\r\n resolver,\r\n mode = 'onChange',\r\n reValidateMode = 'onChange',\r\n criteriaMode,\r\n delayError,\r\n shouldFocusError = true,\r\n onSubmit,\r\n } = options;\r\n\r\n // -------------------------------------------------------------------------\r\n // Initialize React Hook Form\r\n // -------------------------------------------------------------------------\r\n\r\n const form = useForm<TData>({\r\n defaultValues: defaultValues as DefaultValues<TData>,\r\n values: values,\r\n resolver: resolver as unknown as RhfResolver<TData>,\r\n mode,\r\n reValidateMode,\r\n criteriaMode,\r\n delayError,\r\n shouldFocusError,\r\n });\r\n\r\n // -------------------------------------------------------------------------\r\n // Handle controlled values updates\r\n // -------------------------------------------------------------------------\r\n\r\n const prevValuesRef = useRef(values);\r\n\r\n useEffect(() => {\r\n if (values && JSON.stringify(values) !== JSON.stringify(prevValuesRef.current)) {\r\n prevValuesRef.current = values;\r\n }\r\n }, [values]);\r\n\r\n // -------------------------------------------------------------------------\r\n // Build submit handler\r\n // -------------------------------------------------------------------------\r\n\r\n const handleSubmit = form.handleSubmit(async (data) => {\r\n if (onSubmit) {\r\n await onSubmit(data as TData);\r\n }\r\n });\r\n\r\n // -------------------------------------------------------------------------\r\n // Build the adapter API\r\n // -------------------------------------------------------------------------\r\n\r\n const api: FormAdapter<TData> = {\r\n // ---------------------------------------------------------------------\r\n // CORE PROPERTIES\r\n // ---------------------------------------------------------------------\r\n\r\n control: form.control,\r\n\r\n get formState(): FormState {\r\n const state = form.formState;\r\n return {\r\n isSubmitting: state.isSubmitting,\r\n isValidating: state.isValidating,\r\n isDirty: state.isDirty,\r\n isValid: state.isValid,\r\n isLoading: state.isLoading,\r\n errors: normalizeErrors(state.errors),\r\n dirtyFields: flattenNestedObject(state.dirtyFields),\r\n touchedFields: flattenNestedObject(state.touchedFields),\r\n submitCount: state.submitCount,\r\n };\r\n },\r\n\r\n handleSubmit,\r\n\r\n // ---------------------------------------------------------------------\r\n // VALUE MANAGEMENT\r\n // ---------------------------------------------------------------------\r\n\r\n getValues: () => form.getValues(),\r\n\r\n setValue: (name: string, value: unknown, opts?: SetValueOptions) => {\r\n form.setValue(name as Path<TData>, value as PathValue<TData, Path<TData>>, {\r\n shouldValidate: opts?.shouldValidate,\r\n shouldDirty: opts?.shouldDirty ?? true,\r\n shouldTouch: opts?.shouldTouch,\r\n });\r\n },\r\n\r\n reset: (vals) => form.reset(vals as DefaultValues<TData>),\r\n\r\n watch: <T = unknown>(name?: string): T => {\r\n return form.watch(name as Path<TData>) as T;\r\n },\r\n\r\n // ---------------------------------------------------------------------\r\n // VALIDATION\r\n // ---------------------------------------------------------------------\r\n\r\n validate: async (name) => {\r\n if (name) {\r\n const names = Array.isArray(name) ? name : [name];\r\n return form.trigger(names as Path<TData>[]);\r\n }\r\n return form.trigger();\r\n },\r\n\r\n setError: (name: string, error: FieldError) => {\r\n form.setError(name as Path<TData>, {\r\n type: error.type || 'manual',\r\n message: error.message,\r\n });\r\n },\r\n\r\n clearErrors: (name) => {\r\n if (name) {\r\n const names = Array.isArray(name) ? name : [name];\r\n names.forEach(n => form.clearErrors(n as Path<TData>));\r\n } else {\r\n form.clearErrors();\r\n }\r\n },\r\n\r\n // ---------------------------------------------------------------------\r\n // ARRAYS\r\n // ---------------------------------------------------------------------\r\n\r\n array: createArrayHelpers(\r\n (path) => form.getValues(path as Path<TData>) as unknown[],\r\n (path, value) => form.setValue(\r\n path as Path<TData>,\r\n value as PathValue<TData, Path<TData>>,\r\n { shouldDirty: true }\r\n )\r\n ),\r\n\r\n // ---------------------------------------------------------------------\r\n // OPTIONAL ENHANCED FEATURES\r\n // ---------------------------------------------------------------------\r\n\r\n onBlur: (name: string) => {\r\n // Mark field as touched\r\n const hasError = !!getNestedValue(form.formState.errors, name);\r\n\r\n // Trigger validation based on mode\r\n if (mode === 'onBlur' || mode === 'all') {\r\n form.trigger(name as Path<TData>);\r\n } else if (hasError && reValidateMode === 'onBlur') {\r\n // Re-validate if field has error and reValidateMode is onBlur\r\n form.trigger(name as Path<TData>);\r\n }\r\n },\r\n\r\n getFieldState: (name: string) => {\r\n const state = form.getFieldState(name as Path<TData>, form.formState);\r\n return {\r\n isDirty: state.isDirty,\r\n isTouched: state.isTouched,\r\n invalid: state.invalid,\r\n error: state.error?.message,\r\n };\r\n },\r\n\r\n setFocus: (name: string, options?: { shouldSelect?: boolean }) => {\r\n form.setFocus(name as Path<TData>, options);\r\n },\r\n\r\n unregister: (name: string | string[]) => {\r\n const names = Array.isArray(name) ? name : [name];\r\n names.forEach(n => form.unregister(n as Path<TData>));\r\n },\r\n };\r\n\r\n return api;\r\n}\r\n\r\n// =============================================================================\r\n// HELPER: Watch hook for external use\r\n// =============================================================================\r\n\r\n/**\r\n * Hook to watch specific field values reactively.\r\n * Use this when you need to react to field changes outside of components.\r\n * \r\n * @param control - The control object from useRhf (form.control)\r\n * @param name - Field path(s) to watch\r\n */\r\nexport function useRhfWatch<TData extends FieldValues, TValue = unknown>(\r\n control: Control<TData>,\r\n name: string | string[]\r\n): TValue {\r\n if (Array.isArray(name)) {\r\n return useWatch({ control, name: name as unknown as Path<TData>[] }) as TValue;\r\n }\r\n return useWatch({ control, name: name as Path<TData> }) as TValue;\r\n}\r\n\r\n// =============================================================================\r\n// UTILITIES\r\n// =============================================================================\r\n\r\n/**\r\n * Normalize RHF's nested error structure to flat string map.\r\n */\r\nfunction normalizeErrors<TData extends FieldValues>(\r\n errors: FieldErrors<TData>\r\n): Record<string, string | string[] | undefined> {\r\n const result: Record<string, string | string[] | undefined> = {};\r\n\r\n function traverse(obj: Record<string, unknown>, prefix = '') {\r\n for (const key in obj) {\r\n const path = prefix ? `${prefix}.${key}` : key;\r\n const value = obj[key];\r\n\r\n if (isRecord(value) && 'message' in value && typeof value.message === 'string') {\r\n // Leaf error\r\n result[path] = value.message;\r\n } else if (\r\n isRecord(value) &&\r\n 'root' in value &&\r\n isRecord(value.root) &&\r\n 'message' in value.root &&\r\n typeof value.root.message === 'string'\r\n ) {\r\n // Array root error\r\n result[path] = value.root.message;\r\n } else if (isRecord(value)) {\r\n // Nested object, traverse deeper\r\n traverse(value, path);\r\n }\r\n }\r\n }\r\n\r\n traverse(errors as unknown as Record<string, unknown>);\r\n return result;\r\n}\r\n\r\n\r\n\r\nfunction isRecord(value: unknown): value is Record<string, unknown> {\r\n return typeof value === 'object' && value !== null;\r\n}\r\n\r\n// =============================================================================\r\n// RE-EXPORTS FOR CONVENIENCE\r\n// =============================================================================\r\n\r\nexport type { UseFormReturn as RhfForm } from 'react-hook-form';"],"mappings":"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","names":["options"]}
1
+ {"version":3,"sources":["../src/adapters/rhf.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';"],"mappings":"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","names":["options"]}
package/dist/schema.d.mts CHANGED
@@ -1,60 +1,33 @@
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-BT9v2OVg.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-BT9v2OVg.mjs';
3
- export { F as FieldToZod, a as FieldsToShape, I as InferSchema, S as SchemaBuilder, b as SchemaBuilderMap, d as applyCustomValidation, h as coerceToDate, g as coerceToNumber, l as createArrayHelpers, c as createSchema, e as extractValidationConfig, f as fieldsToZodSchema, p as formatBytes, n as generateFieldId, o as getNestedValue, i as getPatternErrorMessage, j as isFileLike, k as isFileTypeAccepted, m as makeOptional, s as setNestedValue } from './utils-BgwyUFGB.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, 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';
4
4
  import { z } from 'zod';
5
5
  import 'react';
6
6
 
7
- /**
8
- * Creates Zod schema for text fields.
9
- */
10
7
  declare function createTextFieldSchema(field: TextField | TextareaField): z.ZodTypeAny;
11
- /**
12
- * Creates Zod schema for email fields.
13
- */
14
8
  declare function createEmailFieldSchema(field: EmailField): z.ZodTypeAny;
15
- /**
16
- * Creates Zod schema for password fields.
17
- */
18
9
  declare function createPasswordFieldSchema(field: PasswordField): z.ZodTypeAny;
19
10
 
20
11
  /**
21
12
  * Creates Zod schema for number fields.
13
+ * Note: Custom validation (field.validate) is handled at root schema level.
22
14
  */
23
15
  declare function createNumberFieldSchema(field: NumberField): z.ZodTypeAny;
24
16
 
25
17
  /**
26
18
  * Creates Zod schema for date and datetime fields.
19
+ * Note: Custom validation (field.validate) is handled at root schema level.
27
20
  */
28
21
  declare function createDateFieldSchema(field: DateField | DatetimeField): z.ZodTypeAny;
29
22
 
30
- /**
31
- * Creates Zod schema for select fields.
32
- */
33
23
  declare function createSelectFieldSchema(field: SelectField): z.ZodTypeAny;
34
- /**
35
- * Creates Zod schema for radio fields.
36
- */
37
24
  declare function createRadioFieldSchema(field: RadioField): z.ZodTypeAny;
38
25
 
39
- /**
40
- * Creates Zod schema for checkbox fields.
41
- */
42
26
  declare function createCheckboxFieldSchema(field: CheckboxField): z.ZodTypeAny;
43
- /**
44
- * Creates Zod schema for switch fields.
45
- */
46
27
  declare function createSwitchFieldSchema(field: SwitchField): z.ZodTypeAny;
47
28
 
48
- /**
49
- * Creates Zod schema for upload fields.
50
- * Supports both single file and hasMany (array) modes.
51
- */
52
29
  declare function createUploadFieldSchema(field: UploadField): z.ZodTypeAny;
53
30
 
54
- /**
55
- * Creates Zod schema for tags fields.
56
- * Tags are always arrays of strings.
57
- */
58
31
  declare function createTagsFieldSchema(field: TagsField): z.ZodTypeAny;
59
32
 
60
33
  type SchemaGenerator = (fields: readonly Field[]) => z.ZodObject<z.ZodRawShape>;
package/dist/schema.d.ts CHANGED
@@ -1,60 +1,33 @@
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-BT9v2OVg.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-BT9v2OVg.js';
3
- export { F as FieldToZod, a as FieldsToShape, I as InferSchema, S as SchemaBuilder, b as SchemaBuilderMap, d as applyCustomValidation, h as coerceToDate, g as coerceToNumber, l as createArrayHelpers, c as createSchema, e as extractValidationConfig, f as fieldsToZodSchema, p as formatBytes, n as generateFieldId, o as getNestedValue, i as getPatternErrorMessage, j as isFileLike, k as isFileTypeAccepted, m as makeOptional, s as setNestedValue } from './utils-DVLpbOoW.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, 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';
4
4
  import { z } from 'zod';
5
5
  import 'react';
6
6
 
7
- /**
8
- * Creates Zod schema for text fields.
9
- */
10
7
  declare function createTextFieldSchema(field: TextField | TextareaField): z.ZodTypeAny;
11
- /**
12
- * Creates Zod schema for email fields.
13
- */
14
8
  declare function createEmailFieldSchema(field: EmailField): z.ZodTypeAny;
15
- /**
16
- * Creates Zod schema for password fields.
17
- */
18
9
  declare function createPasswordFieldSchema(field: PasswordField): z.ZodTypeAny;
19
10
 
20
11
  /**
21
12
  * Creates Zod schema for number fields.
13
+ * Note: Custom validation (field.validate) is handled at root schema level.
22
14
  */
23
15
  declare function createNumberFieldSchema(field: NumberField): z.ZodTypeAny;
24
16
 
25
17
  /**
26
18
  * Creates Zod schema for date and datetime fields.
19
+ * Note: Custom validation (field.validate) is handled at root schema level.
27
20
  */
28
21
  declare function createDateFieldSchema(field: DateField | DatetimeField): z.ZodTypeAny;
29
22
 
30
- /**
31
- * Creates Zod schema for select fields.
32
- */
33
23
  declare function createSelectFieldSchema(field: SelectField): z.ZodTypeAny;
34
- /**
35
- * Creates Zod schema for radio fields.
36
- */
37
24
  declare function createRadioFieldSchema(field: RadioField): z.ZodTypeAny;
38
25
 
39
- /**
40
- * Creates Zod schema for checkbox fields.
41
- */
42
26
  declare function createCheckboxFieldSchema(field: CheckboxField): z.ZodTypeAny;
43
- /**
44
- * Creates Zod schema for switch fields.
45
- */
46
27
  declare function createSwitchFieldSchema(field: SwitchField): z.ZodTypeAny;
47
28
 
48
- /**
49
- * Creates Zod schema for upload fields.
50
- * Supports both single file and hasMany (array) modes.
51
- */
52
29
  declare function createUploadFieldSchema(field: UploadField): z.ZodTypeAny;
53
30
 
54
- /**
55
- * Creates Zod schema for tags fields.
56
- * Tags are always arrays of strings.
57
- */
58
31
  declare function createTagsFieldSchema(field: TagsField): z.ZodTypeAny;
59
32
 
60
33
  type SchemaGenerator = (fields: readonly Field[]) => z.ZodObject<z.ZodRawShape>;