@zod-utils/react-hook-form 0.11.0 → 2.0.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/README.md CHANGED
@@ -63,6 +63,9 @@ npm install @zod-utils/react-hook-form zod react react-hook-form @hookform/resol
63
63
  ## Features
64
64
 
65
65
  - 🎣 **useZodForm** - Automatic type transformation for form inputs (nullable/undefined) while preserving Zod schema validation
66
+ - 📋 **FormSchemaProvider** - React Context for providing schema to form components
67
+ - ✅ **useIsRequiredField** - Hook to check if a field requires valid input
68
+ - 🔄 **Discriminated Union Support** - Full type-safe support for discriminated unions
66
69
  - 📦 **All core utilities** - Re-exports everything from `@zod-utils/core`
67
70
  - ⚛️ **React-optimized** - Built specifically for React applications
68
71
 
@@ -273,6 +276,153 @@ const form2 = useZodForm<
273
276
 
274
277
  ---
275
278
 
279
+ ## Form Schema Context
280
+
281
+ The Form Schema Context system allows you to provide Zod schema context to deeply nested form components without prop drilling.
282
+
283
+ ### `FormSchemaProvider`
284
+
285
+ Provides schema context to all child components. Use this to wrap your form.
286
+
287
+ ```tsx
288
+ import { FormSchemaProvider } from "@zod-utils/react-hook-form";
289
+ import { z } from "zod";
290
+
291
+ const schema = z.object({
292
+ username: z.string().min(3).max(20),
293
+ email: z.string().email(),
294
+ });
295
+
296
+ function MyForm() {
297
+ return (
298
+ <FormSchemaProvider schema={schema}>
299
+ <form>
300
+ <UsernameField />
301
+ <EmailField />
302
+ </form>
303
+ </FormSchemaProvider>
304
+ );
305
+ }
306
+ ```
307
+
308
+ #### With Discriminated Union
309
+
310
+ For discriminated unions, pass the discriminator to enable type-safe field access:
311
+
312
+ ```tsx
313
+ const schema = z.discriminatedUnion("mode", [
314
+ z.object({ mode: z.literal("create"), name: z.string().min(1) }),
315
+ z.object({ mode: z.literal("edit"), id: z.number() }),
316
+ ]);
317
+
318
+ function CreateModeForm() {
319
+ return (
320
+ <FormSchemaProvider
321
+ schema={schema}
322
+ discriminator={{ key: "mode", value: "create" }}
323
+ >
324
+ <NameField /> {/* Only fields from 'create' variant are available */}
325
+ </FormSchemaProvider>
326
+ );
327
+ }
328
+ ```
329
+
330
+ ### `useFormSchema()`
331
+
332
+ Access the schema context from child components:
333
+
334
+ ```tsx
335
+ import { useFormSchema } from "@zod-utils/react-hook-form";
336
+
337
+ function FieldComponent() {
338
+ const context = useFormSchema();
339
+ if (!context) return null;
340
+
341
+ const { schema, discriminator } = context;
342
+ // Use schema for field-level logic
343
+ }
344
+ ```
345
+
346
+ ### `useIsRequiredField({ schema, name, discriminator? })`
347
+
348
+ Hook to check if a field requires valid input (shows validation errors on submit).
349
+ The schema parameter is used for type inference only - the actual schema is retrieved from context.
350
+
351
+ ```tsx
352
+ import { useIsRequiredField } from "@zod-utils/react-hook-form";
353
+
354
+ function FormLabel({ name, schema }: { name: string; schema: z.ZodType }) {
355
+ const isRequired = useIsRequiredField({ schema, name });
356
+
357
+ return (
358
+ <label>
359
+ {name}
360
+ {isRequired && <span className="text-red-500">*</span>}
361
+ </label>
362
+ );
363
+ }
364
+ ```
365
+
366
+ ### `isRequiredField({ schema, name, discriminator? })`
367
+
368
+ Standalone function to check if a field requires valid input:
369
+
370
+ ```tsx
371
+ import { isRequiredField } from "@zod-utils/react-hook-form";
372
+ import { z } from "zod";
373
+
374
+ const schema = z.object({
375
+ username: z.string().min(1), // Required - min(1) rejects empty
376
+ email: z.string(), // Not required - accepts empty string
377
+ age: z.number(), // Required - numbers reject empty input
378
+ bio: z.string().optional(), // Not required - optional
379
+ });
380
+
381
+ isRequiredField({ schema, name: "username" }); // true
382
+ isRequiredField({ schema, name: "email" }); // false
383
+ isRequiredField({ schema, name: "age" }); // true
384
+ isRequiredField({ schema, name: "bio" }); // false
385
+ ```
386
+
387
+ ### `useExtractFieldFromSchema({ schema, name, discriminator? })`
388
+
389
+ Hook to extract a field's Zod schema from a parent schema. Memoized for performance.
390
+
391
+ ```tsx
392
+ import { useExtractFieldFromSchema } from "@zod-utils/react-hook-form";
393
+
394
+ function FieldInfo({ schema, name }: { schema: z.ZodType; name: string }) {
395
+ const fieldSchema = useExtractFieldFromSchema({ schema, name });
396
+
397
+ if (!fieldSchema) return null;
398
+
399
+ // Use fieldSchema for custom validation or field info
400
+ return <span>{fieldSchema._zod.def.typeName}</span>;
401
+ }
402
+ ```
403
+
404
+ ### `useFieldChecks({ schema, name, discriminator? })`
405
+
406
+ Hook to get validation checks from a field's Zod schema. Useful for displaying validation hints like max length or min/max values.
407
+
408
+ ```tsx
409
+ import { useFieldChecks } from "@zod-utils/react-hook-form";
410
+
411
+ function FieldHint({ schema, name }: { schema: z.ZodType; name: string }) {
412
+ const checks = useFieldChecks({ schema, name });
413
+
414
+ const maxLength = checks.find((c) => c.check === "max_length");
415
+ if (maxLength) {
416
+ return <span>Max {maxLength.maximum} characters</span>;
417
+ }
418
+ return null;
419
+ }
420
+ ```
421
+
422
+ **Supported check types:** `min_length`, `max_length`, `greater_than`, `less_than`, `string_format`, and more.
423
+
424
+ ---
425
+
276
426
  ## Core Utilities (Re-exported)
277
427
 
278
428
  All utilities from `@zod-utils/core` are re-exported for convenience:
@@ -285,11 +435,29 @@ import {
285
435
  getPrimitiveType,
286
436
  removeDefault,
287
437
  extractDefaultValue,
438
+ extendWithMeta,
439
+ extractFieldFromSchema,
440
+ getFieldChecks,
288
441
  type Simplify,
289
-
290
- // Type utilities (react-hook-form specific)
442
+ type ZodUnionCheck,
443
+
444
+ // Form schema context & hooks
445
+ FormSchemaContext,
446
+ FormSchemaProvider,
447
+ useFormSchema,
448
+ useIsRequiredField,
449
+ isRequiredField,
450
+ useExtractFieldFromSchema,
451
+ useFieldChecks,
452
+
453
+ // Type utilities
291
454
  type PartialWithNullableObjects,
292
455
  type PartialWithAllNullables,
456
+ type Discriminator,
457
+ type DiscriminatorKey,
458
+ type DiscriminatorValue,
459
+ type InferredFieldValues,
460
+ type ValidFieldPaths,
293
461
  } from "@zod-utils/react-hook-form";
294
462
  ```
295
463
 
package/dist/index.d.mts CHANGED
@@ -1,9 +1,253 @@
1
- import { Simplify } from '@zod-utils/core';
1
+ import { DiscriminatorKey, DiscriminatorValue, Discriminator, ValidPaths, ExtractZodByPath, ZodUnionCheck, Simplify } from '@zod-utils/core';
2
2
  export * from '@zod-utils/core';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { Context, ReactNode } from 'react';
5
+ import { z } from 'zod';
3
6
  import * as react_hook_form from 'react-hook-form';
4
- import { FieldValues, UseFormProps } from 'react-hook-form';
7
+ import { FieldValues, Path, UseFormProps } from 'react-hook-form';
5
8
  import { zodResolver } from '@hookform/resolvers/zod';
6
- import { z } from 'zod';
9
+
10
+ /**
11
+ * Type for the FormSchemaContext with full generic support.
12
+ * @internal
13
+ */
14
+ type FormSchemaContextType<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = Context<{
15
+ schema: TSchema;
16
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
17
+ } | null>;
18
+ /**
19
+ * Context value type for FormSchemaContext.
20
+ */
21
+ type FormSchemaContextValue<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = {
22
+ schema: TSchema;
23
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
24
+ } | null;
25
+ /**
26
+ * React Context for providing Zod schema to form components.
27
+ *
28
+ * Use with {@link FormSchemaProvider} to provide schema context, and
29
+ * {@link useFormSchema} to consume it in child components.
30
+ */
31
+ declare const FormSchemaContext: Context<{
32
+ schema: z.ZodType;
33
+ discriminator?: {
34
+ key: unknown;
35
+ value: unknown;
36
+ };
37
+ } | null>;
38
+ /**
39
+ * Hook to access the form schema from context.
40
+ *
41
+ * The optional `_params` argument is used for TypeScript type inference only.
42
+ * Pass your schema to get proper type narrowing of the context value.
43
+ *
44
+ * @param _params - Optional params for type inference (not used at runtime)
45
+ * @returns The schema context value or null if not within a provider
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * // Without type params (returns generic context)
50
+ * function MyFormField() {
51
+ * const context = useFormSchema();
52
+ * if (!context) return null;
53
+ *
54
+ * const { schema, discriminator } = context;
55
+ * // Use schema for validation or field extraction
56
+ * }
57
+ *
58
+ * // With type params (for type-safe schema access)
59
+ * function TypedFormField() {
60
+ * const context = useFormSchema({ schema: mySchema });
61
+ * // context.schema is now typed as typeof mySchema
62
+ * }
63
+ * ```
64
+ */
65
+ declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = DiscriminatorValue<TSchema, TDiscriminatorKey>>(_params?: {
66
+ schema: TSchema;
67
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
68
+ }): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
69
+ /**
70
+ * Provider component that makes Zod schema available to all child components.
71
+ *
72
+ * Use this to wrap your form and provide schema context to nested components
73
+ * like field labels and validation indicators.
74
+ *
75
+ * @example
76
+ * Basic usage with ZodObject
77
+ * ```tsx
78
+ * const schema = z.object({
79
+ * name: z.string(),
80
+ * email: z.string().email().optional()
81
+ * });
82
+ *
83
+ * <FormSchemaProvider schema={schema}>
84
+ * <YourFormComponents />
85
+ * </FormSchemaProvider>
86
+ * ```
87
+ *
88
+ * @example
89
+ * Usage with discriminated union
90
+ * ```tsx
91
+ * const schema = z.discriminatedUnion('mode', [
92
+ * z.object({ mode: z.literal('create'), name: z.string() }),
93
+ * z.object({ mode: z.literal('edit'), id: z.number() })
94
+ * ]);
95
+ *
96
+ * <FormSchemaProvider
97
+ * schema={schema}
98
+ * discriminator={{ key: 'mode', value: 'create' }}
99
+ * >
100
+ * <YourFormComponents />
101
+ * </FormSchemaProvider>
102
+ * ```
103
+ */
104
+ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, discriminator, children, }: {
105
+ schema: TSchema;
106
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
107
+ children: ReactNode;
108
+ }): react_jsx_runtime.JSX.Element;
109
+ /**
110
+ * Hook to check if a field requires valid input based on the Zod schema.
111
+ *
112
+ * Memoized - only recalculates when schema, name, or discriminator changes.
113
+ *
114
+ * @param params - Schema, name, and optional discriminator
115
+ * @returns true if the field requires valid input, false otherwise
116
+ *
117
+ * @example
118
+ * ```tsx
119
+ * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {
120
+ * const isRequired = useIsRequiredField({ schema, name });
121
+ *
122
+ * return (
123
+ * <label>
124
+ * {name}
125
+ * {isRequired && <span className="text-red-500">*</span>}
126
+ * </label>
127
+ * );
128
+ * }
129
+ * ```
130
+ */
131
+ declare function useIsRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
132
+ schema: TSchema;
133
+ name: TPath;
134
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
135
+ }): boolean;
136
+ /**
137
+ * Determines if a field requires valid input (will show validation errors on empty/invalid input).
138
+ *
139
+ * Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after
140
+ * removing defaults. This tells you if the field will error when user submits empty input.
141
+ *
142
+ * Returns false if the underlying field accepts:
143
+ * - `undefined` (via `.optional()`)
144
+ * - `null` (via `.nullable()`)
145
+ * - Empty strings (plain `z.string()` without `.min(1)`)
146
+ * - Empty arrays (plain `z.array()` without `.min(1)`)
147
+ *
148
+ * @param options - Schema, field name, and optional discriminator
149
+ * @returns true if the field requires valid input, false otherwise
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * const schema = z.object({
154
+ * name: z.string().min(1),
155
+ * bio: z.string().optional(),
156
+ * });
157
+ *
158
+ * isRequiredField({ schema, name: 'name' }); // true
159
+ * isRequiredField({ schema, name: 'bio' }); // false
160
+ * ```
161
+ *
162
+ * @example
163
+ * With discriminated union
164
+ * ```typescript
165
+ * const schema = z.discriminatedUnion('mode', [
166
+ * z.object({ mode: z.literal('create'), name: z.string().min(1) }),
167
+ * z.object({ mode: z.literal('edit'), id: z.number() }),
168
+ * ]);
169
+ *
170
+ * isRequiredField({
171
+ * schema,
172
+ * name: 'name',
173
+ * discriminator: { key: 'mode', value: 'create' },
174
+ * }); // true
175
+ * ```
176
+ */
177
+ declare function isRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
178
+ schema: TSchema;
179
+ name: TPath;
180
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
181
+ }): boolean;
182
+ /**
183
+ * React hook to extract a field's Zod schema from a parent schema.
184
+ *
185
+ * Memoized - only recalculates when schema, name, or discriminator changes.
186
+ * Supports nested paths and discriminated unions.
187
+ *
188
+ * @param params - Schema, name, and optional discriminator
189
+ * @returns The Zod schema for the field, or undefined if not found
190
+ *
191
+ * @example
192
+ * ```tsx
193
+ * function MyFieldInfo({ name, schema }: { name: string; schema: z.ZodType }) {
194
+ * const fieldSchema = useExtractFieldFromSchema({ schema, name });
195
+ *
196
+ * if (!fieldSchema) return null;
197
+ *
198
+ * // Use fieldSchema for custom validation or field info
199
+ * return <span>{fieldSchema._zod.typeName}</span>;
200
+ * }
201
+ * ```
202
+ *
203
+ * @example
204
+ * With discriminated union
205
+ * ```tsx
206
+ * const schema = z.discriminatedUnion('mode', [
207
+ * z.object({ mode: z.literal('create'), name: z.string() }),
208
+ * z.object({ mode: z.literal('edit'), id: z.number() }),
209
+ * ]);
210
+ *
211
+ * const fieldSchema = useExtractFieldFromSchema({
212
+ * schema,
213
+ * name: 'name',
214
+ * discriminator: { key: 'mode', value: 'create' },
215
+ * });
216
+ * // Returns z.string() schema
217
+ * ```
218
+ */
219
+ declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
220
+ schema: TSchema;
221
+ name: TPath;
222
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
223
+ }): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined;
224
+ /**
225
+ * Hook to get validation checks from a field's Zod schema.
226
+ *
227
+ * Memoized - only recalculates when schema, name, or discriminator changes.
228
+ * Combines field extraction and check retrieval in one cached operation.
229
+ *
230
+ * @param params - Schema, name, and optional discriminator
231
+ * @returns Array of validation checks (min, max, pattern, etc.) or empty array
232
+ *
233
+ * @example
234
+ * ```tsx
235
+ * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {
236
+ * const checks = useFieldChecks({ schema, name });
237
+ *
238
+ * const maxLength = checks.find(c => c.check === 'max_length');
239
+ * if (maxLength) {
240
+ * return <span>Max {maxLength.maximum} characters</span>;
241
+ * }
242
+ * return null;
243
+ * }
244
+ * ```
245
+ */
246
+ declare function useFieldChecks<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
247
+ schema: TSchema;
248
+ name: TPath;
249
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
250
+ }): ZodUnionCheck[];
7
251
 
8
252
  /**
9
253
  * Helper type that adds `null` to object-type fields only (excludes arrays).
@@ -52,6 +296,38 @@ type PartialWithNullableObjects<T> = Simplify<Partial<AddNullToObjects<T>>>;
52
296
  type PartialWithAllNullables<T> = {
53
297
  [K in keyof T]?: T[K] | null;
54
298
  };
299
+ /**
300
+ * Infers field values from a Zod schema compatible with React Hook Form's FieldValues.
301
+ *
302
+ * @example
303
+ * ```typescript
304
+ * const schema = z.object({ name: z.string() });
305
+ * type Values = InferredFieldValues<typeof schema>;
306
+ * // { name: string } & FieldValues
307
+ * ```
308
+ */
309
+ type InferredFieldValues<TSchema extends z.ZodType> = z.input<TSchema> & FieldValues;
310
+ /**
311
+ * Type-safe field names for a specific discriminator value.
312
+ *
313
+ * Narrows field names to only those that exist for the given discriminator value
314
+ * in a discriminated union schema.
315
+ *
316
+ * @example
317
+ * ```typescript
318
+ * const schema = z.discriminatedUnion('mode', [
319
+ * z.object({ mode: z.literal('create'), name: z.string() }),
320
+ * z.object({ mode: z.literal('edit'), id: z.number() }),
321
+ * ]);
322
+ *
323
+ * type CreateFields = ValidFieldPaths<typeof schema, 'mode', 'create'>;
324
+ * // "mode" | "name"
325
+ *
326
+ * type EditFields = ValidFieldPaths<typeof schema, 'mode', 'edit'>;
327
+ * // "mode" | "id"
328
+ * ```
329
+ */
330
+ type ValidFieldPaths<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>> = ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue> & Path<TFieldValues>;
55
331
 
56
332
  /**
57
333
  * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.
@@ -223,4 +499,4 @@ declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValu
223
499
  zodResolverOptions?: Parameters<typeof zodResolver>[1];
224
500
  } & Omit<UseFormProps<TFormInput, unknown, TOutput>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<TFormInput, unknown, TOutput>;
225
501
 
226
- export { type PartialWithAllNullables, type PartialWithNullableObjects, useZodForm };
502
+ export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,253 @@
1
- import { Simplify } from '@zod-utils/core';
1
+ import { DiscriminatorKey, DiscriminatorValue, Discriminator, ValidPaths, ExtractZodByPath, ZodUnionCheck, Simplify } from '@zod-utils/core';
2
2
  export * from '@zod-utils/core';
3
+ import * as react_jsx_runtime from 'react/jsx-runtime';
4
+ import { Context, ReactNode } from 'react';
5
+ import { z } from 'zod';
3
6
  import * as react_hook_form from 'react-hook-form';
4
- import { FieldValues, UseFormProps } from 'react-hook-form';
7
+ import { FieldValues, Path, UseFormProps } from 'react-hook-form';
5
8
  import { zodResolver } from '@hookform/resolvers/zod';
6
- import { z } from 'zod';
9
+
10
+ /**
11
+ * Type for the FormSchemaContext with full generic support.
12
+ * @internal
13
+ */
14
+ type FormSchemaContextType<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = Context<{
15
+ schema: TSchema;
16
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
17
+ } | null>;
18
+ /**
19
+ * Context value type for FormSchemaContext.
20
+ */
21
+ type FormSchemaContextValue<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = {
22
+ schema: TSchema;
23
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
24
+ } | null;
25
+ /**
26
+ * React Context for providing Zod schema to form components.
27
+ *
28
+ * Use with {@link FormSchemaProvider} to provide schema context, and
29
+ * {@link useFormSchema} to consume it in child components.
30
+ */
31
+ declare const FormSchemaContext: Context<{
32
+ schema: z.ZodType;
33
+ discriminator?: {
34
+ key: unknown;
35
+ value: unknown;
36
+ };
37
+ } | null>;
38
+ /**
39
+ * Hook to access the form schema from context.
40
+ *
41
+ * The optional `_params` argument is used for TypeScript type inference only.
42
+ * Pass your schema to get proper type narrowing of the context value.
43
+ *
44
+ * @param _params - Optional params for type inference (not used at runtime)
45
+ * @returns The schema context value or null if not within a provider
46
+ *
47
+ * @example
48
+ * ```tsx
49
+ * // Without type params (returns generic context)
50
+ * function MyFormField() {
51
+ * const context = useFormSchema();
52
+ * if (!context) return null;
53
+ *
54
+ * const { schema, discriminator } = context;
55
+ * // Use schema for validation or field extraction
56
+ * }
57
+ *
58
+ * // With type params (for type-safe schema access)
59
+ * function TypedFormField() {
60
+ * const context = useFormSchema({ schema: mySchema });
61
+ * // context.schema is now typed as typeof mySchema
62
+ * }
63
+ * ```
64
+ */
65
+ declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = DiscriminatorValue<TSchema, TDiscriminatorKey>>(_params?: {
66
+ schema: TSchema;
67
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
68
+ }): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
69
+ /**
70
+ * Provider component that makes Zod schema available to all child components.
71
+ *
72
+ * Use this to wrap your form and provide schema context to nested components
73
+ * like field labels and validation indicators.
74
+ *
75
+ * @example
76
+ * Basic usage with ZodObject
77
+ * ```tsx
78
+ * const schema = z.object({
79
+ * name: z.string(),
80
+ * email: z.string().email().optional()
81
+ * });
82
+ *
83
+ * <FormSchemaProvider schema={schema}>
84
+ * <YourFormComponents />
85
+ * </FormSchemaProvider>
86
+ * ```
87
+ *
88
+ * @example
89
+ * Usage with discriminated union
90
+ * ```tsx
91
+ * const schema = z.discriminatedUnion('mode', [
92
+ * z.object({ mode: z.literal('create'), name: z.string() }),
93
+ * z.object({ mode: z.literal('edit'), id: z.number() })
94
+ * ]);
95
+ *
96
+ * <FormSchemaProvider
97
+ * schema={schema}
98
+ * discriminator={{ key: 'mode', value: 'create' }}
99
+ * >
100
+ * <YourFormComponents />
101
+ * </FormSchemaProvider>
102
+ * ```
103
+ */
104
+ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, discriminator, children, }: {
105
+ schema: TSchema;
106
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
107
+ children: ReactNode;
108
+ }): react_jsx_runtime.JSX.Element;
109
+ /**
110
+ * Hook to check if a field requires valid input based on the Zod schema.
111
+ *
112
+ * Memoized - only recalculates when schema, name, or discriminator changes.
113
+ *
114
+ * @param params - Schema, name, and optional discriminator
115
+ * @returns true if the field requires valid input, false otherwise
116
+ *
117
+ * @example
118
+ * ```tsx
119
+ * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {
120
+ * const isRequired = useIsRequiredField({ schema, name });
121
+ *
122
+ * return (
123
+ * <label>
124
+ * {name}
125
+ * {isRequired && <span className="text-red-500">*</span>}
126
+ * </label>
127
+ * );
128
+ * }
129
+ * ```
130
+ */
131
+ declare function useIsRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
132
+ schema: TSchema;
133
+ name: TPath;
134
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
135
+ }): boolean;
136
+ /**
137
+ * Determines if a field requires valid input (will show validation errors on empty/invalid input).
138
+ *
139
+ * Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after
140
+ * removing defaults. This tells you if the field will error when user submits empty input.
141
+ *
142
+ * Returns false if the underlying field accepts:
143
+ * - `undefined` (via `.optional()`)
144
+ * - `null` (via `.nullable()`)
145
+ * - Empty strings (plain `z.string()` without `.min(1)`)
146
+ * - Empty arrays (plain `z.array()` without `.min(1)`)
147
+ *
148
+ * @param options - Schema, field name, and optional discriminator
149
+ * @returns true if the field requires valid input, false otherwise
150
+ *
151
+ * @example
152
+ * ```typescript
153
+ * const schema = z.object({
154
+ * name: z.string().min(1),
155
+ * bio: z.string().optional(),
156
+ * });
157
+ *
158
+ * isRequiredField({ schema, name: 'name' }); // true
159
+ * isRequiredField({ schema, name: 'bio' }); // false
160
+ * ```
161
+ *
162
+ * @example
163
+ * With discriminated union
164
+ * ```typescript
165
+ * const schema = z.discriminatedUnion('mode', [
166
+ * z.object({ mode: z.literal('create'), name: z.string().min(1) }),
167
+ * z.object({ mode: z.literal('edit'), id: z.number() }),
168
+ * ]);
169
+ *
170
+ * isRequiredField({
171
+ * schema,
172
+ * name: 'name',
173
+ * discriminator: { key: 'mode', value: 'create' },
174
+ * }); // true
175
+ * ```
176
+ */
177
+ declare function isRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
178
+ schema: TSchema;
179
+ name: TPath;
180
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
181
+ }): boolean;
182
+ /**
183
+ * React hook to extract a field's Zod schema from a parent schema.
184
+ *
185
+ * Memoized - only recalculates when schema, name, or discriminator changes.
186
+ * Supports nested paths and discriminated unions.
187
+ *
188
+ * @param params - Schema, name, and optional discriminator
189
+ * @returns The Zod schema for the field, or undefined if not found
190
+ *
191
+ * @example
192
+ * ```tsx
193
+ * function MyFieldInfo({ name, schema }: { name: string; schema: z.ZodType }) {
194
+ * const fieldSchema = useExtractFieldFromSchema({ schema, name });
195
+ *
196
+ * if (!fieldSchema) return null;
197
+ *
198
+ * // Use fieldSchema for custom validation or field info
199
+ * return <span>{fieldSchema._zod.typeName}</span>;
200
+ * }
201
+ * ```
202
+ *
203
+ * @example
204
+ * With discriminated union
205
+ * ```tsx
206
+ * const schema = z.discriminatedUnion('mode', [
207
+ * z.object({ mode: z.literal('create'), name: z.string() }),
208
+ * z.object({ mode: z.literal('edit'), id: z.number() }),
209
+ * ]);
210
+ *
211
+ * const fieldSchema = useExtractFieldFromSchema({
212
+ * schema,
213
+ * name: 'name',
214
+ * discriminator: { key: 'mode', value: 'create' },
215
+ * });
216
+ * // Returns z.string() schema
217
+ * ```
218
+ */
219
+ declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
220
+ schema: TSchema;
221
+ name: TPath;
222
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
223
+ }): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined;
224
+ /**
225
+ * Hook to get validation checks from a field's Zod schema.
226
+ *
227
+ * Memoized - only recalculates when schema, name, or discriminator changes.
228
+ * Combines field extraction and check retrieval in one cached operation.
229
+ *
230
+ * @param params - Schema, name, and optional discriminator
231
+ * @returns Array of validation checks (min, max, pattern, etc.) or empty array
232
+ *
233
+ * @example
234
+ * ```tsx
235
+ * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {
236
+ * const checks = useFieldChecks({ schema, name });
237
+ *
238
+ * const maxLength = checks.find(c => c.check === 'max_length');
239
+ * if (maxLength) {
240
+ * return <span>Max {maxLength.maximum} characters</span>;
241
+ * }
242
+ * return null;
243
+ * }
244
+ * ```
245
+ */
246
+ declare function useFieldChecks<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
247
+ schema: TSchema;
248
+ name: TPath;
249
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
250
+ }): ZodUnionCheck[];
7
251
 
8
252
  /**
9
253
  * Helper type that adds `null` to object-type fields only (excludes arrays).
@@ -52,6 +296,38 @@ type PartialWithNullableObjects<T> = Simplify<Partial<AddNullToObjects<T>>>;
52
296
  type PartialWithAllNullables<T> = {
53
297
  [K in keyof T]?: T[K] | null;
54
298
  };
299
+ /**
300
+ * Infers field values from a Zod schema compatible with React Hook Form's FieldValues.
301
+ *
302
+ * @example
303
+ * ```typescript
304
+ * const schema = z.object({ name: z.string() });
305
+ * type Values = InferredFieldValues<typeof schema>;
306
+ * // { name: string } & FieldValues
307
+ * ```
308
+ */
309
+ type InferredFieldValues<TSchema extends z.ZodType> = z.input<TSchema> & FieldValues;
310
+ /**
311
+ * Type-safe field names for a specific discriminator value.
312
+ *
313
+ * Narrows field names to only those that exist for the given discriminator value
314
+ * in a discriminated union schema.
315
+ *
316
+ * @example
317
+ * ```typescript
318
+ * const schema = z.discriminatedUnion('mode', [
319
+ * z.object({ mode: z.literal('create'), name: z.string() }),
320
+ * z.object({ mode: z.literal('edit'), id: z.number() }),
321
+ * ]);
322
+ *
323
+ * type CreateFields = ValidFieldPaths<typeof schema, 'mode', 'create'>;
324
+ * // "mode" | "name"
325
+ *
326
+ * type EditFields = ValidFieldPaths<typeof schema, 'mode', 'edit'>;
327
+ * // "mode" | "id"
328
+ * ```
329
+ */
330
+ type ValidFieldPaths<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>> = ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue> & Path<TFieldValues>;
55
331
 
56
332
  /**
57
333
  * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.
@@ -223,4 +499,4 @@ declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValu
223
499
  zodResolverOptions?: Parameters<typeof zodResolver>[1];
224
500
  } & Omit<UseFormProps<TFormInput, unknown, TOutput>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<TFormInput, unknown, TOutput>;
225
501
 
226
- export { type PartialWithAllNullables, type PartialWithNullableObjects, useZodForm };
502
+ export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  var core = require('@zod-utils/core');
4
+ var react = require('react');
5
+ var jsxRuntime = require('react/jsx-runtime');
4
6
  var zod = require('@hookform/resolvers/zod');
5
7
  var reactHookForm = require('react-hook-form');
6
8
 
@@ -32,6 +34,66 @@ var __objRest = (source, exclude) => {
32
34
  }
33
35
  return target;
34
36
  };
37
+ var FormSchemaContext = react.createContext(null);
38
+ function useFormSchema(_params) {
39
+ return react.useContext(
40
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
41
+ FormSchemaContext
42
+ );
43
+ }
44
+ function FormSchemaProvider({
45
+ schema,
46
+ discriminator,
47
+ children
48
+ }) {
49
+ return /* @__PURE__ */ jsxRuntime.jsx(FormSchemaContext.Provider, { value: { schema, discriminator }, children });
50
+ }
51
+ function useIsRequiredField({
52
+ schema,
53
+ name,
54
+ discriminator
55
+ }) {
56
+ return react.useMemo(
57
+ () => isRequiredField({ schema, name, discriminator }),
58
+ [schema, name, discriminator]
59
+ );
60
+ }
61
+ function isRequiredField({
62
+ schema,
63
+ name,
64
+ discriminator
65
+ }) {
66
+ const field = core.extractFieldFromSchema({
67
+ schema,
68
+ name,
69
+ discriminator
70
+ });
71
+ if (!field) {
72
+ return false;
73
+ }
74
+ return core.requiresValidInput(field);
75
+ }
76
+ function useExtractFieldFromSchema({
77
+ schema,
78
+ name,
79
+ discriminator
80
+ }) {
81
+ return react.useMemo(
82
+ () => core.extractFieldFromSchema({ schema, name, discriminator }),
83
+ [schema, name, discriminator]
84
+ );
85
+ }
86
+ function useFieldChecks({
87
+ schema,
88
+ name,
89
+ discriminator
90
+ }) {
91
+ return react.useMemo(() => {
92
+ const field = core.extractFieldFromSchema({ schema, name, discriminator });
93
+ if (!field) return [];
94
+ return core.getFieldChecks(field);
95
+ }, [schema, name, discriminator]);
96
+ }
35
97
  var useZodForm = (_a) => {
36
98
  var _b = _a, {
37
99
  schema,
@@ -46,6 +108,13 @@ var useZodForm = (_a) => {
46
108
  }, formOptions));
47
109
  };
48
110
 
111
+ exports.FormSchemaContext = FormSchemaContext;
112
+ exports.FormSchemaProvider = FormSchemaProvider;
113
+ exports.isRequiredField = isRequiredField;
114
+ exports.useExtractFieldFromSchema = useExtractFieldFromSchema;
115
+ exports.useFieldChecks = useFieldChecks;
116
+ exports.useFormSchema = useFormSchema;
117
+ exports.useIsRequiredField = useIsRequiredField;
49
118
  exports.useZodForm = useZodForm;
50
119
  Object.keys(core).forEach(function (k) {
51
120
  if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/use-zod-form.ts"],"names":["zodResolver","useForm"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4KO,IAAM,UAAA,GAAa,CAKxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAnLF,GAiLE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAUA,EAAA,MAAM,QAAA,GAAWA,eAAA,CAAY,MAAA,EAAQ,kBAAkB,CAAA;AAGvD,EAAA,OAAOC,qBAAA,CAAQ,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CACqD,CAAA;AAC5D","file":"index.js","sourcesContent":["import { zodResolver } from '@hookform/resolvers/zod';\nimport { type FieldValues, type UseFormProps, useForm } from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`\n * @param options.defaultValues - Default form values (shallow partial - nested objects must be complete if provided)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * Without default values (all fields are optional during editing)\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * age: z.number(),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * // ✅ No defaultValues needed - fields are optional during editing\n * const form = useZodForm({ schema });\n *\n * // Form fields can be set individually as user types\n * form.setValue('name', 'John');\n * form.setValue('email', 'john@example.com');\n * form.setValue('age', 25);\n *\n * // All fields must be valid on submit (per schema validation)\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n TInput extends FieldValues,\n TOutput extends FieldValues,\n TFormInput extends\n PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TFormInput;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<\n UseFormProps<TFormInput, unknown, TOutput>,\n 'resolver' | 'defaultValues'\n>) => {\n const resolver = zodResolver(schema, zodResolverOptions);\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useForm({\n resolver,\n ...formOptions,\n } as unknown as UseFormProps<TFormInput, unknown, TOutput>);\n};\n"]}
1
+ {"version":3,"sources":["../src/context.tsx","../src/use-zod-form.ts"],"names":["createContext","useContext","jsx","useMemo","extractFieldFromSchema","requiresValidInput","getFieldChecks","zodResolver","useForm"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DO,IAAM,iBAAA,GAAoBA,oBAMvB,IAAI;AA6BP,SAAS,cAUd,OAAA,EAQyE;AACzE,EAAA,OAAOC,gBAAA;AAAA;AAAA,IAEL;AAAA,GAQF;AACF;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EAQG;AACD,EAAA,uBACEC,cAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,kBAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQY;AACV,EAAA,OAAOC,aAAA;AAAA,IACL,MAAM,eAAA,CAAgB,EAAE,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAA;AAAA,IACrD,CAAC,MAAA,EAAQ,IAAA,EAAM,aAAa;AAAA,GAC9B;AACF;AA2CO,SAAS,eAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQY;AACV,EAAA,MAAM,QAAQC,2BAAA,CAAuB;AAAA,IACnC,MAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAOC,wBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,yBAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQ+D;AAC7D,EAAA,OAAOF,aAAA;AAAA,IACL,MAAMC,2BAAA,CAAuB,EAAE,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAA;AAAA,IAC5D,CAAC,MAAA,EAAQ,IAAA,EAAM,aAAa;AAAA,GAC9B;AACF;AAwBO,SAAS,cAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQoB;AAClB,EAAA,OAAOD,cAAQ,MAAM;AACnB,IAAA,MAAM,QAAQC,2BAAA,CAAuB,EAAE,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAA;AACpE,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAOE,oBAAe,KAAK,CAAA;AAAA,EAC7B,CAAA,EAAG,CAAC,MAAA,EAAQ,IAAA,EAAM,aAAa,CAAC,CAAA;AAClC;AC/OO,IAAM,UAAA,GAAa,CAKxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAnLF,GAiLE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAUA,EAAA,MAAM,QAAA,GAAWC,eAAA,CAAY,MAAA,EAAQ,kBAAkB,CAAA;AAGvD,EAAA,OAAOC,qBAAA,CAAQ,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CACqD,CAAA;AAC5D","file":"index.js","sourcesContent":["'use client';\n\nimport {\n type Discriminator,\n type DiscriminatorKey,\n type DiscriminatorValue,\n type ExtractZodByPath,\n extractFieldFromSchema,\n getFieldChecks,\n requiresValidInput,\n type ValidPaths,\n type ZodUnionCheck,\n} from '@zod-utils/core';\nimport {\n type Context,\n createContext,\n type ReactNode,\n useContext,\n useMemo,\n} from 'react';\nimport type { z } from 'zod';\n\n/**\n * Type for the FormSchemaContext with full generic support.\n * @internal\n */\nexport type FormSchemaContextType<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n> = Context<{\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n} | null>;\n\n/**\n * Context value type for FormSchemaContext.\n */\nexport type FormSchemaContextValue<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n> = {\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n} | null;\n\n/**\n * React Context for providing Zod schema to form components.\n *\n * Use with {@link FormSchemaProvider} to provide schema context, and\n * {@link useFormSchema} to consume it in child components.\n */\nexport const FormSchemaContext = createContext<{\n schema: z.ZodType;\n discriminator?: {\n key: unknown;\n value: unknown;\n };\n} | null>(null);\n\n/**\n * Hook to access the form schema from context.\n *\n * The optional `_params` argument is used for TypeScript type inference only.\n * Pass your schema to get proper type narrowing of the context value.\n *\n * @param _params - Optional params for type inference (not used at runtime)\n * @returns The schema context value or null if not within a provider\n *\n * @example\n * ```tsx\n * // Without type params (returns generic context)\n * function MyFormField() {\n * const context = useFormSchema();\n * if (!context) return null;\n *\n * const { schema, discriminator } = context;\n * // Use schema for validation or field extraction\n * }\n *\n * // With type params (for type-safe schema access)\n * function TypedFormField() {\n * const context = useFormSchema({ schema: mySchema });\n * // context.schema is now typed as typeof mySchema\n * }\n * ```\n */\nexport function useFormSchema<\n TSchema extends z.ZodType = z.ZodType,\n TDiscriminatorKey extends\n DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>(\n // Parameter used for type inference only, not at runtime\n _params?: {\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n },\n): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue> {\n return useContext(\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n FormSchemaContext as Context<{\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n } | null>,\n );\n}\n\n/**\n * Provider component that makes Zod schema available to all child components.\n *\n * Use this to wrap your form and provide schema context to nested components\n * like field labels and validation indicators.\n *\n * @example\n * Basic usage with ZodObject\n * ```tsx\n * const schema = z.object({\n * name: z.string(),\n * email: z.string().email().optional()\n * });\n *\n * <FormSchemaProvider schema={schema}>\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n *\n * @example\n * Usage with discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() })\n * ]);\n *\n * <FormSchemaProvider\n * schema={schema}\n * discriminator={{ key: 'mode', value: 'create' }}\n * >\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n */\nexport function FormSchemaProvider<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n discriminator,\n children,\n}: {\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n children: ReactNode;\n}) {\n return (\n <FormSchemaContext.Provider value={{ schema, discriminator }}>\n {children}\n </FormSchemaContext.Provider>\n );\n}\n\n/**\n * Hook to check if a field requires valid input based on the Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```tsx\n * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {\n * const isRequired = useIsRequiredField({ schema, name });\n *\n * return (\n * <label>\n * {name}\n * {isRequired && <span className=\"text-red-500\">*</span>}\n * </label>\n * );\n * }\n * ```\n */\nexport function useIsRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): boolean {\n return useMemo(\n () => isRequiredField({ schema, name, discriminator }),\n [schema, name, discriminator],\n );\n}\n\n/**\n * Determines if a field requires valid input (will show validation errors on empty/invalid input).\n *\n * Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after\n * removing defaults. This tells you if the field will error when user submits empty input.\n *\n * Returns false if the underlying field accepts:\n * - `undefined` (via `.optional()`)\n * - `null` (via `.nullable()`)\n * - Empty strings (plain `z.string()` without `.min(1)`)\n * - Empty arrays (plain `z.array()` without `.min(1)`)\n *\n * @param options - Schema, field name, and optional discriminator\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * bio: z.string().optional(),\n * });\n *\n * isRequiredField({ schema, name: 'name' }); // true\n * isRequiredField({ schema, name: 'bio' }); // false\n * ```\n *\n * @example\n * With discriminated union\n * ```typescript\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string().min(1) }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * isRequiredField({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * }); // true\n * ```\n */\nexport function isRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): boolean {\n const field = extractFieldFromSchema({\n schema,\n name,\n discriminator,\n });\n\n if (!field) {\n return false;\n }\n\n return requiresValidInput(field);\n}\n\n/**\n * React hook to extract a field's Zod schema from a parent schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Supports nested paths and discriminated unions.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns The Zod schema for the field, or undefined if not found\n *\n * @example\n * ```tsx\n * function MyFieldInfo({ name, schema }: { name: string; schema: z.ZodType }) {\n * const fieldSchema = useExtractFieldFromSchema({ schema, name });\n *\n * if (!fieldSchema) return null;\n *\n * // Use fieldSchema for custom validation or field info\n * return <span>{fieldSchema._zod.typeName}</span>;\n * }\n * ```\n *\n * @example\n * With discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * const fieldSchema = useExtractFieldFromSchema({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * });\n * // Returns z.string() schema\n * ```\n */\nexport function useExtractFieldFromSchema<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined {\n return useMemo(\n () => extractFieldFromSchema({ schema, name, discriminator }),\n [schema, name, discriminator],\n );\n}\n\n/**\n * Hook to get validation checks from a field's Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Combines field extraction and check retrieval in one cached operation.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns Array of validation checks (min, max, pattern, etc.) or empty array\n *\n * @example\n * ```tsx\n * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {\n * const checks = useFieldChecks({ schema, name });\n *\n * const maxLength = checks.find(c => c.check === 'max_length');\n * if (maxLength) {\n * return <span>Max {maxLength.maximum} characters</span>;\n * }\n * return null;\n * }\n * ```\n */\nexport function useFieldChecks<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): ZodUnionCheck[] {\n return useMemo(() => {\n const field = extractFieldFromSchema({ schema, name, discriminator });\n if (!field) return [];\n return getFieldChecks(field);\n }, [schema, name, discriminator]);\n}\n","import { zodResolver } from '@hookform/resolvers/zod';\nimport { type FieldValues, type UseFormProps, useForm } from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`\n * @param options.defaultValues - Default form values (shallow partial - nested objects must be complete if provided)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * Without default values (all fields are optional during editing)\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * age: z.number(),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * // ✅ No defaultValues needed - fields are optional during editing\n * const form = useZodForm({ schema });\n *\n * // Form fields can be set individually as user types\n * form.setValue('name', 'John');\n * form.setValue('email', 'john@example.com');\n * form.setValue('age', 25);\n *\n * // All fields must be valid on submit (per schema validation)\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n TInput extends FieldValues,\n TOutput extends FieldValues,\n TFormInput extends\n PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TFormInput;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<\n UseFormProps<TFormInput, unknown, TOutput>,\n 'resolver' | 'defaultValues'\n>) => {\n const resolver = zodResolver(schema, zodResolverOptions);\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useForm({\n resolver,\n ...formOptions,\n } as unknown as UseFormProps<TFormInput, unknown, TOutput>);\n};\n"]}
package/dist/index.mjs CHANGED
@@ -1,4 +1,7 @@
1
+ import { extractFieldFromSchema, requiresValidInput, getFieldChecks } from '@zod-utils/core';
1
2
  export * from '@zod-utils/core';
3
+ import { createContext, useContext, useMemo } from 'react';
4
+ import { jsx } from 'react/jsx-runtime';
2
5
  import { zodResolver } from '@hookform/resolvers/zod';
3
6
  import { useForm } from 'react-hook-form';
4
7
 
@@ -30,6 +33,66 @@ var __objRest = (source, exclude) => {
30
33
  }
31
34
  return target;
32
35
  };
36
+ var FormSchemaContext = createContext(null);
37
+ function useFormSchema(_params) {
38
+ return useContext(
39
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
40
+ FormSchemaContext
41
+ );
42
+ }
43
+ function FormSchemaProvider({
44
+ schema,
45
+ discriminator,
46
+ children
47
+ }) {
48
+ return /* @__PURE__ */ jsx(FormSchemaContext.Provider, { value: { schema, discriminator }, children });
49
+ }
50
+ function useIsRequiredField({
51
+ schema,
52
+ name,
53
+ discriminator
54
+ }) {
55
+ return useMemo(
56
+ () => isRequiredField({ schema, name, discriminator }),
57
+ [schema, name, discriminator]
58
+ );
59
+ }
60
+ function isRequiredField({
61
+ schema,
62
+ name,
63
+ discriminator
64
+ }) {
65
+ const field = extractFieldFromSchema({
66
+ schema,
67
+ name,
68
+ discriminator
69
+ });
70
+ if (!field) {
71
+ return false;
72
+ }
73
+ return requiresValidInput(field);
74
+ }
75
+ function useExtractFieldFromSchema({
76
+ schema,
77
+ name,
78
+ discriminator
79
+ }) {
80
+ return useMemo(
81
+ () => extractFieldFromSchema({ schema, name, discriminator }),
82
+ [schema, name, discriminator]
83
+ );
84
+ }
85
+ function useFieldChecks({
86
+ schema,
87
+ name,
88
+ discriminator
89
+ }) {
90
+ return useMemo(() => {
91
+ const field = extractFieldFromSchema({ schema, name, discriminator });
92
+ if (!field) return [];
93
+ return getFieldChecks(field);
94
+ }, [schema, name, discriminator]);
95
+ }
33
96
  var useZodForm = (_a) => {
34
97
  var _b = _a, {
35
98
  schema,
@@ -44,6 +107,6 @@ var useZodForm = (_a) => {
44
107
  }, formOptions));
45
108
  };
46
109
 
47
- export { useZodForm };
110
+ export { FormSchemaContext, FormSchemaProvider, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
48
111
  //# sourceMappingURL=index.mjs.map
49
112
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/use-zod-form.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4KO,IAAM,UAAA,GAAa,CAKxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAnLF,GAiLE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAUA,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,MAAA,EAAQ,kBAAkB,CAAA;AAGvD,EAAA,OAAO,OAAA,CAAQ,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CACqD,CAAA;AAC5D","file":"index.mjs","sourcesContent":["import { zodResolver } from '@hookform/resolvers/zod';\nimport { type FieldValues, type UseFormProps, useForm } from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`\n * @param options.defaultValues - Default form values (shallow partial - nested objects must be complete if provided)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * Without default values (all fields are optional during editing)\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * age: z.number(),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * // ✅ No defaultValues needed - fields are optional during editing\n * const form = useZodForm({ schema });\n *\n * // Form fields can be set individually as user types\n * form.setValue('name', 'John');\n * form.setValue('email', 'john@example.com');\n * form.setValue('age', 25);\n *\n * // All fields must be valid on submit (per schema validation)\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n TInput extends FieldValues,\n TOutput extends FieldValues,\n TFormInput extends\n PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TFormInput;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<\n UseFormProps<TFormInput, unknown, TOutput>,\n 'resolver' | 'defaultValues'\n>) => {\n const resolver = zodResolver(schema, zodResolverOptions);\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useForm({\n resolver,\n ...formOptions,\n } as unknown as UseFormProps<TFormInput, unknown, TOutput>);\n};\n"]}
1
+ {"version":3,"sources":["../src/context.tsx","../src/use-zod-form.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DO,IAAM,iBAAA,GAAoB,cAMvB,IAAI;AA6BP,SAAS,cAUd,OAAA,EAQyE;AACzE,EAAA,OAAO,UAAA;AAAA;AAAA,IAEL;AAAA,GAQF;AACF;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EAQG;AACD,EAAA,uBACE,GAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,kBAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQY;AACV,EAAA,OAAO,OAAA;AAAA,IACL,MAAM,eAAA,CAAgB,EAAE,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAA;AAAA,IACrD,CAAC,MAAA,EAAQ,IAAA,EAAM,aAAa;AAAA,GAC9B;AACF;AA2CO,SAAS,eAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQY;AACV,EAAA,MAAM,QAAQ,sBAAA,CAAuB;AAAA,IACnC,MAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,mBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,yBAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQ+D;AAC7D,EAAA,OAAO,OAAA;AAAA,IACL,MAAM,sBAAA,CAAuB,EAAE,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAA;AAAA,IAC5D,CAAC,MAAA,EAAQ,IAAA,EAAM,aAAa;AAAA,GAC9B;AACF;AAwBO,SAAS,cAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQoB;AAClB,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,MAAM,QAAQ,sBAAA,CAAuB,EAAE,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAA;AACpE,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAO,eAAe,KAAK,CAAA;AAAA,EAC7B,CAAA,EAAG,CAAC,MAAA,EAAQ,IAAA,EAAM,aAAa,CAAC,CAAA;AAClC;AC/OO,IAAM,UAAA,GAAa,CAKxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAnLF,GAiLE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAUA,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,MAAA,EAAQ,kBAAkB,CAAA;AAGvD,EAAA,OAAO,OAAA,CAAQ,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CACqD,CAAA;AAC5D","file":"index.mjs","sourcesContent":["'use client';\n\nimport {\n type Discriminator,\n type DiscriminatorKey,\n type DiscriminatorValue,\n type ExtractZodByPath,\n extractFieldFromSchema,\n getFieldChecks,\n requiresValidInput,\n type ValidPaths,\n type ZodUnionCheck,\n} from '@zod-utils/core';\nimport {\n type Context,\n createContext,\n type ReactNode,\n useContext,\n useMemo,\n} from 'react';\nimport type { z } from 'zod';\n\n/**\n * Type for the FormSchemaContext with full generic support.\n * @internal\n */\nexport type FormSchemaContextType<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n> = Context<{\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n} | null>;\n\n/**\n * Context value type for FormSchemaContext.\n */\nexport type FormSchemaContextValue<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n> = {\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n} | null;\n\n/**\n * React Context for providing Zod schema to form components.\n *\n * Use with {@link FormSchemaProvider} to provide schema context, and\n * {@link useFormSchema} to consume it in child components.\n */\nexport const FormSchemaContext = createContext<{\n schema: z.ZodType;\n discriminator?: {\n key: unknown;\n value: unknown;\n };\n} | null>(null);\n\n/**\n * Hook to access the form schema from context.\n *\n * The optional `_params` argument is used for TypeScript type inference only.\n * Pass your schema to get proper type narrowing of the context value.\n *\n * @param _params - Optional params for type inference (not used at runtime)\n * @returns The schema context value or null if not within a provider\n *\n * @example\n * ```tsx\n * // Without type params (returns generic context)\n * function MyFormField() {\n * const context = useFormSchema();\n * if (!context) return null;\n *\n * const { schema, discriminator } = context;\n * // Use schema for validation or field extraction\n * }\n *\n * // With type params (for type-safe schema access)\n * function TypedFormField() {\n * const context = useFormSchema({ schema: mySchema });\n * // context.schema is now typed as typeof mySchema\n * }\n * ```\n */\nexport function useFormSchema<\n TSchema extends z.ZodType = z.ZodType,\n TDiscriminatorKey extends\n DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>(\n // Parameter used for type inference only, not at runtime\n _params?: {\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n },\n): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue> {\n return useContext(\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n FormSchemaContext as Context<{\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n } | null>,\n );\n}\n\n/**\n * Provider component that makes Zod schema available to all child components.\n *\n * Use this to wrap your form and provide schema context to nested components\n * like field labels and validation indicators.\n *\n * @example\n * Basic usage with ZodObject\n * ```tsx\n * const schema = z.object({\n * name: z.string(),\n * email: z.string().email().optional()\n * });\n *\n * <FormSchemaProvider schema={schema}>\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n *\n * @example\n * Usage with discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() })\n * ]);\n *\n * <FormSchemaProvider\n * schema={schema}\n * discriminator={{ key: 'mode', value: 'create' }}\n * >\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n */\nexport function FormSchemaProvider<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n discriminator,\n children,\n}: {\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n children: ReactNode;\n}) {\n return (\n <FormSchemaContext.Provider value={{ schema, discriminator }}>\n {children}\n </FormSchemaContext.Provider>\n );\n}\n\n/**\n * Hook to check if a field requires valid input based on the Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```tsx\n * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {\n * const isRequired = useIsRequiredField({ schema, name });\n *\n * return (\n * <label>\n * {name}\n * {isRequired && <span className=\"text-red-500\">*</span>}\n * </label>\n * );\n * }\n * ```\n */\nexport function useIsRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): boolean {\n return useMemo(\n () => isRequiredField({ schema, name, discriminator }),\n [schema, name, discriminator],\n );\n}\n\n/**\n * Determines if a field requires valid input (will show validation errors on empty/invalid input).\n *\n * Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after\n * removing defaults. This tells you if the field will error when user submits empty input.\n *\n * Returns false if the underlying field accepts:\n * - `undefined` (via `.optional()`)\n * - `null` (via `.nullable()`)\n * - Empty strings (plain `z.string()` without `.min(1)`)\n * - Empty arrays (plain `z.array()` without `.min(1)`)\n *\n * @param options - Schema, field name, and optional discriminator\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * bio: z.string().optional(),\n * });\n *\n * isRequiredField({ schema, name: 'name' }); // true\n * isRequiredField({ schema, name: 'bio' }); // false\n * ```\n *\n * @example\n * With discriminated union\n * ```typescript\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string().min(1) }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * isRequiredField({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * }); // true\n * ```\n */\nexport function isRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): boolean {\n const field = extractFieldFromSchema({\n schema,\n name,\n discriminator,\n });\n\n if (!field) {\n return false;\n }\n\n return requiresValidInput(field);\n}\n\n/**\n * React hook to extract a field's Zod schema from a parent schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Supports nested paths and discriminated unions.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns The Zod schema for the field, or undefined if not found\n *\n * @example\n * ```tsx\n * function MyFieldInfo({ name, schema }: { name: string; schema: z.ZodType }) {\n * const fieldSchema = useExtractFieldFromSchema({ schema, name });\n *\n * if (!fieldSchema) return null;\n *\n * // Use fieldSchema for custom validation or field info\n * return <span>{fieldSchema._zod.typeName}</span>;\n * }\n * ```\n *\n * @example\n * With discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * const fieldSchema = useExtractFieldFromSchema({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * });\n * // Returns z.string() schema\n * ```\n */\nexport function useExtractFieldFromSchema<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined {\n return useMemo(\n () => extractFieldFromSchema({ schema, name, discriminator }),\n [schema, name, discriminator],\n );\n}\n\n/**\n * Hook to get validation checks from a field's Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Combines field extraction and check retrieval in one cached operation.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns Array of validation checks (min, max, pattern, etc.) or empty array\n *\n * @example\n * ```tsx\n * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {\n * const checks = useFieldChecks({ schema, name });\n *\n * const maxLength = checks.find(c => c.check === 'max_length');\n * if (maxLength) {\n * return <span>Max {maxLength.maximum} characters</span>;\n * }\n * return null;\n * }\n * ```\n */\nexport function useFieldChecks<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): ZodUnionCheck[] {\n return useMemo(() => {\n const field = extractFieldFromSchema({ schema, name, discriminator });\n if (!field) return [];\n return getFieldChecks(field);\n }, [schema, name, discriminator]);\n}\n","import { zodResolver } from '@hookform/resolvers/zod';\nimport { type FieldValues, type UseFormProps, useForm } from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`\n * @param options.defaultValues - Default form values (shallow partial - nested objects must be complete if provided)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * Without default values (all fields are optional during editing)\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * age: z.number(),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * // ✅ No defaultValues needed - fields are optional during editing\n * const form = useZodForm({ schema });\n *\n * // Form fields can be set individually as user types\n * form.setValue('name', 'John');\n * form.setValue('email', 'john@example.com');\n * form.setValue('age', 25);\n *\n * // All fields must be valid on submit (per schema validation)\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n TInput extends FieldValues,\n TOutput extends FieldValues,\n TFormInput extends\n PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TFormInput;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<\n UseFormProps<TFormInput, unknown, TOutput>,\n 'resolver' | 'defaultValues'\n>) => {\n const resolver = zodResolver(schema, zodResolverOptions);\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useForm({\n resolver,\n ...formOptions,\n } as unknown as UseFormProps<TFormInput, unknown, TOutput>);\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zod-utils/react-hook-form",
3
- "version": "0.11.0",
3
+ "version": "2.0.0",
4
4
  "description": "React Hook Form integration and utilities for Zod schemas",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",