@buildnbuzz/buzzform 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/rhf.d.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { FieldValues, Control } from 'react-hook-form';
2
+ export { UseFormReturn as RhfForm } from 'react-hook-form';
3
+ import { f as AdapterOptions, b as FormAdapter } from './adapter-BT9v2OVg.js';
4
+ import 'react';
5
+ import 'zod';
6
+
7
+ /**
8
+ * Options specific to the React Hook Form adapter.
9
+ * Extends base AdapterOptions with RHF-specific features.
10
+ */
11
+ interface RhfAdapterOptions<TData extends FieldValues = FieldValues> extends AdapterOptions<TData> {
12
+ /**
13
+ * React Hook Form's reValidateMode.
14
+ * When to re-validate after initial validation.
15
+ * @default 'onChange'
16
+ */
17
+ reValidateMode?: 'onChange' | 'onBlur' | 'onSubmit';
18
+ /**
19
+ * Validation strategy before submit.
20
+ * - 'firstError': Return first error only (faster)
21
+ * - 'all': Return all errors (better UX for complex forms)
22
+ * @default 'firstError'
23
+ */
24
+ criteriaMode?: 'firstError' | 'all';
25
+ /**
26
+ * Delay validation by specified ms (debounce).
27
+ * Useful for expensive async validation.
28
+ */
29
+ delayError?: number;
30
+ /**
31
+ * Focus on the first field with an error after submit.
32
+ * @default true
33
+ */
34
+ shouldFocusError?: boolean;
35
+ }
36
+ /**
37
+ * React Hook Form adapter implementing the FormAdapter interface.
38
+ *
39
+ * This is the default adapter for BuzzForm. It provides full implementation
40
+ * of all required and optional FormAdapter methods using React Hook Form.
41
+ *
42
+ * @example
43
+ * // In FormProvider
44
+ * import { useRhf } from '@buildnbuzz/buzzform/rhf';
45
+ *
46
+ * <FormProvider adapter={useRhf}>
47
+ * <App />
48
+ * </FormProvider>
49
+ *
50
+ * @example
51
+ * // Direct usage
52
+ * const form = useRhf({
53
+ * defaultValues: { email: '', password: '' },
54
+ * resolver: zodResolver(schema),
55
+ * onSubmit: async (data) => {
56
+ * await loginUser(data);
57
+ * },
58
+ * });
59
+ */
60
+ declare function useRhf<TData extends FieldValues = FieldValues>(options: RhfAdapterOptions<TData>): FormAdapter<TData>;
61
+ /**
62
+ * Hook to watch specific field values reactively.
63
+ * Use this when you need to react to field changes outside of components.
64
+ *
65
+ * @param control - The control object from useRhf (form.control)
66
+ * @param name - Field path(s) to watch
67
+ */
68
+ declare function useRhfWatch<TData extends FieldValues, TValue = unknown>(control: Control<TData>, name: string | string[]): TValue;
69
+
70
+ export { type RhfAdapterOptions, useRhf, useRhfWatch };
package/dist/rhf.js ADDED
@@ -0,0 +1,293 @@
1
+ "use strict";
2
+ "use client";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/adapters/rhf.ts
22
+ var rhf_exports = {};
23
+ __export(rhf_exports, {
24
+ useRhf: () => useRhf,
25
+ useRhfWatch: () => useRhfWatch
26
+ });
27
+ module.exports = __toCommonJS(rhf_exports);
28
+ var import_react = require("react");
29
+ var import_react_hook_form = require("react-hook-form");
30
+
31
+ // src/utils/array.ts
32
+ var import_nanoid = require("nanoid");
33
+ function createArrayHelpers(getArray, setArray) {
34
+ return {
35
+ fields: (path) => {
36
+ const arr = getArray(path);
37
+ if (!Array.isArray(arr)) return [];
38
+ return arr.map((item, index) => ({
39
+ id: item?.id || `${path}-${index}`,
40
+ ...item
41
+ }));
42
+ },
43
+ append: (path, value) => {
44
+ const current = getArray(path) || [];
45
+ const itemWithId = ensureId(value);
46
+ setArray(path, [...current, itemWithId]);
47
+ },
48
+ prepend: (path, value) => {
49
+ const current = getArray(path) || [];
50
+ const itemWithId = ensureId(value);
51
+ setArray(path, [itemWithId, ...current]);
52
+ },
53
+ insert: (path, index, value) => {
54
+ const current = [...getArray(path) || []];
55
+ const itemWithId = ensureId(value);
56
+ current.splice(index, 0, itemWithId);
57
+ setArray(path, current);
58
+ },
59
+ remove: (path, index) => {
60
+ const current = [...getArray(path) || []];
61
+ current.splice(index, 1);
62
+ setArray(path, current);
63
+ },
64
+ move: (path, from, to) => {
65
+ const current = [...getArray(path) || []];
66
+ const [item] = current.splice(from, 1);
67
+ current.splice(to, 0, item);
68
+ setArray(path, current);
69
+ },
70
+ swap: (path, indexA, indexB) => {
71
+ const current = [...getArray(path) || []];
72
+ const temp = current[indexA];
73
+ current[indexA] = current[indexB];
74
+ current[indexB] = temp;
75
+ setArray(path, current);
76
+ },
77
+ replace: (path, values) => {
78
+ const itemsWithIds = values.map(ensureId);
79
+ setArray(path, itemsWithIds);
80
+ },
81
+ update: (path, index, value) => {
82
+ const current = [...getArray(path) || []];
83
+ const existingId = current[index]?.id;
84
+ current[index] = {
85
+ ...typeof value === "object" && value !== null ? value : {},
86
+ id: existingId || (0, import_nanoid.nanoid)()
87
+ };
88
+ setArray(path, current);
89
+ }
90
+ };
91
+ }
92
+ function ensureId(value) {
93
+ if (typeof value === "object" && value !== null) {
94
+ const obj = value;
95
+ if (!obj.id) {
96
+ return { ...obj, id: (0, import_nanoid.nanoid)() };
97
+ }
98
+ return obj;
99
+ }
100
+ return { value, id: (0, import_nanoid.nanoid)() };
101
+ }
102
+
103
+ // src/lib/utils.ts
104
+ function getNestedValue(obj, path) {
105
+ if (!obj || !path) return void 0;
106
+ return path.split(".").reduce((acc, key) => {
107
+ if (acc && typeof acc === "object" && acc !== null) {
108
+ return acc[key];
109
+ }
110
+ return void 0;
111
+ }, obj);
112
+ }
113
+ function flattenNestedObject(obj, prefix = "") {
114
+ const result = {};
115
+ for (const key in obj) {
116
+ const path = prefix ? `${prefix}.${key}` : key;
117
+ const value = obj[key];
118
+ if (typeof value === "boolean") {
119
+ result[path] = value;
120
+ } else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
121
+ Object.assign(result, flattenNestedObject(value, path));
122
+ }
123
+ }
124
+ return result;
125
+ }
126
+
127
+ // src/adapters/rhf.ts
128
+ function useRhf(options) {
129
+ const {
130
+ defaultValues,
131
+ values,
132
+ resolver,
133
+ mode = "onChange",
134
+ reValidateMode = "onChange",
135
+ criteriaMode,
136
+ delayError,
137
+ shouldFocusError = true,
138
+ onSubmit
139
+ } = options;
140
+ const form = (0, import_react_hook_form.useForm)({
141
+ defaultValues,
142
+ values,
143
+ resolver,
144
+ mode,
145
+ reValidateMode,
146
+ criteriaMode,
147
+ delayError,
148
+ shouldFocusError
149
+ });
150
+ const prevValuesRef = (0, import_react.useRef)(values);
151
+ (0, import_react.useEffect)(() => {
152
+ if (values && JSON.stringify(values) !== JSON.stringify(prevValuesRef.current)) {
153
+ prevValuesRef.current = values;
154
+ }
155
+ }, [values]);
156
+ const handleSubmit = form.handleSubmit(async (data) => {
157
+ if (onSubmit) {
158
+ await onSubmit(data);
159
+ }
160
+ });
161
+ const api = {
162
+ // ---------------------------------------------------------------------
163
+ // CORE PROPERTIES
164
+ // ---------------------------------------------------------------------
165
+ control: form.control,
166
+ get formState() {
167
+ const state = form.formState;
168
+ return {
169
+ isSubmitting: state.isSubmitting,
170
+ isValidating: state.isValidating,
171
+ isDirty: state.isDirty,
172
+ isValid: state.isValid,
173
+ isLoading: state.isLoading,
174
+ errors: normalizeErrors(state.errors),
175
+ dirtyFields: flattenNestedObject(state.dirtyFields),
176
+ touchedFields: flattenNestedObject(state.touchedFields),
177
+ submitCount: state.submitCount
178
+ };
179
+ },
180
+ handleSubmit,
181
+ // ---------------------------------------------------------------------
182
+ // VALUE MANAGEMENT
183
+ // ---------------------------------------------------------------------
184
+ getValues: () => form.getValues(),
185
+ setValue: (name, value, opts) => {
186
+ form.setValue(name, value, {
187
+ shouldValidate: opts?.shouldValidate,
188
+ shouldDirty: opts?.shouldDirty ?? true,
189
+ shouldTouch: opts?.shouldTouch
190
+ });
191
+ },
192
+ reset: (vals) => form.reset(vals),
193
+ watch: (name) => {
194
+ return form.watch(name);
195
+ },
196
+ // ---------------------------------------------------------------------
197
+ // VALIDATION
198
+ // ---------------------------------------------------------------------
199
+ validate: async (name) => {
200
+ if (name) {
201
+ const names = Array.isArray(name) ? name : [name];
202
+ return form.trigger(names);
203
+ }
204
+ return form.trigger();
205
+ },
206
+ setError: (name, error) => {
207
+ form.setError(name, {
208
+ type: error.type || "manual",
209
+ message: error.message
210
+ });
211
+ },
212
+ clearErrors: (name) => {
213
+ if (name) {
214
+ const names = Array.isArray(name) ? name : [name];
215
+ names.forEach((n) => form.clearErrors(n));
216
+ } else {
217
+ form.clearErrors();
218
+ }
219
+ },
220
+ // ---------------------------------------------------------------------
221
+ // ARRAYS
222
+ // ---------------------------------------------------------------------
223
+ array: createArrayHelpers(
224
+ (path) => form.getValues(path),
225
+ (path, value) => form.setValue(
226
+ path,
227
+ value,
228
+ { shouldDirty: true }
229
+ )
230
+ ),
231
+ // ---------------------------------------------------------------------
232
+ // OPTIONAL ENHANCED FEATURES
233
+ // ---------------------------------------------------------------------
234
+ onBlur: (name) => {
235
+ const hasError = !!getNestedValue(form.formState.errors, name);
236
+ if (mode === "onBlur" || mode === "all") {
237
+ form.trigger(name);
238
+ } else if (hasError && reValidateMode === "onBlur") {
239
+ form.trigger(name);
240
+ }
241
+ },
242
+ getFieldState: (name) => {
243
+ const state = form.getFieldState(name, form.formState);
244
+ return {
245
+ isDirty: state.isDirty,
246
+ isTouched: state.isTouched,
247
+ invalid: state.invalid,
248
+ error: state.error?.message
249
+ };
250
+ },
251
+ setFocus: (name, options2) => {
252
+ form.setFocus(name, options2);
253
+ },
254
+ unregister: (name) => {
255
+ const names = Array.isArray(name) ? name : [name];
256
+ names.forEach((n) => form.unregister(n));
257
+ }
258
+ };
259
+ return api;
260
+ }
261
+ function useRhfWatch(control, name) {
262
+ if (Array.isArray(name)) {
263
+ return (0, import_react_hook_form.useWatch)({ control, name });
264
+ }
265
+ return (0, import_react_hook_form.useWatch)({ control, name });
266
+ }
267
+ function normalizeErrors(errors) {
268
+ const result = {};
269
+ function traverse(obj, prefix = "") {
270
+ for (const key in obj) {
271
+ const path = prefix ? `${prefix}.${key}` : key;
272
+ const value = obj[key];
273
+ if (isRecord(value) && "message" in value && typeof value.message === "string") {
274
+ result[path] = value.message;
275
+ } else if (isRecord(value) && "root" in value && isRecord(value.root) && "message" in value.root && typeof value.root.message === "string") {
276
+ result[path] = value.root.message;
277
+ } else if (isRecord(value)) {
278
+ traverse(value, path);
279
+ }
280
+ }
281
+ }
282
+ traverse(errors);
283
+ return result;
284
+ }
285
+ function isRecord(value) {
286
+ return typeof value === "object" && value !== null;
287
+ }
288
+ // Annotate the CommonJS export names for ESM import in node:
289
+ 0 && (module.exports = {
290
+ useRhf,
291
+ useRhfWatch
292
+ });
293
+ //# sourceMappingURL=rhf.js.map
@@ -0,0 +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"]}
package/dist/rhf.mjs ADDED
@@ -0,0 +1,175 @@
1
+ "use client";
2
+ import {
3
+ createArrayHelpers,
4
+ flattenNestedObject,
5
+ getNestedValue
6
+ } from "./chunk-DDDGBPVU.mjs";
7
+
8
+ // src/adapters/rhf.ts
9
+ import { useRef, useEffect } from "react";
10
+ import { useForm, useWatch } from "react-hook-form";
11
+ function useRhf(options) {
12
+ const {
13
+ defaultValues,
14
+ values,
15
+ resolver,
16
+ mode = "onChange",
17
+ reValidateMode = "onChange",
18
+ criteriaMode,
19
+ delayError,
20
+ shouldFocusError = true,
21
+ onSubmit
22
+ } = options;
23
+ const form = useForm({
24
+ defaultValues,
25
+ values,
26
+ resolver,
27
+ mode,
28
+ reValidateMode,
29
+ criteriaMode,
30
+ delayError,
31
+ shouldFocusError
32
+ });
33
+ const prevValuesRef = useRef(values);
34
+ useEffect(() => {
35
+ if (values && JSON.stringify(values) !== JSON.stringify(prevValuesRef.current)) {
36
+ prevValuesRef.current = values;
37
+ }
38
+ }, [values]);
39
+ const handleSubmit = form.handleSubmit(async (data) => {
40
+ if (onSubmit) {
41
+ await onSubmit(data);
42
+ }
43
+ });
44
+ const api = {
45
+ // ---------------------------------------------------------------------
46
+ // CORE PROPERTIES
47
+ // ---------------------------------------------------------------------
48
+ control: form.control,
49
+ get formState() {
50
+ const state = form.formState;
51
+ return {
52
+ isSubmitting: state.isSubmitting,
53
+ isValidating: state.isValidating,
54
+ isDirty: state.isDirty,
55
+ isValid: state.isValid,
56
+ isLoading: state.isLoading,
57
+ errors: normalizeErrors(state.errors),
58
+ dirtyFields: flattenNestedObject(state.dirtyFields),
59
+ touchedFields: flattenNestedObject(state.touchedFields),
60
+ submitCount: state.submitCount
61
+ };
62
+ },
63
+ handleSubmit,
64
+ // ---------------------------------------------------------------------
65
+ // VALUE MANAGEMENT
66
+ // ---------------------------------------------------------------------
67
+ getValues: () => form.getValues(),
68
+ setValue: (name, value, opts) => {
69
+ form.setValue(name, value, {
70
+ shouldValidate: opts?.shouldValidate,
71
+ shouldDirty: opts?.shouldDirty ?? true,
72
+ shouldTouch: opts?.shouldTouch
73
+ });
74
+ },
75
+ reset: (vals) => form.reset(vals),
76
+ watch: (name) => {
77
+ return form.watch(name);
78
+ },
79
+ // ---------------------------------------------------------------------
80
+ // VALIDATION
81
+ // ---------------------------------------------------------------------
82
+ validate: async (name) => {
83
+ if (name) {
84
+ const names = Array.isArray(name) ? name : [name];
85
+ return form.trigger(names);
86
+ }
87
+ return form.trigger();
88
+ },
89
+ setError: (name, error) => {
90
+ form.setError(name, {
91
+ type: error.type || "manual",
92
+ message: error.message
93
+ });
94
+ },
95
+ clearErrors: (name) => {
96
+ if (name) {
97
+ const names = Array.isArray(name) ? name : [name];
98
+ names.forEach((n) => form.clearErrors(n));
99
+ } else {
100
+ form.clearErrors();
101
+ }
102
+ },
103
+ // ---------------------------------------------------------------------
104
+ // ARRAYS
105
+ // ---------------------------------------------------------------------
106
+ array: createArrayHelpers(
107
+ (path) => form.getValues(path),
108
+ (path, value) => form.setValue(
109
+ path,
110
+ value,
111
+ { shouldDirty: true }
112
+ )
113
+ ),
114
+ // ---------------------------------------------------------------------
115
+ // OPTIONAL ENHANCED FEATURES
116
+ // ---------------------------------------------------------------------
117
+ onBlur: (name) => {
118
+ const hasError = !!getNestedValue(form.formState.errors, name);
119
+ if (mode === "onBlur" || mode === "all") {
120
+ form.trigger(name);
121
+ } else if (hasError && reValidateMode === "onBlur") {
122
+ form.trigger(name);
123
+ }
124
+ },
125
+ getFieldState: (name) => {
126
+ const state = form.getFieldState(name, form.formState);
127
+ return {
128
+ isDirty: state.isDirty,
129
+ isTouched: state.isTouched,
130
+ invalid: state.invalid,
131
+ error: state.error?.message
132
+ };
133
+ },
134
+ setFocus: (name, options2) => {
135
+ form.setFocus(name, options2);
136
+ },
137
+ unregister: (name) => {
138
+ const names = Array.isArray(name) ? name : [name];
139
+ names.forEach((n) => form.unregister(n));
140
+ }
141
+ };
142
+ return api;
143
+ }
144
+ function useRhfWatch(control, name) {
145
+ if (Array.isArray(name)) {
146
+ return useWatch({ control, name });
147
+ }
148
+ return useWatch({ control, name });
149
+ }
150
+ function normalizeErrors(errors) {
151
+ const result = {};
152
+ function traverse(obj, prefix = "") {
153
+ for (const key in obj) {
154
+ const path = prefix ? `${prefix}.${key}` : key;
155
+ const value = obj[key];
156
+ if (isRecord(value) && "message" in value && typeof value.message === "string") {
157
+ result[path] = value.message;
158
+ } else if (isRecord(value) && "root" in value && isRecord(value.root) && "message" in value.root && typeof value.root.message === "string") {
159
+ result[path] = value.root.message;
160
+ } else if (isRecord(value)) {
161
+ traverse(value, path);
162
+ }
163
+ }
164
+ }
165
+ traverse(errors);
166
+ return result;
167
+ }
168
+ function isRecord(value) {
169
+ return typeof value === "object" && value !== null;
170
+ }
171
+ export {
172
+ useRhf,
173
+ useRhfWatch
174
+ };
175
+ //# sourceMappingURL=rhf.mjs.map
@@ -0,0 +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"]}