@zod-utils/react-hook-form 6.2.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -241,7 +241,7 @@ You can override the default input type transformation if needed:
241
241
  ```typescript
242
242
  import {
243
243
  useZodForm,
244
- DeepPartialWithAllNullables,
244
+ PartialWithAllNullables,
245
245
  } from "@zod-utils/react-hook-form";
246
246
  import { z } from "zod";
247
247
 
@@ -251,10 +251,10 @@ const schema = z.object({
251
251
  age: z.number(),
252
252
  });
253
253
 
254
- // Option 1: Use DeepPartialWithAllNullables to make ALL fields accept null
254
+ // Option 1: Use PartialWithAllNullables to make ALL fields accept null
255
255
  const form = useZodForm<
256
256
  z.infer<typeof schema>,
257
- DeepPartialWithAllNullables<z.infer<typeof schema>>
257
+ PartialWithAllNullables<z.infer<typeof schema>>
258
258
  >({
259
259
  schema,
260
260
  defaultValues: { username: null, email: null, age: null },
@@ -453,11 +453,10 @@ import {
453
453
  flattenFieldSelector,
454
454
 
455
455
  // Type utilities
456
- type DeepPartialWithNullableObjects,
457
- type DeepPartialWithAllNullables,
458
- // Deprecated aliases (kept for backward compatibility)
459
- type PartialWithNullableObjects, // Use DeepPartialWithNullableObjects
460
- type PartialWithAllNullables, // Use DeepPartialWithAllNullables
456
+ type PartialWithNullableObjects,
457
+ type PartialWithAllNullables,
458
+ type PartialFields,
459
+ partialFields,
461
460
  type DiscriminatorProps,
462
461
  type DiscriminatorKey,
463
462
  type DiscriminatorValue,
@@ -473,19 +472,20 @@ See [@zod-utils/core documentation](../core/README.md) for details on schema uti
473
472
 
474
473
  ### Type Utilities
475
474
 
476
- #### `DeepPartialWithNullableObjects<T>`
475
+ #### `PartialWithNullableObjects<T>`
477
476
 
478
- Recursively transforms properties based on their type. Primitive and array fields become optional-only (not nullable), while object fields become optional, nullable, and **recursively transformed**.
477
+ Transforms properties based on their type. By default, **non-recursive** - nested object fields stay strict.
479
478
 
480
479
  **Transformation rules:**
481
480
 
482
481
  - **Primitives** (string, number, boolean): optional → `type | undefined`
483
482
  - **Arrays**: optional → `type[] | undefined`
484
483
  - **Built-in objects** (Date, RegExp, etc.): optional and nullable → `type | null | undefined`
485
- - **Plain objects**: optional, nullable, and **recursively transformed** → `DeepType | null | undefined`
484
+ - **Plain objects**: optional and nullable, but **nested fields stay strict** → `{ strictField: type } | null | undefined`
485
+ - **Objects marked with `partialFields()`**: optional, nullable, and **recursively transformed** on direct fields
486
486
 
487
487
  ```typescript
488
- import type { DeepPartialWithNullableObjects } from "@zod-utils/react-hook-form";
488
+ import type { PartialWithNullableObjects } from "@zod-utils/react-hook-form";
489
489
 
490
490
  type User = {
491
491
  name: string;
@@ -494,39 +494,33 @@ type User = {
494
494
  profile: { bio: string; settings: { theme: string } };
495
495
  };
496
496
 
497
- type FormInput = DeepPartialWithNullableObjects<User>;
497
+ type FormInput = PartialWithNullableObjects<User>;
498
498
  // {
499
499
  // name?: string; // Primitive: optional, not nullable
500
500
  // age?: number; // Primitive: optional, not nullable
501
501
  // tags?: string[]; // Array: optional, not nullable
502
- // profile?: { // Object: optional, nullable, AND recursive
503
- // bio?: string; // Nested primitive: also optional
504
- // settings?: { theme?: string } | null; // Nested object: also recursive
502
+ // profile?: { // Object: optional, nullable
503
+ // bio: string; // Nested field: STRICT (not optional)
504
+ // settings: { theme: string }; // Nested object: STRICT
505
505
  // } | null;
506
506
  // }
507
507
  ```
508
508
 
509
- This ensures `useWatch` returns correct types (`T | undefined`) at any nesting depth, since form fields are `undefined` until values are set:
509
+ This is ideal for forms where nested objects come from selectors/dropdowns (should be complete when provided).
510
510
 
511
- ```typescript
512
- const form = useZodForm({ schema });
513
- const bio = form.watch("profile.bio"); // Correctly typed as `string | undefined`
514
- ```
515
-
516
- > **Note:** `PartialWithNullableObjects<T>` is a deprecated alias for `DeepPartialWithNullableObjects<T>`, kept for backward compatibility.
517
-
518
- #### `DeepPartialWithAllNullables<T>`
511
+ #### `PartialWithAllNullables<T>`
519
512
 
520
- Recursively makes all fields optional and nullable, regardless of type.
513
+ Makes all fields optional and nullable, but by default **non-recursive** - nested object fields stay strict.
521
514
 
522
515
  **Transformation rules:**
523
516
 
524
517
  - **Primitives**: optional and nullable → `type | null | undefined`
525
518
  - **Arrays**: optional and nullable → `type[] | null | undefined`
526
- - **Plain objects**: optional, nullable, and **recursively transformed** `DeepType | null | undefined`
519
+ - **Plain objects**: optional and nullable, but **nested fields stay strict**
520
+ - **Objects marked with `partialFields()`**: optional, nullable, and **recursively transformed** on direct fields
527
521
 
528
522
  ```typescript
529
- import type { DeepPartialWithAllNullables } from "@zod-utils/react-hook-form";
523
+ import type { PartialWithAllNullables } from "@zod-utils/react-hook-form";
530
524
 
531
525
  type User = {
532
526
  name: string;
@@ -534,17 +528,53 @@ type User = {
534
528
  profile: { bio: string };
535
529
  };
536
530
 
537
- type FormInput = DeepPartialWithAllNullables<User>;
531
+ type FormInput = PartialWithAllNullables<User>;
538
532
  // {
539
- // name?: string | null; // All fields: optional AND nullable
540
- // age?: number | null; // All fields: optional AND nullable
541
- // profile?: { bio?: string | null } | null; // Nested fields also nullable
533
+ // name?: string | null; // Primitive: optional AND nullable
534
+ // age?: number | null; // Primitive: optional AND nullable
535
+ // profile?: { bio: string } | null; // Object: nullable, but bio is STRICT
542
536
  // }
543
537
  ```
544
538
 
545
- Use this when all fields need to accept `null`, not just objects/arrays.
539
+ #### `partialFields(schema)` - Opt-in Recursive Transformation
540
+
541
+ Use `partialFields()` to mark specific nested objects that should have their **direct fields** made partial. This is useful for objects where users fill in fields manually (vs. objects selected from dropdowns).
542
+
543
+ ```typescript
544
+ import { partialFields } from "@zod-utils/react-hook-form";
545
+ import { z } from "zod";
546
+
547
+ const schema = z.object({
548
+ price: z.number(),
549
+ // User fills in these fields - opt-in to partial
550
+ detail: partialFields(
551
+ z.object({
552
+ hotel: z.string(),
553
+ nights: z.number(),
554
+ })
555
+ ),
556
+ // Selected from dropdown - stays strict
557
+ agent: z.object({
558
+ name: z.string(),
559
+ fee: z.number(),
560
+ }),
561
+ });
562
+
563
+ type FormInput = PartialWithNullableObjects<z.infer<typeof schema>>;
564
+ // {
565
+ // price?: number;
566
+ // detail?: {
567
+ // hotel?: string; // Partial - user input
568
+ // nights?: number; // Partial - user input
569
+ // } | null;
570
+ // agent?: {
571
+ // name: string; // STRICT - from selector
572
+ // fee: number; // STRICT - from selector
573
+ // } | null;
574
+ // }
575
+ ```
546
576
 
547
- > **Note:** `PartialWithAllNullables<T>` is a deprecated alias for `DeepPartialWithAllNullables<T>`, kept for backward compatibility.
577
+ **Note:** `partialFields()` only affects the direct fields of the marked object. Nested objects within it will still stay strict unless they are also wrapped with `partialFields()`.
548
578
 
549
579
  ---
550
580
 
package/dist/index.d.mts CHANGED
@@ -229,77 +229,150 @@ declare function useFieldChecks<TSchema extends z.ZodType, TDiscriminatorKey ext
229
229
  */
230
230
  type BuiltInObject = Date | RegExp | Map<unknown, unknown> | Set<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown> | Error;
231
231
  /**
232
- * Recursively transforms object types for form inputs.
233
- *
234
- * - **Primitives** (string, number, boolean): optional → `type | undefined`
235
- * - **Arrays**: optional → `type[] | undefined`
236
- * - **Built-in objects** (Date, RegExp, etc.): optional and nullable → `type | null | undefined`
237
- * - **Plain objects**: optional, nullable, and recursively transformed `DeepType | null | undefined`
238
- *
239
- * This ensures `useWatch` returns correct types at any nesting depth,
240
- * since form fields are `undefined` until values are set.
232
+ * Brand symbol for marking objects that should receive recursive partial transformation.
233
+ * @internal
234
+ */
235
+ declare const FormInputBrand: unique symbol;
236
+ /**
237
+ * Branded type to mark objects that should have their direct fields made partial.
238
+ * Use with {@link partialFields} helper to mark specific schema objects.
241
239
  *
242
240
  * @example
243
241
  * ```typescript
244
- * type User = { name: string; tags: string[]; profile: { bio: string } };
245
- * type FormInput = DeepPartialWithNullableObjects<User>;
246
- * // Evaluates to: { name?: string; tags?: string[]; profile?: { bio?: string } | null; }
242
+ * // Objects marked with PartialFields will have their direct fields made optional
243
+ * type MarkedObject = PartialFields<{ name: string; age: number }>;
247
244
  * ```
245
+ */
246
+ type PartialFields<T> = T & {
247
+ readonly [FormInputBrand]?: typeof FormInputBrand;
248
+ };
249
+ /**
250
+ * Helper type to safely get the brand property from a type.
251
+ * Returns `unknown` for non-branded types, `typeof FormInputBrand | undefined` for branded types.
252
+ * @internal
253
+ */
254
+ type GetPartialFieldsBrand<T> = (T & {
255
+ [FormInputBrand]?: unknown;
256
+ })[typeof FormInputBrand];
257
+ /**
258
+ * Helper type to check if a type has the PartialFields brand.
259
+ * Uses bidirectional extends check to distinguish branded from non-branded types.
260
+ * @internal
261
+ */
262
+ type IsPartialFieldsBranded<T> = GetPartialFieldsBrand<T> extends typeof FormInputBrand | undefined ? typeof FormInputBrand extends GetPartialFieldsBrand<T> ? true : false : false;
263
+ /**
264
+ * Helper type to extract the inner type from a PartialFields branded type.
265
+ * @internal
266
+ */
267
+ type ExtractPartialFieldsInner<T> = T extends PartialFields<infer U> ? U : never;
268
+ /**
269
+ * Helper function to mark a Zod schema so its direct fields become partial.
270
+ *
271
+ * By default, nested objects in form inputs keep their fields strict (only the object
272
+ * itself becomes nullable). Use this helper to opt-in specific objects to have their
273
+ * direct fields also become optional.
274
+ *
275
+ * **Note:** This only affects the direct fields of the marked object. Nested objects
276
+ * within it will still stay strict unless they are also wrapped with `partialFields()`.
277
+ *
278
+ * **Use cases:**
279
+ * - Form input fields that users fill in manually (should be partial)
280
+ * - Objects from selectors/dropdowns should NOT use this (keep strict)
248
281
  *
249
282
  * @example
250
- * Type inference with useZodForm
251
283
  * ```typescript
252
- * const schema = z.object({ name: z.string(), age: z.number() });
253
- * const form = useZodForm({ schema }); // ✅ Works without defaultValues
254
- * const name = form.watch('name'); // ✅ Correctly typed as string | undefined
284
+ * import { partialFields } from '@zod-utils/react-hook-form';
285
+ *
286
+ * const schema = z.object({
287
+ * price: z.number(),
288
+ * // User fills in these fields - opt-in to partial
289
+ * detail: partialFields(z.object({
290
+ * hotel: z.string(),
291
+ * nights: z.number(),
292
+ * })),
293
+ * // Selected from dropdown - stays strict
294
+ * agent: z.object({
295
+ * name: z.string(),
296
+ * fee: z.number(),
297
+ * }),
298
+ * });
299
+ *
300
+ * // Result with PartialWithNullableObjects:
301
+ * // detail.hotel → string | undefined (partial - user input)
302
+ * // detail.nights → number | undefined (partial - user input)
303
+ * // agent.name → string (strict! - from selector)
304
+ * // agent.fee → number (strict! - from selector)
255
305
  * ```
306
+ */
307
+ declare function partialFields<T extends z.ZodType>(schema: T): z.ZodType<PartialFields<z.infer<T>>, PartialFields<z.input<T>>>;
308
+ /**
309
+ * Transforms object types for form inputs with selective recursion.
310
+ *
311
+ * **Default behavior (non-recursive):**
312
+ * - **Primitives** (string, number, boolean): optional → `type | undefined`
313
+ * - **Arrays**: optional → `type[] | undefined`
314
+ * - **Built-in objects** (Date, RegExp, etc.): optional and nullable → `type | null | undefined`
315
+ * - **Plain objects**: optional and nullable, but nested fields stay **strict** → `{ strictField: type } | null | undefined`
316
+ *
317
+ * **Opt-in recursive behavior:**
318
+ * - Objects marked with {@link partialFields} will have their nested fields recursively transformed
319
+ *
320
+ * This ensures objects from selectors/dropdowns keep strict types for their fields,
321
+ * while form input fields can be partially filled.
256
322
  *
257
323
  * @example
258
- * Nested fields are also correctly typed
259
324
  * ```typescript
260
- * const schema = z.object({ profile: z.object({ bio: z.string() }) });
261
- * const form = useZodForm({ schema });
262
- * const bio = form.watch('profile.bio'); // ✅ Correctly typed as string | undefined
325
+ * import { partialFields } from '@zod-utils/react-hook-form';
326
+ * import { z } from 'zod';
327
+ *
328
+ * const schema = z.object({
329
+ * price: z.number(),
330
+ * detail: partialFields(z.object({ hotel: z.string(), nights: z.number() })),
331
+ * agent: z.object({ name: z.string(), fee: z.number() }),
332
+ * });
333
+ *
334
+ * type FormInput = PartialWithNullableObjects<z.infer<typeof schema>>;
335
+ * // {
336
+ * // price?: number;
337
+ * // detail?: { hotel?: string; nights?: number } | null; // Recursive!
338
+ * // agent?: { name: string; fee: number } | null; // Strict nested!
339
+ * // }
263
340
  * ```
264
341
  */
265
- type DeepPartialWithNullableObjects<T> = {
266
- [K in keyof T]?: T[K] extends readonly unknown[] ? T[K] : T[K] extends BuiltInObject ? T[K] | null : T[K] extends object ? Simplify<DeepPartialWithNullableObjects<T[K]>> | null : T[K];
342
+ type PartialWithNullableObjects<T> = {
343
+ [K in keyof T]?: T[K] extends readonly unknown[] ? T[K] : T[K] extends BuiltInObject ? T[K] | null : IsPartialFieldsBranded<T[K]> extends true ? Simplify<PartialWithNullableObjects<ExtractPartialFieldsInner<T[K]>>> | null : T[K] extends object ? T[K] | null : T[K];
267
344
  };
268
345
  /**
269
- * @deprecated Use {@link DeepPartialWithNullableObjects} instead.
270
- * This alias is kept for backward compatibility.
271
- */
272
- type PartialWithNullableObjects<T> = DeepPartialWithNullableObjects<T>;
273
- /**
274
- * Recursively makes all fields optional and nullable.
346
+ * Transforms all fields to be optional and nullable, with selective recursion.
347
+ *
348
+ * Similar to {@link PartialWithNullableObjects} but also adds `| null` to primitives and arrays.
275
349
  *
350
+ * **Default behavior (non-recursive):**
276
351
  * - **Primitives**: optional and nullable → `type | null | undefined`
277
352
  * - **Arrays**: optional and nullable → `type[] | null | undefined`
278
- * - **Built-in objects** (Date, RegExp, etc.): optional and nullable `type | null | undefined`
279
- * - **Plain objects**: optional, nullable, and recursively transformed → `DeepType | null | undefined`
353
+ * - **Plain objects**: optional and nullable, but nested fields stay **strict**
354
+ *
355
+ * **Opt-in recursive behavior:**
356
+ * - Objects marked with {@link partialFields} will have their nested fields recursively transformed
280
357
  *
281
358
  * @example
282
359
  * ```typescript
283
360
  * type User = { name: string; age: number; profile: { bio: string } };
284
- * type FormInput = DeepPartialWithAllNullables<User>;
285
- * // { name?: string | null; age?: number | null; profile?: { bio?: string | null } | null; }
361
+ * type FormInput = PartialWithAllNullables<User>;
362
+ * // { name?: string | null; age?: number | null; profile?: { bio: string } | null; }
363
+ * // Note: profile.bio stays strict (string, not string | null | undefined)
286
364
  * ```
287
365
  */
288
- type DeepPartialWithAllNullables<T> = {
289
- [K in keyof T]?: T[K] extends readonly unknown[] ? T[K] | null : T[K] extends BuiltInObject ? T[K] | null : T[K] extends object ? Simplify<DeepPartialWithAllNullables<T[K]>> | null : T[K] | null;
366
+ type PartialWithAllNullables<T> = {
367
+ [K in keyof T]?: T[K] extends readonly unknown[] ? T[K] | null : T[K] extends BuiltInObject ? T[K] | null : IsPartialFieldsBranded<T[K]> extends true ? Simplify<PartialWithAllNullables<ExtractPartialFieldsInner<T[K]>>> | null : T[K] extends object ? T[K] | null : T[K] | null;
290
368
  };
291
- /**
292
- * @deprecated Use {@link DeepPartialWithAllNullables} instead.
293
- * This alias is kept for backward compatibility.
294
- */
295
- type PartialWithAllNullables<T> = DeepPartialWithAllNullables<T>;
296
369
 
297
370
  /**
298
371
  * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.
299
372
  *
300
373
  * This hook eliminates the TypeScript friction between React Hook Form's nullable field values
301
374
  * and Zod's strict output types. It uses a two-type schema pattern where:
302
- * - **Input type** (`DeepPartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing
375
+ * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing
303
376
  * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)
304
377
  *
305
378
  * **Key Benefits:**
@@ -310,7 +383,7 @@ type PartialWithAllNullables<T> = DeepPartialWithAllNullables<T>;
310
383
  *
311
384
  * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)
312
385
  * @template TOutput - The Zod schema output type (extends FieldValues)
313
- * @template TFormInput - The form input type (defaults to DeepPartialWithNullableObjects<TInput>)
386
+ * @template TFormInput - The form input type (defaults to PartialWithNullableObjects<TInput>)
314
387
  * @template TDefaultValues - The type of default values (inferred from usage for better type safety)
315
388
  *
316
389
  * @param options - Configuration object
@@ -455,12 +528,12 @@ type PartialWithAllNullables<T> = DeepPartialWithAllNullables<T>;
455
528
  * }
456
529
  * ```
457
530
  *
458
- * @see {@link DeepPartialWithNullableObjects} for the type transformation utility
531
+ * @see {@link PartialWithNullableObjects} for the type transformation utility
459
532
  * @see https://react-hook-form.com/docs/useform for React Hook Form documentation
460
533
  * @see https://zod.dev for Zod schema documentation
461
534
  * @since 0.1.0
462
535
  */
463
- declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValues, TFormInput extends DeepPartialWithAllNullables<TInput> = DeepPartialWithNullableObjects<TInput>, TDefaultValues extends Partial<TFormInput> | undefined = undefined>({ schema, zodResolverOptions, ...formOptions }: {
536
+ declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValues, TFormInput extends PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>, TDefaultValues extends Partial<TFormInput> | undefined = undefined>({ schema, zodResolverOptions, ...formOptions }: {
464
537
  schema: z.ZodType<TOutput, TInput>;
465
538
  defaultValues?: TDefaultValues;
466
539
  zodResolverOptions?: Parameters<typeof zodResolver>[1];
@@ -491,4 +564,4 @@ declare function flattenFieldSelector(params?: {
491
564
  };
492
565
  }): unknown[];
493
566
 
494
- export { type DeepPartialWithAllNullables, type DeepPartialWithNullableObjects, FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type PartialWithAllNullables, type PartialWithNullableObjects, flattenFieldSelector, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
567
+ export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type PartialFields, type PartialWithAllNullables, type PartialWithNullableObjects, flattenFieldSelector, isRequiredField, partialFields, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
package/dist/index.d.ts CHANGED
@@ -229,77 +229,150 @@ declare function useFieldChecks<TSchema extends z.ZodType, TDiscriminatorKey ext
229
229
  */
230
230
  type BuiltInObject = Date | RegExp | Map<unknown, unknown> | Set<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown> | Error;
231
231
  /**
232
- * Recursively transforms object types for form inputs.
233
- *
234
- * - **Primitives** (string, number, boolean): optional → `type | undefined`
235
- * - **Arrays**: optional → `type[] | undefined`
236
- * - **Built-in objects** (Date, RegExp, etc.): optional and nullable → `type | null | undefined`
237
- * - **Plain objects**: optional, nullable, and recursively transformed `DeepType | null | undefined`
238
- *
239
- * This ensures `useWatch` returns correct types at any nesting depth,
240
- * since form fields are `undefined` until values are set.
232
+ * Brand symbol for marking objects that should receive recursive partial transformation.
233
+ * @internal
234
+ */
235
+ declare const FormInputBrand: unique symbol;
236
+ /**
237
+ * Branded type to mark objects that should have their direct fields made partial.
238
+ * Use with {@link partialFields} helper to mark specific schema objects.
241
239
  *
242
240
  * @example
243
241
  * ```typescript
244
- * type User = { name: string; tags: string[]; profile: { bio: string } };
245
- * type FormInput = DeepPartialWithNullableObjects<User>;
246
- * // Evaluates to: { name?: string; tags?: string[]; profile?: { bio?: string } | null; }
242
+ * // Objects marked with PartialFields will have their direct fields made optional
243
+ * type MarkedObject = PartialFields<{ name: string; age: number }>;
247
244
  * ```
245
+ */
246
+ type PartialFields<T> = T & {
247
+ readonly [FormInputBrand]?: typeof FormInputBrand;
248
+ };
249
+ /**
250
+ * Helper type to safely get the brand property from a type.
251
+ * Returns `unknown` for non-branded types, `typeof FormInputBrand | undefined` for branded types.
252
+ * @internal
253
+ */
254
+ type GetPartialFieldsBrand<T> = (T & {
255
+ [FormInputBrand]?: unknown;
256
+ })[typeof FormInputBrand];
257
+ /**
258
+ * Helper type to check if a type has the PartialFields brand.
259
+ * Uses bidirectional extends check to distinguish branded from non-branded types.
260
+ * @internal
261
+ */
262
+ type IsPartialFieldsBranded<T> = GetPartialFieldsBrand<T> extends typeof FormInputBrand | undefined ? typeof FormInputBrand extends GetPartialFieldsBrand<T> ? true : false : false;
263
+ /**
264
+ * Helper type to extract the inner type from a PartialFields branded type.
265
+ * @internal
266
+ */
267
+ type ExtractPartialFieldsInner<T> = T extends PartialFields<infer U> ? U : never;
268
+ /**
269
+ * Helper function to mark a Zod schema so its direct fields become partial.
270
+ *
271
+ * By default, nested objects in form inputs keep their fields strict (only the object
272
+ * itself becomes nullable). Use this helper to opt-in specific objects to have their
273
+ * direct fields also become optional.
274
+ *
275
+ * **Note:** This only affects the direct fields of the marked object. Nested objects
276
+ * within it will still stay strict unless they are also wrapped with `partialFields()`.
277
+ *
278
+ * **Use cases:**
279
+ * - Form input fields that users fill in manually (should be partial)
280
+ * - Objects from selectors/dropdowns should NOT use this (keep strict)
248
281
  *
249
282
  * @example
250
- * Type inference with useZodForm
251
283
  * ```typescript
252
- * const schema = z.object({ name: z.string(), age: z.number() });
253
- * const form = useZodForm({ schema }); // ✅ Works without defaultValues
254
- * const name = form.watch('name'); // ✅ Correctly typed as string | undefined
284
+ * import { partialFields } from '@zod-utils/react-hook-form';
285
+ *
286
+ * const schema = z.object({
287
+ * price: z.number(),
288
+ * // User fills in these fields - opt-in to partial
289
+ * detail: partialFields(z.object({
290
+ * hotel: z.string(),
291
+ * nights: z.number(),
292
+ * })),
293
+ * // Selected from dropdown - stays strict
294
+ * agent: z.object({
295
+ * name: z.string(),
296
+ * fee: z.number(),
297
+ * }),
298
+ * });
299
+ *
300
+ * // Result with PartialWithNullableObjects:
301
+ * // detail.hotel → string | undefined (partial - user input)
302
+ * // detail.nights → number | undefined (partial - user input)
303
+ * // agent.name → string (strict! - from selector)
304
+ * // agent.fee → number (strict! - from selector)
255
305
  * ```
306
+ */
307
+ declare function partialFields<T extends z.ZodType>(schema: T): z.ZodType<PartialFields<z.infer<T>>, PartialFields<z.input<T>>>;
308
+ /**
309
+ * Transforms object types for form inputs with selective recursion.
310
+ *
311
+ * **Default behavior (non-recursive):**
312
+ * - **Primitives** (string, number, boolean): optional → `type | undefined`
313
+ * - **Arrays**: optional → `type[] | undefined`
314
+ * - **Built-in objects** (Date, RegExp, etc.): optional and nullable → `type | null | undefined`
315
+ * - **Plain objects**: optional and nullable, but nested fields stay **strict** → `{ strictField: type } | null | undefined`
316
+ *
317
+ * **Opt-in recursive behavior:**
318
+ * - Objects marked with {@link partialFields} will have their nested fields recursively transformed
319
+ *
320
+ * This ensures objects from selectors/dropdowns keep strict types for their fields,
321
+ * while form input fields can be partially filled.
256
322
  *
257
323
  * @example
258
- * Nested fields are also correctly typed
259
324
  * ```typescript
260
- * const schema = z.object({ profile: z.object({ bio: z.string() }) });
261
- * const form = useZodForm({ schema });
262
- * const bio = form.watch('profile.bio'); // ✅ Correctly typed as string | undefined
325
+ * import { partialFields } from '@zod-utils/react-hook-form';
326
+ * import { z } from 'zod';
327
+ *
328
+ * const schema = z.object({
329
+ * price: z.number(),
330
+ * detail: partialFields(z.object({ hotel: z.string(), nights: z.number() })),
331
+ * agent: z.object({ name: z.string(), fee: z.number() }),
332
+ * });
333
+ *
334
+ * type FormInput = PartialWithNullableObjects<z.infer<typeof schema>>;
335
+ * // {
336
+ * // price?: number;
337
+ * // detail?: { hotel?: string; nights?: number } | null; // Recursive!
338
+ * // agent?: { name: string; fee: number } | null; // Strict nested!
339
+ * // }
263
340
  * ```
264
341
  */
265
- type DeepPartialWithNullableObjects<T> = {
266
- [K in keyof T]?: T[K] extends readonly unknown[] ? T[K] : T[K] extends BuiltInObject ? T[K] | null : T[K] extends object ? Simplify<DeepPartialWithNullableObjects<T[K]>> | null : T[K];
342
+ type PartialWithNullableObjects<T> = {
343
+ [K in keyof T]?: T[K] extends readonly unknown[] ? T[K] : T[K] extends BuiltInObject ? T[K] | null : IsPartialFieldsBranded<T[K]> extends true ? Simplify<PartialWithNullableObjects<ExtractPartialFieldsInner<T[K]>>> | null : T[K] extends object ? T[K] | null : T[K];
267
344
  };
268
345
  /**
269
- * @deprecated Use {@link DeepPartialWithNullableObjects} instead.
270
- * This alias is kept for backward compatibility.
271
- */
272
- type PartialWithNullableObjects<T> = DeepPartialWithNullableObjects<T>;
273
- /**
274
- * Recursively makes all fields optional and nullable.
346
+ * Transforms all fields to be optional and nullable, with selective recursion.
347
+ *
348
+ * Similar to {@link PartialWithNullableObjects} but also adds `| null` to primitives and arrays.
275
349
  *
350
+ * **Default behavior (non-recursive):**
276
351
  * - **Primitives**: optional and nullable → `type | null | undefined`
277
352
  * - **Arrays**: optional and nullable → `type[] | null | undefined`
278
- * - **Built-in objects** (Date, RegExp, etc.): optional and nullable `type | null | undefined`
279
- * - **Plain objects**: optional, nullable, and recursively transformed → `DeepType | null | undefined`
353
+ * - **Plain objects**: optional and nullable, but nested fields stay **strict**
354
+ *
355
+ * **Opt-in recursive behavior:**
356
+ * - Objects marked with {@link partialFields} will have their nested fields recursively transformed
280
357
  *
281
358
  * @example
282
359
  * ```typescript
283
360
  * type User = { name: string; age: number; profile: { bio: string } };
284
- * type FormInput = DeepPartialWithAllNullables<User>;
285
- * // { name?: string | null; age?: number | null; profile?: { bio?: string | null } | null; }
361
+ * type FormInput = PartialWithAllNullables<User>;
362
+ * // { name?: string | null; age?: number | null; profile?: { bio: string } | null; }
363
+ * // Note: profile.bio stays strict (string, not string | null | undefined)
286
364
  * ```
287
365
  */
288
- type DeepPartialWithAllNullables<T> = {
289
- [K in keyof T]?: T[K] extends readonly unknown[] ? T[K] | null : T[K] extends BuiltInObject ? T[K] | null : T[K] extends object ? Simplify<DeepPartialWithAllNullables<T[K]>> | null : T[K] | null;
366
+ type PartialWithAllNullables<T> = {
367
+ [K in keyof T]?: T[K] extends readonly unknown[] ? T[K] | null : T[K] extends BuiltInObject ? T[K] | null : IsPartialFieldsBranded<T[K]> extends true ? Simplify<PartialWithAllNullables<ExtractPartialFieldsInner<T[K]>>> | null : T[K] extends object ? T[K] | null : T[K] | null;
290
368
  };
291
- /**
292
- * @deprecated Use {@link DeepPartialWithAllNullables} instead.
293
- * This alias is kept for backward compatibility.
294
- */
295
- type PartialWithAllNullables<T> = DeepPartialWithAllNullables<T>;
296
369
 
297
370
  /**
298
371
  * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.
299
372
  *
300
373
  * This hook eliminates the TypeScript friction between React Hook Form's nullable field values
301
374
  * and Zod's strict output types. It uses a two-type schema pattern where:
302
- * - **Input type** (`DeepPartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing
375
+ * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing
303
376
  * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)
304
377
  *
305
378
  * **Key Benefits:**
@@ -310,7 +383,7 @@ type PartialWithAllNullables<T> = DeepPartialWithAllNullables<T>;
310
383
  *
311
384
  * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)
312
385
  * @template TOutput - The Zod schema output type (extends FieldValues)
313
- * @template TFormInput - The form input type (defaults to DeepPartialWithNullableObjects<TInput>)
386
+ * @template TFormInput - The form input type (defaults to PartialWithNullableObjects<TInput>)
314
387
  * @template TDefaultValues - The type of default values (inferred from usage for better type safety)
315
388
  *
316
389
  * @param options - Configuration object
@@ -455,12 +528,12 @@ type PartialWithAllNullables<T> = DeepPartialWithAllNullables<T>;
455
528
  * }
456
529
  * ```
457
530
  *
458
- * @see {@link DeepPartialWithNullableObjects} for the type transformation utility
531
+ * @see {@link PartialWithNullableObjects} for the type transformation utility
459
532
  * @see https://react-hook-form.com/docs/useform for React Hook Form documentation
460
533
  * @see https://zod.dev for Zod schema documentation
461
534
  * @since 0.1.0
462
535
  */
463
- declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValues, TFormInput extends DeepPartialWithAllNullables<TInput> = DeepPartialWithNullableObjects<TInput>, TDefaultValues extends Partial<TFormInput> | undefined = undefined>({ schema, zodResolverOptions, ...formOptions }: {
536
+ declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValues, TFormInput extends PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>, TDefaultValues extends Partial<TFormInput> | undefined = undefined>({ schema, zodResolverOptions, ...formOptions }: {
464
537
  schema: z.ZodType<TOutput, TInput>;
465
538
  defaultValues?: TDefaultValues;
466
539
  zodResolverOptions?: Parameters<typeof zodResolver>[1];
@@ -491,4 +564,4 @@ declare function flattenFieldSelector(params?: {
491
564
  };
492
565
  }): unknown[];
493
566
 
494
- export { type DeepPartialWithAllNullables, type DeepPartialWithNullableObjects, FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type PartialWithAllNullables, type PartialWithNullableObjects, flattenFieldSelector, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
567
+ export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type PartialFields, type PartialWithAllNullables, type PartialWithNullableObjects, flattenFieldSelector, isRequiredField, partialFields, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
package/dist/index.js CHANGED
@@ -89,6 +89,11 @@ function useFieldChecks(params) {
89
89
  return core.getFieldChecks(field);
90
90
  }, [...flattenFieldSelector(params)]);
91
91
  }
92
+
93
+ // src/types.ts
94
+ function partialFields(schema) {
95
+ return schema;
96
+ }
92
97
  var useZodForm = (_a) => {
93
98
  var _b = _a, {
94
99
  schema,
@@ -107,6 +112,7 @@ exports.FormSchemaContext = FormSchemaContext;
107
112
  exports.FormSchemaProvider = FormSchemaProvider;
108
113
  exports.flattenFieldSelector = flattenFieldSelector;
109
114
  exports.isRequiredField = isRequiredField;
115
+ exports.partialFields = partialFields;
110
116
  exports.useExtractFieldFromSchema = useExtractFieldFromSchema;
111
117
  exports.useFieldChecks = useFieldChecks;
112
118
  exports.useFormSchema = useFormSchema;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/context.tsx","../src/use-zod-form.ts"],"names":["createContext","useContext","jsx","useMemo","extractFieldFromSchema","requiresValidInput","getFieldChecks","zodResolver","useForm"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkBO,SAAS,qBAAqB,MAAA,EAIlC;AAtBH,EAAA,IAAA,EAAA,EAAA,EAAA;AAuBE,EAAA,OAAO;AAAA,IACL,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,MAAA;AAAA,IACR,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,IAAA;AAAA,IAAA,CACR,EAAA,GAAA,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,kBAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAuB,GAAA;AAAA,IAAA,CACvB,EAAA,GAAA,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,kBAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAuB;AAAA,GACzB;AACF;AC0BO,IAAM,iBAAA,GAAoBA,oBAMvB,IAAI;AA6BP,SAAS,cAUd,OAAA,EAKyE;AAKzE,EAAA,OAAOC,iBAAW,iBAAiB,CAAA;AAKrC;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EAI6B;AAC3B,EAAA,uBACEC,cAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,mBAUd,MAAA,EASS;AAET,EAAA,OAAOC,cAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,OAAO,gBAAgB,MAAM,CAAA;AAAA,EAC/B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AA2CO,SAAS,gBAUd,MAAA,EAOS;AACT,EAAA,MAAM,KAAA,GAAQC,4BAAuB,MAAM,CAAA;AAE3C,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAOC,wBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,0BAUd,MAAA,EASuB;AAEvB,EAAA,OAAOF,cAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAOC,4BAAuB,MAAM,CAAA;AAAA,EACtC,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AAwBO,SAAS,eAUd,MAAA,EASiB;AAEjB,EAAA,OAAOD,cAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,EAAC;AAAA,IACV;AAEA,IAAA,MAAM,KAAA,GAAQC,4BAAuB,MAAM,CAAA;AAC3C,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAOE,oBAAe,KAAK,CAAA;AAAA,EAC7B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AC3OO,IAAM,UAAA,GAAa,CAMxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAtLF,GAoLE,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":["import type { z } from 'zod';\n\n/**\n * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.\n *\n * This is useful for `useMemo` and `useCallback` dependencies where you want to avoid\n * re-running when object references change but values stay the same.\n *\n * @param params - The FieldSelector containing schema, name, and optional discriminator\n * @returns An array of primitive values suitable for React dependency arrays\n *\n * @example\n * ```tsx\n * const memoizedValue = useMemo(() => {\n * return extractFieldFromSchema(params);\n * }, flattenFieldSelector(params));\n * ```\n */\nexport function flattenFieldSelector(params?: {\n schema?: z.ZodType;\n name?: string;\n discriminator?: { key: unknown; value: unknown };\n}) {\n return [\n params?.schema,\n params?.name,\n params?.discriminator?.key,\n params?.discriminator?.value,\n ];\n}\n","'use client';\n\nimport {\n type DiscriminatorKey,\n type DiscriminatorValue,\n extractFieldFromSchema,\n type FieldSelectorProps,\n getFieldChecks,\n requiresValidInput,\n type SchemaAndDiscriminatorProps,\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';\nimport { flattenFieldSelector } from './utils';\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<SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\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> = SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\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?: SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >,\n): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue> {\n // Type assertion is necessary because React context is created with a generic type,\n // but we want to return a narrower type based on the generic parameters.\n // The caller is responsible for ensuring type safety via the _params argument.\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useContext(FormSchemaContext) as FormSchemaContextValue<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\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}: SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n> & { children: ReactNode }) {\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | undefined,\n): boolean {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params) {\n return false;\n }\n return isRequiredField(params);\n }, [...flattenFieldSelector(params)]);\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): boolean {\n const field = extractFieldFromSchema(params);\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | undefined,\n): z.ZodType | undefined {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params) {\n return undefined;\n }\n return extractFieldFromSchema(params);\n }, [...flattenFieldSelector(params)]);\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | undefined,\n): ZodUnionCheck[] {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params) {\n return [];\n }\n\n const field = extractFieldFromSchema(params);\n if (!field) return [];\n return getFieldChecks(field);\n }, [...flattenFieldSelector(params)]);\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 DeepPartialWithAllNullables,\n DeepPartialWithNullableObjects,\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** (`DeepPartialWithNullableObjects<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 TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TFormInput - The form input type (defaults to DeepPartialWithNullableObjects<TInput>)\n * @template TDefaultValues - The type of default values (inferred from usage for better type safety)\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 DeepPartialWithNullableObjects} 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 DeepPartialWithAllNullables<TInput> = DeepPartialWithNullableObjects<TInput>,\n TDefaultValues extends Partial<TFormInput> | undefined = undefined,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TDefaultValues;\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/utils.ts","../src/context.tsx","../src/types.ts","../src/use-zod-form.ts"],"names":["createContext","useContext","jsx","useMemo","extractFieldFromSchema","requiresValidInput","getFieldChecks","zodResolver","useForm"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkBO,SAAS,qBAAqB,MAAA,EAIlC;AAtBH,EAAA,IAAA,EAAA,EAAA,EAAA;AAuBE,EAAA,OAAO;AAAA,IACL,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,MAAA;AAAA,IACR,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,IAAA;AAAA,IAAA,CACR,EAAA,GAAA,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,kBAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAuB,GAAA;AAAA,IAAA,CACvB,EAAA,GAAA,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,kBAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAuB;AAAA,GACzB;AACF;AC0BO,IAAM,iBAAA,GAAoBA,oBAMvB,IAAI;AA6BP,SAAS,cAUd,OAAA,EAKyE;AAKzE,EAAA,OAAOC,iBAAW,iBAAiB,CAAA;AAKrC;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EAI6B;AAC3B,EAAA,uBACEC,cAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,mBAUd,MAAA,EASS;AAET,EAAA,OAAOC,cAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,OAAO,gBAAgB,MAAM,CAAA;AAAA,EAC/B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AA2CO,SAAS,gBAUd,MAAA,EAOS;AACT,EAAA,MAAM,KAAA,GAAQC,4BAAuB,MAAM,CAAA;AAE3C,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAOC,wBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,0BAUd,MAAA,EASuB;AAEvB,EAAA,OAAOF,cAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAOC,4BAAuB,MAAM,CAAA;AAAA,EACtC,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AAwBO,SAAS,eAUd,MAAA,EASiB;AAEjB,EAAA,OAAOD,cAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,EAAC;AAAA,IACV;AAEA,IAAA,MAAM,KAAA,GAAQC,4BAAuB,MAAM,CAAA;AAC3C,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAOE,oBAAe,KAAK,CAAA;AAAA,EAC7B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;;;AC9SO,SAAS,cACd,MAAA,EACiE;AAEjE,EAAA,OAAO,MAAA;AAIT;AC2DO,IAAM,UAAA,GAAa,CAMxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAtLF,GAoLE,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":["import type { z } from 'zod';\n\n/**\n * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.\n *\n * This is useful for `useMemo` and `useCallback` dependencies where you want to avoid\n * re-running when object references change but values stay the same.\n *\n * @param params - The FieldSelector containing schema, name, and optional discriminator\n * @returns An array of primitive values suitable for React dependency arrays\n *\n * @example\n * ```tsx\n * const memoizedValue = useMemo(() => {\n * return extractFieldFromSchema(params);\n * }, flattenFieldSelector(params));\n * ```\n */\nexport function flattenFieldSelector(params?: {\n schema?: z.ZodType;\n name?: string;\n discriminator?: { key: unknown; value: unknown };\n}) {\n return [\n params?.schema,\n params?.name,\n params?.discriminator?.key,\n params?.discriminator?.value,\n ];\n}\n","'use client';\n\nimport {\n type DiscriminatorKey,\n type DiscriminatorValue,\n extractFieldFromSchema,\n type FieldSelectorProps,\n getFieldChecks,\n requiresValidInput,\n type SchemaAndDiscriminatorProps,\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';\nimport { flattenFieldSelector } from './utils';\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<SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\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> = SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\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?: SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >,\n): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue> {\n // Type assertion is necessary because React context is created with a generic type,\n // but we want to return a narrower type based on the generic parameters.\n // The caller is responsible for ensuring type safety via the _params argument.\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useContext(FormSchemaContext) as FormSchemaContextValue<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\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}: SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n> & { children: ReactNode }) {\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | undefined,\n): boolean {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params) {\n return false;\n }\n return isRequiredField(params);\n }, [...flattenFieldSelector(params)]);\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): boolean {\n const field = extractFieldFromSchema(params);\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | undefined,\n): z.ZodType | undefined {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params) {\n return undefined;\n }\n return extractFieldFromSchema(params);\n }, [...flattenFieldSelector(params)]);\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | undefined,\n): ZodUnionCheck[] {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params) {\n return [];\n }\n\n const field = extractFieldFromSchema(params);\n if (!field) return [];\n return getFieldChecks(field);\n }, [...flattenFieldSelector(params)]);\n}\n","import type { Simplify } from '@zod-utils/core';\nimport type { z } from 'zod';\n\n/**\n * Built-in types that should not be recursively transformed.\n * These are treated as leaf values (like primitives) and only get `| null` added.\n * @internal\n */\ntype BuiltInObject =\n | Date\n | RegExp\n | Map<unknown, unknown>\n | Set<unknown>\n | WeakMap<object, unknown>\n | WeakSet<object>\n | Promise<unknown>\n | Error;\n\n/**\n * Brand symbol for marking objects that should receive recursive partial transformation.\n * @internal\n */\ndeclare const FormInputBrand: unique symbol;\n\n/**\n * Branded type to mark objects that should have their direct fields made partial.\n * Use with {@link partialFields} helper to mark specific schema objects.\n *\n * @example\n * ```typescript\n * // Objects marked with PartialFields will have their direct fields made optional\n * type MarkedObject = PartialFields<{ name: string; age: number }>;\n * ```\n */\nexport type PartialFields<T> = T & {\n readonly [FormInputBrand]?: typeof FormInputBrand;\n};\n\n/**\n * Helper type to safely get the brand property from a type.\n * Returns `unknown` for non-branded types, `typeof FormInputBrand | undefined` for branded types.\n * @internal\n */\ntype GetPartialFieldsBrand<T> = (T & {\n [FormInputBrand]?: unknown;\n})[typeof FormInputBrand];\n\n/**\n * Helper type to check if a type has the PartialFields brand.\n * Uses bidirectional extends check to distinguish branded from non-branded types.\n * @internal\n */\ntype IsPartialFieldsBranded<T> = GetPartialFieldsBrand<T> extends\n | typeof FormInputBrand\n | undefined\n ? typeof FormInputBrand extends GetPartialFieldsBrand<T>\n ? true\n : false\n : false;\n\n/**\n * Helper type to extract the inner type from a PartialFields branded type.\n * @internal\n */\ntype ExtractPartialFieldsInner<T> = T extends PartialFields<infer U>\n ? U\n : never;\n\n/**\n * Helper function to mark a Zod schema so its direct fields become partial.\n *\n * By default, nested objects in form inputs keep their fields strict (only the object\n * itself becomes nullable). Use this helper to opt-in specific objects to have their\n * direct fields also become optional.\n *\n * **Note:** This only affects the direct fields of the marked object. Nested objects\n * within it will still stay strict unless they are also wrapped with `partialFields()`.\n *\n * **Use cases:**\n * - Form input fields that users fill in manually (should be partial)\n * - Objects from selectors/dropdowns should NOT use this (keep strict)\n *\n * @example\n * ```typescript\n * import { partialFields } from '@zod-utils/react-hook-form';\n *\n * const schema = z.object({\n * price: z.number(),\n * // User fills in these fields - opt-in to partial\n * detail: partialFields(z.object({\n * hotel: z.string(),\n * nights: z.number(),\n * })),\n * // Selected from dropdown - stays strict\n * agent: z.object({\n * name: z.string(),\n * fee: z.number(),\n * }),\n * });\n *\n * // Result with PartialWithNullableObjects:\n * // detail.hotel → string | undefined (partial - user input)\n * // detail.nights → number | undefined (partial - user input)\n * // agent.name → string (strict! - from selector)\n * // agent.fee → number (strict! - from selector)\n * ```\n */\nexport function partialFields<T extends z.ZodType>(\n schema: T,\n): z.ZodType<PartialFields<z.infer<T>>, PartialFields<z.input<T>>> {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return schema as z.ZodType<\n PartialFields<z.infer<T>>,\n PartialFields<z.input<T>>\n >;\n}\n\n/**\n * Transforms object types for form inputs with selective recursion.\n *\n * **Default behavior (non-recursive):**\n * - **Primitives** (string, number, boolean): optional → `type | undefined`\n * - **Arrays**: optional → `type[] | undefined`\n * - **Built-in objects** (Date, RegExp, etc.): optional and nullable → `type | null | undefined`\n * - **Plain objects**: optional and nullable, but nested fields stay **strict** → `{ strictField: type } | null | undefined`\n *\n * **Opt-in recursive behavior:**\n * - Objects marked with {@link partialFields} will have their nested fields recursively transformed\n *\n * This ensures objects from selectors/dropdowns keep strict types for their fields,\n * while form input fields can be partially filled.\n *\n * @example\n * ```typescript\n * import { partialFields } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * price: z.number(),\n * detail: partialFields(z.object({ hotel: z.string(), nights: z.number() })),\n * agent: z.object({ name: z.string(), fee: z.number() }),\n * });\n *\n * type FormInput = PartialWithNullableObjects<z.infer<typeof schema>>;\n * // {\n * // price?: number;\n * // detail?: { hotel?: string; nights?: number } | null; // Recursive!\n * // agent?: { name: string; fee: number } | null; // Strict nested!\n * // }\n * ```\n */\nexport type PartialWithNullableObjects<T> = {\n [K in keyof T]?: T[K] extends readonly unknown[]\n ? T[K] // Arrays: just optional (via ?), no null, no recursion\n : T[K] extends BuiltInObject\n ? T[K] | null // Built-in objects: optional + nullable, no recursion\n : IsPartialFieldsBranded<T[K]> extends true\n ? Simplify<\n PartialWithNullableObjects<ExtractPartialFieldsInner<T[K]>>\n > | null // FormInput marked: recurse + null + optional\n : T[K] extends object\n ? T[K] | null // Plain objects: optional + nullable, NO recursion (strict nested)\n : T[K]; // Primitives: just optional (via ?)\n};\n\n/**\n * Transforms all fields to be optional and nullable, with selective recursion.\n *\n * Similar to {@link PartialWithNullableObjects} but also adds `| null` to primitives and arrays.\n *\n * **Default behavior (non-recursive):**\n * - **Primitives**: optional and nullable → `type | null | undefined`\n * - **Arrays**: optional and nullable → `type[] | null | undefined`\n * - **Plain objects**: optional and nullable, but nested fields stay **strict**\n *\n * **Opt-in recursive behavior:**\n * - Objects marked with {@link partialFields} will have their nested fields recursively transformed\n *\n * @example\n * ```typescript\n * type User = { name: string; age: number; profile: { bio: string } };\n * type FormInput = PartialWithAllNullables<User>;\n * // { name?: string | null; age?: number | null; profile?: { bio: string } | null; }\n * // Note: profile.bio stays strict (string, not string | null | undefined)\n * ```\n */\nexport type PartialWithAllNullables<T> = {\n [K in keyof T]?: T[K] extends readonly unknown[]\n ? T[K] | null // Arrays: optional + nullable, no recursion\n : T[K] extends BuiltInObject\n ? T[K] | null // Built-in objects: optional + nullable, no recursion\n : IsPartialFieldsBranded<T[K]> extends true\n ? Simplify<\n PartialWithAllNullables<ExtractPartialFieldsInner<T[K]>>\n > | null // FormInput marked: recurse + null + optional\n : T[K] extends object\n ? T[K] | null // Plain objects: optional + nullable, NO recursion (strict nested)\n : T[K] | null; // Primitives: optional + nullable\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 TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TFormInput - The form input type (defaults to PartialWithNullableObjects<TInput>)\n * @template TDefaultValues - The type of default values (inferred from usage for better type safety)\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 TDefaultValues extends Partial<TFormInput> | undefined = undefined,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TDefaultValues;\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
@@ -88,6 +88,11 @@ function useFieldChecks(params) {
88
88
  return getFieldChecks(field);
89
89
  }, [...flattenFieldSelector(params)]);
90
90
  }
91
+
92
+ // src/types.ts
93
+ function partialFields(schema) {
94
+ return schema;
95
+ }
91
96
  var useZodForm = (_a) => {
92
97
  var _b = _a, {
93
98
  schema,
@@ -102,6 +107,6 @@ var useZodForm = (_a) => {
102
107
  }, formOptions));
103
108
  };
104
109
 
105
- export { FormSchemaContext, FormSchemaProvider, flattenFieldSelector, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
110
+ export { FormSchemaContext, FormSchemaProvider, flattenFieldSelector, isRequiredField, partialFields, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
106
111
  //# sourceMappingURL=index.mjs.map
107
112
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/context.tsx","../src/use-zod-form.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkBO,SAAS,qBAAqB,MAAA,EAIlC;AAtBH,EAAA,IAAA,EAAA,EAAA,EAAA;AAuBE,EAAA,OAAO;AAAA,IACL,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,MAAA;AAAA,IACR,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,IAAA;AAAA,IAAA,CACR,EAAA,GAAA,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,kBAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAuB,GAAA;AAAA,IAAA,CACvB,EAAA,GAAA,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,kBAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAuB;AAAA,GACzB;AACF;AC0BO,IAAM,iBAAA,GAAoB,cAMvB,IAAI;AA6BP,SAAS,cAUd,OAAA,EAKyE;AAKzE,EAAA,OAAO,WAAW,iBAAiB,CAAA;AAKrC;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EAI6B;AAC3B,EAAA,uBACE,GAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,mBAUd,MAAA,EASS;AAET,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,OAAO,gBAAgB,MAAM,CAAA;AAAA,EAC/B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AA2CO,SAAS,gBAUd,MAAA,EAOS;AACT,EAAA,MAAM,KAAA,GAAQ,uBAAuB,MAAM,CAAA;AAE3C,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,mBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,0BAUd,MAAA,EASuB;AAEvB,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAO,uBAAuB,MAAM,CAAA;AAAA,EACtC,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AAwBO,SAAS,eAUd,MAAA,EASiB;AAEjB,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,EAAC;AAAA,IACV;AAEA,IAAA,MAAM,KAAA,GAAQ,uBAAuB,MAAM,CAAA;AAC3C,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAO,eAAe,KAAK,CAAA;AAAA,EAC7B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AC3OO,IAAM,UAAA,GAAa,CAMxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAtLF,GAoLE,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 type { z } from 'zod';\n\n/**\n * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.\n *\n * This is useful for `useMemo` and `useCallback` dependencies where you want to avoid\n * re-running when object references change but values stay the same.\n *\n * @param params - The FieldSelector containing schema, name, and optional discriminator\n * @returns An array of primitive values suitable for React dependency arrays\n *\n * @example\n * ```tsx\n * const memoizedValue = useMemo(() => {\n * return extractFieldFromSchema(params);\n * }, flattenFieldSelector(params));\n * ```\n */\nexport function flattenFieldSelector(params?: {\n schema?: z.ZodType;\n name?: string;\n discriminator?: { key: unknown; value: unknown };\n}) {\n return [\n params?.schema,\n params?.name,\n params?.discriminator?.key,\n params?.discriminator?.value,\n ];\n}\n","'use client';\n\nimport {\n type DiscriminatorKey,\n type DiscriminatorValue,\n extractFieldFromSchema,\n type FieldSelectorProps,\n getFieldChecks,\n requiresValidInput,\n type SchemaAndDiscriminatorProps,\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';\nimport { flattenFieldSelector } from './utils';\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<SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\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> = SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\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?: SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >,\n): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue> {\n // Type assertion is necessary because React context is created with a generic type,\n // but we want to return a narrower type based on the generic parameters.\n // The caller is responsible for ensuring type safety via the _params argument.\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useContext(FormSchemaContext) as FormSchemaContextValue<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\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}: SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n> & { children: ReactNode }) {\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | undefined,\n): boolean {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params) {\n return false;\n }\n return isRequiredField(params);\n }, [...flattenFieldSelector(params)]);\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): boolean {\n const field = extractFieldFromSchema(params);\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | undefined,\n): z.ZodType | undefined {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params) {\n return undefined;\n }\n return extractFieldFromSchema(params);\n }, [...flattenFieldSelector(params)]);\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | undefined,\n): ZodUnionCheck[] {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params) {\n return [];\n }\n\n const field = extractFieldFromSchema(params);\n if (!field) return [];\n return getFieldChecks(field);\n }, [...flattenFieldSelector(params)]);\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 DeepPartialWithAllNullables,\n DeepPartialWithNullableObjects,\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** (`DeepPartialWithNullableObjects<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 TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TFormInput - The form input type (defaults to DeepPartialWithNullableObjects<TInput>)\n * @template TDefaultValues - The type of default values (inferred from usage for better type safety)\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 DeepPartialWithNullableObjects} 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 DeepPartialWithAllNullables<TInput> = DeepPartialWithNullableObjects<TInput>,\n TDefaultValues extends Partial<TFormInput> | undefined = undefined,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TDefaultValues;\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/utils.ts","../src/context.tsx","../src/types.ts","../src/use-zod-form.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkBO,SAAS,qBAAqB,MAAA,EAIlC;AAtBH,EAAA,IAAA,EAAA,EAAA,EAAA;AAuBE,EAAA,OAAO;AAAA,IACL,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,MAAA;AAAA,IACR,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,IAAA;AAAA,IAAA,CACR,EAAA,GAAA,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,kBAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAuB,GAAA;AAAA,IAAA,CACvB,EAAA,GAAA,MAAA,IAAA,IAAA,GAAA,MAAA,GAAA,MAAA,CAAQ,kBAAR,IAAA,GAAA,MAAA,GAAA,EAAA,CAAuB;AAAA,GACzB;AACF;AC0BO,IAAM,iBAAA,GAAoB,cAMvB,IAAI;AA6BP,SAAS,cAUd,OAAA,EAKyE;AAKzE,EAAA,OAAO,WAAW,iBAAiB,CAAA;AAKrC;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EAI6B;AAC3B,EAAA,uBACE,GAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,mBAUd,MAAA,EASS;AAET,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,KAAA;AAAA,IACT;AACA,IAAA,OAAO,gBAAgB,MAAM,CAAA;AAAA,EAC/B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AA2CO,SAAS,gBAUd,MAAA,EAOS;AACT,EAAA,MAAM,KAAA,GAAQ,uBAAuB,MAAM,CAAA;AAE3C,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,mBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,0BAUd,MAAA,EASuB;AAEvB,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,MAAA;AAAA,IACT;AACA,IAAA,OAAO,uBAAuB,MAAM,CAAA;AAAA,EACtC,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AAwBO,SAAS,eAUd,MAAA,EASiB;AAEjB,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,EAAQ;AACX,MAAA,OAAO,EAAC;AAAA,IACV;AAEA,IAAA,MAAM,KAAA,GAAQ,uBAAuB,MAAM,CAAA;AAC3C,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAO,eAAe,KAAK,CAAA;AAAA,EAC7B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;;;AC9SO,SAAS,cACd,MAAA,EACiE;AAEjE,EAAA,OAAO,MAAA;AAIT;AC2DO,IAAM,UAAA,GAAa,CAMxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAtLF,GAoLE,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 type { z } from 'zod';\n\n/**\n * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.\n *\n * This is useful for `useMemo` and `useCallback` dependencies where you want to avoid\n * re-running when object references change but values stay the same.\n *\n * @param params - The FieldSelector containing schema, name, and optional discriminator\n * @returns An array of primitive values suitable for React dependency arrays\n *\n * @example\n * ```tsx\n * const memoizedValue = useMemo(() => {\n * return extractFieldFromSchema(params);\n * }, flattenFieldSelector(params));\n * ```\n */\nexport function flattenFieldSelector(params?: {\n schema?: z.ZodType;\n name?: string;\n discriminator?: { key: unknown; value: unknown };\n}) {\n return [\n params?.schema,\n params?.name,\n params?.discriminator?.key,\n params?.discriminator?.value,\n ];\n}\n","'use client';\n\nimport {\n type DiscriminatorKey,\n type DiscriminatorValue,\n extractFieldFromSchema,\n type FieldSelectorProps,\n getFieldChecks,\n requiresValidInput,\n type SchemaAndDiscriminatorProps,\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';\nimport { flattenFieldSelector } from './utils';\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<SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\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> = SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\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?: SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >,\n): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue> {\n // Type assertion is necessary because React context is created with a generic type,\n // but we want to return a narrower type based on the generic parameters.\n // The caller is responsible for ensuring type safety via the _params argument.\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useContext(FormSchemaContext) as FormSchemaContextValue<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\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}: SchemaAndDiscriminatorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n> & { children: ReactNode }) {\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | undefined,\n): boolean {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params) {\n return false;\n }\n return isRequiredField(params);\n }, [...flattenFieldSelector(params)]);\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): boolean {\n const field = extractFieldFromSchema(params);\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | undefined,\n): z.ZodType | undefined {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params) {\n return undefined;\n }\n return extractFieldFromSchema(params);\n }, [...flattenFieldSelector(params)]);\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 TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelectorProps<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | undefined,\n): ZodUnionCheck[] {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params) {\n return [];\n }\n\n const field = extractFieldFromSchema(params);\n if (!field) return [];\n return getFieldChecks(field);\n }, [...flattenFieldSelector(params)]);\n}\n","import type { Simplify } from '@zod-utils/core';\nimport type { z } from 'zod';\n\n/**\n * Built-in types that should not be recursively transformed.\n * These are treated as leaf values (like primitives) and only get `| null` added.\n * @internal\n */\ntype BuiltInObject =\n | Date\n | RegExp\n | Map<unknown, unknown>\n | Set<unknown>\n | WeakMap<object, unknown>\n | WeakSet<object>\n | Promise<unknown>\n | Error;\n\n/**\n * Brand symbol for marking objects that should receive recursive partial transformation.\n * @internal\n */\ndeclare const FormInputBrand: unique symbol;\n\n/**\n * Branded type to mark objects that should have their direct fields made partial.\n * Use with {@link partialFields} helper to mark specific schema objects.\n *\n * @example\n * ```typescript\n * // Objects marked with PartialFields will have their direct fields made optional\n * type MarkedObject = PartialFields<{ name: string; age: number }>;\n * ```\n */\nexport type PartialFields<T> = T & {\n readonly [FormInputBrand]?: typeof FormInputBrand;\n};\n\n/**\n * Helper type to safely get the brand property from a type.\n * Returns `unknown` for non-branded types, `typeof FormInputBrand | undefined` for branded types.\n * @internal\n */\ntype GetPartialFieldsBrand<T> = (T & {\n [FormInputBrand]?: unknown;\n})[typeof FormInputBrand];\n\n/**\n * Helper type to check if a type has the PartialFields brand.\n * Uses bidirectional extends check to distinguish branded from non-branded types.\n * @internal\n */\ntype IsPartialFieldsBranded<T> = GetPartialFieldsBrand<T> extends\n | typeof FormInputBrand\n | undefined\n ? typeof FormInputBrand extends GetPartialFieldsBrand<T>\n ? true\n : false\n : false;\n\n/**\n * Helper type to extract the inner type from a PartialFields branded type.\n * @internal\n */\ntype ExtractPartialFieldsInner<T> = T extends PartialFields<infer U>\n ? U\n : never;\n\n/**\n * Helper function to mark a Zod schema so its direct fields become partial.\n *\n * By default, nested objects in form inputs keep their fields strict (only the object\n * itself becomes nullable). Use this helper to opt-in specific objects to have their\n * direct fields also become optional.\n *\n * **Note:** This only affects the direct fields of the marked object. Nested objects\n * within it will still stay strict unless they are also wrapped with `partialFields()`.\n *\n * **Use cases:**\n * - Form input fields that users fill in manually (should be partial)\n * - Objects from selectors/dropdowns should NOT use this (keep strict)\n *\n * @example\n * ```typescript\n * import { partialFields } from '@zod-utils/react-hook-form';\n *\n * const schema = z.object({\n * price: z.number(),\n * // User fills in these fields - opt-in to partial\n * detail: partialFields(z.object({\n * hotel: z.string(),\n * nights: z.number(),\n * })),\n * // Selected from dropdown - stays strict\n * agent: z.object({\n * name: z.string(),\n * fee: z.number(),\n * }),\n * });\n *\n * // Result with PartialWithNullableObjects:\n * // detail.hotel → string | undefined (partial - user input)\n * // detail.nights → number | undefined (partial - user input)\n * // agent.name → string (strict! - from selector)\n * // agent.fee → number (strict! - from selector)\n * ```\n */\nexport function partialFields<T extends z.ZodType>(\n schema: T,\n): z.ZodType<PartialFields<z.infer<T>>, PartialFields<z.input<T>>> {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return schema as z.ZodType<\n PartialFields<z.infer<T>>,\n PartialFields<z.input<T>>\n >;\n}\n\n/**\n * Transforms object types for form inputs with selective recursion.\n *\n * **Default behavior (non-recursive):**\n * - **Primitives** (string, number, boolean): optional → `type | undefined`\n * - **Arrays**: optional → `type[] | undefined`\n * - **Built-in objects** (Date, RegExp, etc.): optional and nullable → `type | null | undefined`\n * - **Plain objects**: optional and nullable, but nested fields stay **strict** → `{ strictField: type } | null | undefined`\n *\n * **Opt-in recursive behavior:**\n * - Objects marked with {@link partialFields} will have their nested fields recursively transformed\n *\n * This ensures objects from selectors/dropdowns keep strict types for their fields,\n * while form input fields can be partially filled.\n *\n * @example\n * ```typescript\n * import { partialFields } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * price: z.number(),\n * detail: partialFields(z.object({ hotel: z.string(), nights: z.number() })),\n * agent: z.object({ name: z.string(), fee: z.number() }),\n * });\n *\n * type FormInput = PartialWithNullableObjects<z.infer<typeof schema>>;\n * // {\n * // price?: number;\n * // detail?: { hotel?: string; nights?: number } | null; // Recursive!\n * // agent?: { name: string; fee: number } | null; // Strict nested!\n * // }\n * ```\n */\nexport type PartialWithNullableObjects<T> = {\n [K in keyof T]?: T[K] extends readonly unknown[]\n ? T[K] // Arrays: just optional (via ?), no null, no recursion\n : T[K] extends BuiltInObject\n ? T[K] | null // Built-in objects: optional + nullable, no recursion\n : IsPartialFieldsBranded<T[K]> extends true\n ? Simplify<\n PartialWithNullableObjects<ExtractPartialFieldsInner<T[K]>>\n > | null // FormInput marked: recurse + null + optional\n : T[K] extends object\n ? T[K] | null // Plain objects: optional + nullable, NO recursion (strict nested)\n : T[K]; // Primitives: just optional (via ?)\n};\n\n/**\n * Transforms all fields to be optional and nullable, with selective recursion.\n *\n * Similar to {@link PartialWithNullableObjects} but also adds `| null` to primitives and arrays.\n *\n * **Default behavior (non-recursive):**\n * - **Primitives**: optional and nullable → `type | null | undefined`\n * - **Arrays**: optional and nullable → `type[] | null | undefined`\n * - **Plain objects**: optional and nullable, but nested fields stay **strict**\n *\n * **Opt-in recursive behavior:**\n * - Objects marked with {@link partialFields} will have their nested fields recursively transformed\n *\n * @example\n * ```typescript\n * type User = { name: string; age: number; profile: { bio: string } };\n * type FormInput = PartialWithAllNullables<User>;\n * // { name?: string | null; age?: number | null; profile?: { bio: string } | null; }\n * // Note: profile.bio stays strict (string, not string | null | undefined)\n * ```\n */\nexport type PartialWithAllNullables<T> = {\n [K in keyof T]?: T[K] extends readonly unknown[]\n ? T[K] | null // Arrays: optional + nullable, no recursion\n : T[K] extends BuiltInObject\n ? T[K] | null // Built-in objects: optional + nullable, no recursion\n : IsPartialFieldsBranded<T[K]> extends true\n ? Simplify<\n PartialWithAllNullables<ExtractPartialFieldsInner<T[K]>>\n > | null // FormInput marked: recurse + null + optional\n : T[K] extends object\n ? T[K] | null // Plain objects: optional + nullable, NO recursion (strict nested)\n : T[K] | null; // Primitives: optional + nullable\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 TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TFormInput - The form input type (defaults to PartialWithNullableObjects<TInput>)\n * @template TDefaultValues - The type of default values (inferred from usage for better type safety)\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 TDefaultValues extends Partial<TFormInput> | undefined = undefined,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TDefaultValues;\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": "6.2.0",
3
+ "version": "7.0.1",
4
4
  "description": "React Hook Form integration and utilities for Zod schemas",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",