@zod-utils/react-hook-form 5.0.0 → 6.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
@@ -343,10 +343,9 @@ function FieldComponent() {
343
343
  }
344
344
  ```
345
345
 
346
- ### `useIsRequiredField({ schema?, name?, discriminator? })`
346
+ ### `useIsRequiredField({ schema, name, discriminator? })`
347
347
 
348
348
  Hook to check if a field requires valid input (shows validation errors on submit).
349
- Returns `false` if schema or name is not provided, making it safe to use in conditional contexts.
350
349
 
351
350
  ```tsx
352
351
  import { useIsRequiredField } from "@zod-utils/react-hook-form";
@@ -451,18 +450,19 @@ import {
451
450
  useFieldChecks,
452
451
 
453
452
  // Form field utilities
454
- toFormFieldSelector,
455
453
  flattenFieldSelector,
456
454
 
457
455
  // Type utilities
458
456
  type PartialWithNullableObjects,
459
457
  type PartialWithAllNullables,
460
- type Discriminator,
458
+ type DiscriminatorProps,
461
459
  type DiscriminatorKey,
462
460
  type DiscriminatorValue,
463
- type InferredFieldValues,
464
- type ValidFieldPaths,
465
- type FormFieldSelector,
461
+ type SchemaProps,
462
+ type SchemaAndDiscriminatorProps,
463
+ type NameProps,
464
+ type NameAndDiscriminatorProps,
465
+ type FieldSelectorProps,
466
466
  } from "@zod-utils/react-hook-form";
467
467
  ```
468
468
 
@@ -530,127 +530,6 @@ Use this when all fields need to accept `null`, not just objects/arrays.
530
530
 
531
531
  ---
532
532
 
533
- #### `ValidFieldPaths<TSchema, TDiscriminatorKey?, TDiscriminatorValue?, TFieldValues?, TFilterType?, TStrict?>`
534
-
535
- Type-safe field paths for React Hook Form with optional type filtering and discriminated union support.
536
-
537
- ```typescript
538
- import type { ValidFieldPaths } from "@zod-utils/react-hook-form";
539
- import { z } from "zod";
540
-
541
- const schema = z.object({
542
- name: z.string(),
543
- age: z.number(),
544
- score: z.number().optional(),
545
- active: z.boolean(),
546
- });
547
-
548
- // Get all field paths
549
- type AllPaths = ValidFieldPaths<typeof schema>;
550
- // "name" | "age" | "score" | "active"
551
-
552
- // Filter by type - only number field paths (use TFilterType parameter)
553
- type NumberPaths = ValidFieldPaths<typeof schema, never, never, never, number>;
554
- // "age" | "score"
555
-
556
- // Only accept boolean field paths
557
- type BooleanPaths = ValidFieldPaths<typeof schema, never, never, never, boolean>;
558
- // "active"
559
- ```
560
-
561
- **Use case - Type-safe form field components:**
562
-
563
- ```tsx
564
- // NumberFormField only accepts paths to number fields
565
- function NumberFormField<
566
- TSchema extends z.ZodType,
567
- TPath extends ValidFieldPaths<TSchema, never, never, never, number>
568
- >({ schema, name }: { schema: TSchema; name: TPath }) {
569
- // name is guaranteed to be a path to a number field
570
- return <input type="number" name={name} />;
571
- }
572
-
573
- // ✅ Works - 'age' is a number field
574
- <NumberFormField schema={schema} name="age" />
575
-
576
- // ❌ TypeScript error - 'name' is a string field, not number
577
- <NumberFormField schema={schema} name="name" />
578
- ```
579
-
580
- **With discriminated unions:**
581
-
582
- ```typescript
583
- const formSchema = z.discriminatedUnion("mode", [
584
- z.object({ mode: z.literal("create"), name: z.string(), priority: z.number() }),
585
- z.object({ mode: z.literal("edit"), id: z.number(), rating: z.number() }),
586
- ]);
587
-
588
- // Number paths for 'edit' variant
589
- type EditNumberPaths = ValidFieldPaths<
590
- typeof formSchema,
591
- "mode",
592
- "edit",
593
- never,
594
- number
595
- >;
596
- // "id" | "rating"
597
- ```
598
-
599
- ---
600
-
601
- #### `FormFieldSelector<TSchema, TPath, TDiscriminatorKey?, TDiscriminatorValue?, TFieldValues?, TFilterType?, TStrict?>`
602
-
603
- Type-safe field selector for React Hook Form with discriminated union support. Useful for building form field components with type-safe props.
604
-
605
- ```typescript
606
- import type { FormFieldSelector } from "@zod-utils/react-hook-form";
607
- import { z } from "zod";
608
-
609
- const schema = z.object({
610
- name: z.string(),
611
- age: z.number(),
612
- });
613
-
614
- // Basic usage - no discriminator needed
615
- type NameSelector = FormFieldSelector<typeof schema, "name">;
616
- // { schema: typeof schema; name: "name" }
617
-
618
- // With discriminated union
619
- const formSchema = z.discriminatedUnion("mode", [
620
- z.object({ mode: z.literal("create"), name: z.string() }),
621
- z.object({ mode: z.literal("edit"), id: z.number() }),
622
- ]);
623
-
624
- type CreateNameSelector = FormFieldSelector<typeof formSchema, "name", "mode", "create">;
625
- // { schema: typeof formSchema; name: "name"; discriminator: { key: "mode"; value: "create" } }
626
- ```
627
-
628
- ---
629
-
630
- #### `toFormFieldSelector(props)`
631
-
632
- Extracts a `FormFieldSelector` from props containing schema, name, and optional discriminator. Encapsulates type assertion so callers don't need eslint-disable.
633
-
634
- ```typescript
635
- import { toFormFieldSelector } from "@zod-utils/react-hook-form";
636
-
637
- // In a form field component
638
- function InputFormField<TSchema extends z.ZodType>({ schema, name, discriminator, ...inputProps }) {
639
- const selectorProps = toFormFieldSelector({ schema, name, discriminator });
640
- // Use selectorProps for extractFieldFromSchema, useIsRequiredField, etc.
641
- }
642
-
643
- // In a factory function
644
- function createInputFormField<TSchema extends z.ZodType>({ schema }: { schema: TSchema }) {
645
- return function BoundInputFormField({ name, discriminator, ...rest }) {
646
- const selectorProps = toFormFieldSelector({ schema, name, discriminator });
647
- return <InputFormField {...selectorProps} {...rest} />;
648
- };
649
- }
650
- ```
651
-
652
- ---
653
-
654
533
  #### `flattenFieldSelector(params)`
655
534
 
656
535
  Flattens a `FieldSelector` into an array of primitive values for use in React dependency arrays.
@@ -769,65 +648,6 @@ form.register("nonexistent.field");
769
648
 
770
649
  ---
771
650
 
772
- ## Migration Guide
773
-
774
- ### Migrating to v3.0.0
775
-
776
- #### `ValidFieldPathsOfType` removed → Use `ValidFieldPaths` with type filtering
777
-
778
- The `ValidFieldPathsOfType` type has been removed and consolidated into `ValidFieldPaths` with a new `TFilterType` parameter.
779
-
780
- **Before (v2.x):**
781
- ```typescript
782
- import type { ValidFieldPathsOfType } from "@zod-utils/react-hook-form";
783
-
784
- // Get number field paths
785
- type NumberPaths = ValidFieldPathsOfType<typeof schema, number>;
786
-
787
- // With discriminated union
788
- type EditNumberPaths = ValidFieldPathsOfType<typeof schema, number, "mode", "edit">;
789
- ```
790
-
791
- **After (v3.x):**
792
- ```typescript
793
- import type { ValidFieldPaths } from "@zod-utils/react-hook-form";
794
-
795
- // Get number field paths - TFilterType is now the 5th parameter
796
- type NumberPaths = ValidFieldPaths<typeof schema, never, never, never, number>;
797
-
798
- // With discriminated union
799
- type EditNumberPaths = ValidFieldPaths<typeof schema, "mode", "edit", never, number>;
800
- ```
801
-
802
- **Parameter order change:**
803
- - v2.x: `ValidFieldPathsOfType<TSchema, TValueConstraint, TDiscriminatorKey, TDiscriminatorValue, TFieldValues>`
804
- - v3.x: `ValidFieldPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>`
805
-
806
- ### Migrating to v4.0.0
807
-
808
- #### `mergeFormFieldSelectorProps` renamed → Use `toFormFieldSelector`
809
-
810
- The function has been renamed and simplified to accept a single props object.
811
-
812
- **Before (v3.x):**
813
- ```typescript
814
- import { mergeFormFieldSelectorProps } from "@zod-utils/react-hook-form";
815
-
816
- const selectorProps = mergeFormFieldSelectorProps(
817
- { schema },
818
- { name, discriminator }
819
- );
820
- ```
821
-
822
- **After (v4.x):**
823
- ```typescript
824
- import { toFormFieldSelector } from "@zod-utils/react-hook-form";
825
-
826
- const selectorProps = toFormFieldSelector({ schema, name, discriminator });
827
- ```
828
-
829
- ---
830
-
831
651
  ## License
832
652
 
833
653
  MIT
package/dist/index.d.mts CHANGED
@@ -1,22 +1,21 @@
1
- import { DiscriminatorKey, DiscriminatorValue, FieldSelector, ValidPaths, Discriminator, ExtractZodByPath, ZodUnionCheck, Simplify, InnerFieldSelector } from '@zod-utils/core';
1
+ import { DiscriminatorKey, DiscriminatorValue, SchemaAndDiscriminatorProps, FieldSelectorProps, ZodUnionCheck, Simplify } from '@zod-utils/core';
2
2
  export * from '@zod-utils/core';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { Context, ReactNode } from 'react';
5
5
  import { z } from 'zod';
6
- import { SomeType } from 'zod/v4/core';
7
6
  import * as react_hook_form from 'react-hook-form';
8
- import { FieldValues, Path, UseFormProps } from 'react-hook-form';
7
+ import { FieldValues, UseFormProps } from 'react-hook-form';
9
8
  import { zodResolver } from '@hookform/resolvers/zod';
10
9
 
11
10
  /**
12
11
  * Type for the FormSchemaContext with full generic support.
13
12
  * @internal
14
13
  */
15
- type FormSchemaContextType<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true> = Context<Omit<FieldSelector<TSchema, ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, 'name'> | null>;
14
+ type FormSchemaContextType<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = Context<SchemaAndDiscriminatorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue> | null>;
16
15
  /**
17
16
  * Context value type for FormSchemaContext.
18
17
  */
19
- type FormSchemaContextValue<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true> = Omit<FieldSelector<TSchema, ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, 'name'> | null;
18
+ type FormSchemaContextValue<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = SchemaAndDiscriminatorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue> | null;
20
19
  /**
21
20
  * React Context for providing Zod schema to form components.
22
21
  *
@@ -57,19 +56,7 @@ declare const FormSchemaContext: Context<{
57
56
  * }
58
57
  * ```
59
58
  */
60
- declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true>(_params?: TSchema extends z.ZodPipe<infer In, SomeType> ? In extends z.ZodDiscriminatedUnion ? {
61
- schema: TSchema;
62
- discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
63
- } : {
64
- schema: TSchema;
65
- discriminator?: undefined;
66
- } : TSchema extends z.ZodDiscriminatedUnion ? {
67
- schema: TSchema;
68
- discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
69
- } : {
70
- schema: TSchema;
71
- discriminator?: undefined;
72
- }): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>;
59
+ declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = DiscriminatorValue<TSchema, TDiscriminatorKey>>(_params?: SchemaAndDiscriminatorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue>): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
73
60
  /**
74
61
  * Provider component that makes Zod schema available to all child components.
75
62
  *
@@ -105,19 +92,7 @@ declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscrimin
105
92
  * </FormSchemaProvider>
106
93
  * ```
107
94
  */
108
- declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, discriminator, children, }: (TSchema extends z.ZodPipe<infer In, SomeType> ? In extends z.ZodDiscriminatedUnion ? {
109
- schema: TSchema;
110
- discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
111
- } : {
112
- schema: TSchema;
113
- discriminator?: undefined;
114
- } : TSchema extends z.ZodDiscriminatedUnion ? {
115
- schema: TSchema;
116
- discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
117
- } : {
118
- schema: TSchema;
119
- discriminator?: undefined;
120
- }) & {
95
+ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, discriminator, children, }: SchemaAndDiscriminatorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue> & {
121
96
  children: ReactNode;
122
97
  }): react_jsx_runtime.JSX.Element;
123
98
  /**
@@ -125,8 +100,8 @@ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey
125
100
  *
126
101
  * Memoized - only recalculates when schema, name, or discriminator changes.
127
102
  *
128
- * @param params - Schema, name, and optional discriminator (schema and name are optional)
129
- * @returns true if the field requires valid input, false if it doesn't or if schema/name is not provided
103
+ * @param params - Schema, name, and optional discriminator
104
+ * @returns true if the field requires valid input, false otherwise
130
105
  *
131
106
  * @example
132
107
  * ```tsx
@@ -142,11 +117,7 @@ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey
142
117
  * }
143
118
  * ```
144
119
  */
145
- declare function useIsRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict> | {
146
- schema?: undefined;
147
- name?: undefined;
148
- discriminator?: undefined;
149
- }): boolean;
120
+ declare function useIsRequiredField<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelectorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict> | undefined): boolean;
150
121
  /**
151
122
  * Determines if a field requires valid input (will show validation errors on empty/invalid input).
152
123
  *
@@ -188,7 +159,7 @@ declare function useIsRequiredField<TSchema extends z.ZodType, TPath extends Val
188
159
  * }); // true
189
160
  * ```
190
161
  */
191
- declare function isRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>): boolean;
162
+ declare function isRequiredField<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelectorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>): boolean;
192
163
  /**
193
164
  * React hook to extract a field's Zod schema from a parent schema.
194
165
  *
@@ -226,7 +197,7 @@ declare function isRequiredField<TSchema extends z.ZodType, TPath extends ValidP
226
197
  * // Returns z.string() schema
227
198
  * ```
228
199
  */
229
- declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined;
200
+ declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelectorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict> | undefined): z.ZodType | undefined;
230
201
  /**
231
202
  * Hook to get validation checks from a field's Zod schema.
232
203
  *
@@ -249,7 +220,7 @@ declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TPath exte
249
220
  * }
250
221
  * ```
251
222
  */
252
- declare function useFieldChecks<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>): ZodUnionCheck[];
223
+ declare function useFieldChecks<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelectorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict> | undefined): ZodUnionCheck[];
253
224
 
254
225
  /**
255
226
  * Helper type that adds `null` to object-type fields only (excludes arrays).
@@ -298,115 +269,6 @@ type PartialWithNullableObjects<T> = Simplify<Partial<AddNullToObjects<T>>>;
298
269
  type PartialWithAllNullables<T> = {
299
270
  [K in keyof T]?: T[K] | null;
300
271
  };
301
- /**
302
- * Infers field values from a Zod schema compatible with React Hook Form's FieldValues.
303
- *
304
- * @example
305
- * ```typescript
306
- * const schema = z.object({ name: z.string() });
307
- * type Values = InferredFieldValues<typeof schema>;
308
- * // { name: string } & FieldValues
309
- * ```
310
- */
311
- type InferredFieldValues<TSchema extends z.ZodType> = z.input<TSchema> & FieldValues;
312
- /**
313
- * Type-safe field paths for React Hook Form with optional filtering and discriminated union support.
314
- *
315
- * Combines `ValidPaths` from `@zod-utils/core` with React Hook Form's `Path` type
316
- * for full compatibility with form field APIs.
317
- *
318
- * @template TSchema - The Zod schema type
319
- * @template TDiscriminatorKey - The discriminator key (for discriminated unions)
320
- * @template TDiscriminatorValue - The discriminator value (for discriminated unions)
321
- * @template TFilterType - The value type to filter by (default: unknown = no filtering)
322
- * @template TStrict - Whether to use strict type matching (default: true)
323
- * @template TFieldValues - The form field values type (inferred from schema)
324
- *
325
- * @example
326
- * Basic usage - all field paths
327
- * ```typescript
328
- * const schema = z.object({
329
- * name: z.string(),
330
- * age: z.number(),
331
- * });
332
- *
333
- * type AllFields = ValidFieldPaths<typeof schema>;
334
- * // "name" | "age"
335
- * ```
336
- *
337
- * @example
338
- * Filter by type - string fields only
339
- * ```typescript
340
- * const schema = z.object({
341
- * name: z.string(),
342
- * age: z.number(),
343
- * email: z.string().optional(),
344
- * });
345
- *
346
- * type StringFields = ValidFieldPaths<typeof schema, never, never, string>;
347
- * // "name" | "email"
348
- * ```
349
- *
350
- * @example
351
- * With discriminated union
352
- * ```typescript
353
- * const schema = z.discriminatedUnion('mode', [
354
- * z.object({ mode: z.literal('create'), name: z.string() }),
355
- * z.object({ mode: z.literal('edit'), id: z.number() }),
356
- * ]);
357
- *
358
- * type CreateFields = ValidFieldPaths<typeof schema, 'mode', 'create'>;
359
- * // "mode" | "name"
360
- *
361
- * type EditNumberFields = ValidFieldPaths<typeof schema, 'mode', 'edit', number>;
362
- * // "id"
363
- * ```
364
- *
365
- * @see {@link ValidPaths} for the base type without react-hook-form Path intersection
366
- */
367
- type ValidFieldPaths<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>, TFilterType = unknown, TStrict extends boolean = true> = ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict> & Path<TFieldValues>;
368
- /**
369
- * Type-safe field selector for React Hook Form with discriminated union support.
370
- *
371
- * Returns an object type containing:
372
- * - `schema` - The Zod schema
373
- * - `name` - The field path (type-safe)
374
- * - `discriminator` - Required for discriminated unions, contains `key` and `value`
375
- *
376
- * @template TSchema - The Zod schema type
377
- * @template TPath - The field path
378
- * @template TDiscriminatorKey - The discriminator key (for discriminated unions)
379
- * @template TDiscriminatorValue - The discriminator value (for discriminated unions)
380
- * @template TFilterType - The value type to filter by (default: unknown = no filtering)
381
- * @template TStrict - Whether to use strict type matching (default: true)
382
- *
383
- * @example
384
- * Basic usage with regular schema
385
- * ```typescript
386
- * const schema = z.object({
387
- * name: z.string(),
388
- * age: z.number(),
389
- * });
390
- *
391
- * type NameSelector = FormFieldSelector<typeof schema, 'name'>;
392
- * // { schema: typeof schema; name: 'name' }
393
- * ```
394
- *
395
- * @example
396
- * With discriminated union
397
- * ```typescript
398
- * const schema = z.discriminatedUnion('mode', [
399
- * z.object({ mode: z.literal('create'), name: z.string() }),
400
- * z.object({ mode: z.literal('edit'), id: z.number() }),
401
- * ]);
402
- *
403
- * type CreateNameSelector = FormFieldSelector<typeof schema, 'name', 'mode', 'create'>;
404
- * // { schema: typeof schema; name: 'name'; discriminator: { key: 'mode'; value: 'create' } }
405
- * ```
406
- *
407
- * @see {@link FieldSelector} from `@zod-utils/core` for the base type
408
- */
409
- type FormFieldSelector<TSchema extends z.ZodType, TPath extends ValidFieldPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>, TFilterType = unknown, TStrict extends boolean = true> = InnerFieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>;
410
272
 
411
273
  /**
412
274
  * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.
@@ -580,26 +442,6 @@ declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValu
580
442
  zodResolverOptions?: Parameters<typeof zodResolver>[1];
581
443
  } & Omit<UseFormProps<TFormInput, unknown, TOutput>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<TFormInput, unknown, TOutput>;
582
444
 
583
- /**
584
- * Extracts a FormFieldSelector from props containing schema, name, and optional discriminator.
585
- * Encapsulates type assertion so callers don't need eslint-disable.
586
- *
587
- * @param props - Object containing schema, name, and optional discriminator
588
- * @returns Properly typed FormFieldSelector
589
- *
590
- * @example
591
- * ```typescript
592
- * const selectorProps = toFormFieldSelector<TSchema, TPath, ...>(props);
593
- * ```
594
- */
595
- declare function toFormFieldSelector<TSchema extends z.ZodType, TPath extends ValidFieldPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>, TFilterType = unknown, TStrict extends boolean = true>(props: {
596
- schema: z.ZodType;
597
- name: string;
598
- discriminator?: {
599
- key: string;
600
- value: unknown;
601
- };
602
- }): FormFieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>;
603
445
  /**
604
446
  * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.
605
447
  *
@@ -616,10 +458,13 @@ declare function toFormFieldSelector<TSchema extends z.ZodType, TPath extends Va
616
458
  * }, flattenFieldSelector(params));
617
459
  * ```
618
460
  */
619
- declare function flattenFieldSelector<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict> | {
620
- schema?: undefined;
621
- name?: undefined;
622
- discriminator?: undefined;
623
- }): any[];
461
+ declare function flattenFieldSelector(params?: {
462
+ schema?: z.ZodType;
463
+ name?: string;
464
+ discriminator?: {
465
+ key: unknown;
466
+ value: unknown;
467
+ };
468
+ }): unknown[];
624
469
 
625
- export { type FormFieldSelector, FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, flattenFieldSelector, isRequiredField, toFormFieldSelector, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
470
+ export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type PartialWithAllNullables, type PartialWithNullableObjects, flattenFieldSelector, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
package/dist/index.d.ts CHANGED
@@ -1,22 +1,21 @@
1
- import { DiscriminatorKey, DiscriminatorValue, FieldSelector, ValidPaths, Discriminator, ExtractZodByPath, ZodUnionCheck, Simplify, InnerFieldSelector } from '@zod-utils/core';
1
+ import { DiscriminatorKey, DiscriminatorValue, SchemaAndDiscriminatorProps, FieldSelectorProps, ZodUnionCheck, Simplify } from '@zod-utils/core';
2
2
  export * from '@zod-utils/core';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { Context, ReactNode } from 'react';
5
5
  import { z } from 'zod';
6
- import { SomeType } from 'zod/v4/core';
7
6
  import * as react_hook_form from 'react-hook-form';
8
- import { FieldValues, Path, UseFormProps } from 'react-hook-form';
7
+ import { FieldValues, UseFormProps } from 'react-hook-form';
9
8
  import { zodResolver } from '@hookform/resolvers/zod';
10
9
 
11
10
  /**
12
11
  * Type for the FormSchemaContext with full generic support.
13
12
  * @internal
14
13
  */
15
- type FormSchemaContextType<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true> = Context<Omit<FieldSelector<TSchema, ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, 'name'> | null>;
14
+ type FormSchemaContextType<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = Context<SchemaAndDiscriminatorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue> | null>;
16
15
  /**
17
16
  * Context value type for FormSchemaContext.
18
17
  */
19
- type FormSchemaContextValue<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true> = Omit<FieldSelector<TSchema, ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, 'name'> | null;
18
+ type FormSchemaContextValue<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = SchemaAndDiscriminatorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue> | null;
20
19
  /**
21
20
  * React Context for providing Zod schema to form components.
22
21
  *
@@ -57,19 +56,7 @@ declare const FormSchemaContext: Context<{
57
56
  * }
58
57
  * ```
59
58
  */
60
- declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true>(_params?: TSchema extends z.ZodPipe<infer In, SomeType> ? In extends z.ZodDiscriminatedUnion ? {
61
- schema: TSchema;
62
- discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
63
- } : {
64
- schema: TSchema;
65
- discriminator?: undefined;
66
- } : TSchema extends z.ZodDiscriminatedUnion ? {
67
- schema: TSchema;
68
- discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
69
- } : {
70
- schema: TSchema;
71
- discriminator?: undefined;
72
- }): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>;
59
+ declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = DiscriminatorValue<TSchema, TDiscriminatorKey>>(_params?: SchemaAndDiscriminatorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue>): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
73
60
  /**
74
61
  * Provider component that makes Zod schema available to all child components.
75
62
  *
@@ -105,19 +92,7 @@ declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscrimin
105
92
  * </FormSchemaProvider>
106
93
  * ```
107
94
  */
108
- declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, discriminator, children, }: (TSchema extends z.ZodPipe<infer In, SomeType> ? In extends z.ZodDiscriminatedUnion ? {
109
- schema: TSchema;
110
- discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
111
- } : {
112
- schema: TSchema;
113
- discriminator?: undefined;
114
- } : TSchema extends z.ZodDiscriminatedUnion ? {
115
- schema: TSchema;
116
- discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
117
- } : {
118
- schema: TSchema;
119
- discriminator?: undefined;
120
- }) & {
95
+ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, discriminator, children, }: SchemaAndDiscriminatorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue> & {
121
96
  children: ReactNode;
122
97
  }): react_jsx_runtime.JSX.Element;
123
98
  /**
@@ -125,8 +100,8 @@ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey
125
100
  *
126
101
  * Memoized - only recalculates when schema, name, or discriminator changes.
127
102
  *
128
- * @param params - Schema, name, and optional discriminator (schema and name are optional)
129
- * @returns true if the field requires valid input, false if it doesn't or if schema/name is not provided
103
+ * @param params - Schema, name, and optional discriminator
104
+ * @returns true if the field requires valid input, false otherwise
130
105
  *
131
106
  * @example
132
107
  * ```tsx
@@ -142,11 +117,7 @@ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey
142
117
  * }
143
118
  * ```
144
119
  */
145
- declare function useIsRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict> | {
146
- schema?: undefined;
147
- name?: undefined;
148
- discriminator?: undefined;
149
- }): boolean;
120
+ declare function useIsRequiredField<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelectorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict> | undefined): boolean;
150
121
  /**
151
122
  * Determines if a field requires valid input (will show validation errors on empty/invalid input).
152
123
  *
@@ -188,7 +159,7 @@ declare function useIsRequiredField<TSchema extends z.ZodType, TPath extends Val
188
159
  * }); // true
189
160
  * ```
190
161
  */
191
- declare function isRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>): boolean;
162
+ declare function isRequiredField<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelectorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>): boolean;
192
163
  /**
193
164
  * React hook to extract a field's Zod schema from a parent schema.
194
165
  *
@@ -226,7 +197,7 @@ declare function isRequiredField<TSchema extends z.ZodType, TPath extends ValidP
226
197
  * // Returns z.string() schema
227
198
  * ```
228
199
  */
229
- declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined;
200
+ declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelectorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict> | undefined): z.ZodType | undefined;
230
201
  /**
231
202
  * Hook to get validation checks from a field's Zod schema.
232
203
  *
@@ -249,7 +220,7 @@ declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TPath exte
249
220
  * }
250
221
  * ```
251
222
  */
252
- declare function useFieldChecks<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>): ZodUnionCheck[];
223
+ declare function useFieldChecks<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelectorProps<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict> | undefined): ZodUnionCheck[];
253
224
 
254
225
  /**
255
226
  * Helper type that adds `null` to object-type fields only (excludes arrays).
@@ -298,115 +269,6 @@ type PartialWithNullableObjects<T> = Simplify<Partial<AddNullToObjects<T>>>;
298
269
  type PartialWithAllNullables<T> = {
299
270
  [K in keyof T]?: T[K] | null;
300
271
  };
301
- /**
302
- * Infers field values from a Zod schema compatible with React Hook Form's FieldValues.
303
- *
304
- * @example
305
- * ```typescript
306
- * const schema = z.object({ name: z.string() });
307
- * type Values = InferredFieldValues<typeof schema>;
308
- * // { name: string } & FieldValues
309
- * ```
310
- */
311
- type InferredFieldValues<TSchema extends z.ZodType> = z.input<TSchema> & FieldValues;
312
- /**
313
- * Type-safe field paths for React Hook Form with optional filtering and discriminated union support.
314
- *
315
- * Combines `ValidPaths` from `@zod-utils/core` with React Hook Form's `Path` type
316
- * for full compatibility with form field APIs.
317
- *
318
- * @template TSchema - The Zod schema type
319
- * @template TDiscriminatorKey - The discriminator key (for discriminated unions)
320
- * @template TDiscriminatorValue - The discriminator value (for discriminated unions)
321
- * @template TFilterType - The value type to filter by (default: unknown = no filtering)
322
- * @template TStrict - Whether to use strict type matching (default: true)
323
- * @template TFieldValues - The form field values type (inferred from schema)
324
- *
325
- * @example
326
- * Basic usage - all field paths
327
- * ```typescript
328
- * const schema = z.object({
329
- * name: z.string(),
330
- * age: z.number(),
331
- * });
332
- *
333
- * type AllFields = ValidFieldPaths<typeof schema>;
334
- * // "name" | "age"
335
- * ```
336
- *
337
- * @example
338
- * Filter by type - string fields only
339
- * ```typescript
340
- * const schema = z.object({
341
- * name: z.string(),
342
- * age: z.number(),
343
- * email: z.string().optional(),
344
- * });
345
- *
346
- * type StringFields = ValidFieldPaths<typeof schema, never, never, string>;
347
- * // "name" | "email"
348
- * ```
349
- *
350
- * @example
351
- * With discriminated union
352
- * ```typescript
353
- * const schema = z.discriminatedUnion('mode', [
354
- * z.object({ mode: z.literal('create'), name: z.string() }),
355
- * z.object({ mode: z.literal('edit'), id: z.number() }),
356
- * ]);
357
- *
358
- * type CreateFields = ValidFieldPaths<typeof schema, 'mode', 'create'>;
359
- * // "mode" | "name"
360
- *
361
- * type EditNumberFields = ValidFieldPaths<typeof schema, 'mode', 'edit', number>;
362
- * // "id"
363
- * ```
364
- *
365
- * @see {@link ValidPaths} for the base type without react-hook-form Path intersection
366
- */
367
- type ValidFieldPaths<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>, TFilterType = unknown, TStrict extends boolean = true> = ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict> & Path<TFieldValues>;
368
- /**
369
- * Type-safe field selector for React Hook Form with discriminated union support.
370
- *
371
- * Returns an object type containing:
372
- * - `schema` - The Zod schema
373
- * - `name` - The field path (type-safe)
374
- * - `discriminator` - Required for discriminated unions, contains `key` and `value`
375
- *
376
- * @template TSchema - The Zod schema type
377
- * @template TPath - The field path
378
- * @template TDiscriminatorKey - The discriminator key (for discriminated unions)
379
- * @template TDiscriminatorValue - The discriminator value (for discriminated unions)
380
- * @template TFilterType - The value type to filter by (default: unknown = no filtering)
381
- * @template TStrict - Whether to use strict type matching (default: true)
382
- *
383
- * @example
384
- * Basic usage with regular schema
385
- * ```typescript
386
- * const schema = z.object({
387
- * name: z.string(),
388
- * age: z.number(),
389
- * });
390
- *
391
- * type NameSelector = FormFieldSelector<typeof schema, 'name'>;
392
- * // { schema: typeof schema; name: 'name' }
393
- * ```
394
- *
395
- * @example
396
- * With discriminated union
397
- * ```typescript
398
- * const schema = z.discriminatedUnion('mode', [
399
- * z.object({ mode: z.literal('create'), name: z.string() }),
400
- * z.object({ mode: z.literal('edit'), id: z.number() }),
401
- * ]);
402
- *
403
- * type CreateNameSelector = FormFieldSelector<typeof schema, 'name', 'mode', 'create'>;
404
- * // { schema: typeof schema; name: 'name'; discriminator: { key: 'mode'; value: 'create' } }
405
- * ```
406
- *
407
- * @see {@link FieldSelector} from `@zod-utils/core` for the base type
408
- */
409
- type FormFieldSelector<TSchema extends z.ZodType, TPath extends ValidFieldPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema> = never, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = never, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>, TFilterType = unknown, TStrict extends boolean = true> = InnerFieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>;
410
272
 
411
273
  /**
412
274
  * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.
@@ -580,26 +442,6 @@ declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValu
580
442
  zodResolverOptions?: Parameters<typeof zodResolver>[1];
581
443
  } & Omit<UseFormProps<TFormInput, unknown, TOutput>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<TFormInput, unknown, TOutput>;
582
444
 
583
- /**
584
- * Extracts a FormFieldSelector from props containing schema, name, and optional discriminator.
585
- * Encapsulates type assertion so callers don't need eslint-disable.
586
- *
587
- * @param props - Object containing schema, name, and optional discriminator
588
- * @returns Properly typed FormFieldSelector
589
- *
590
- * @example
591
- * ```typescript
592
- * const selectorProps = toFormFieldSelector<TSchema, TPath, ...>(props);
593
- * ```
594
- */
595
- declare function toFormFieldSelector<TSchema extends z.ZodType, TPath extends ValidFieldPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>, TFilterType = unknown, TStrict extends boolean = true>(props: {
596
- schema: z.ZodType;
597
- name: string;
598
- discriminator?: {
599
- key: string;
600
- value: unknown;
601
- };
602
- }): FormFieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>;
603
445
  /**
604
446
  * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.
605
447
  *
@@ -616,10 +458,13 @@ declare function toFormFieldSelector<TSchema extends z.ZodType, TPath extends Va
616
458
  * }, flattenFieldSelector(params));
617
459
  * ```
618
460
  */
619
- declare function flattenFieldSelector<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFilterType = unknown, TStrict extends boolean = true>(params: FieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict> | {
620
- schema?: undefined;
621
- name?: undefined;
622
- discriminator?: undefined;
623
- }): any[];
461
+ declare function flattenFieldSelector(params?: {
462
+ schema?: z.ZodType;
463
+ name?: string;
464
+ discriminator?: {
465
+ key: unknown;
466
+ value: unknown;
467
+ };
468
+ }): unknown[];
624
469
 
625
- export { type FormFieldSelector, FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, flattenFieldSelector, isRequiredField, toFormFieldSelector, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
470
+ export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type PartialWithAllNullables, type PartialWithNullableObjects, flattenFieldSelector, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
package/dist/index.js CHANGED
@@ -36,23 +36,18 @@ var __objRest = (source, exclude) => {
36
36
  };
37
37
 
38
38
  // src/utils.ts
39
- function toFormFieldSelector(props) {
40
- const { schema, name, discriminator } = props;
41
- return { schema, name, discriminator };
42
- }
43
39
  function flattenFieldSelector(params) {
44
- const _a = params, { discriminator } = _a, rest = __objRest(_a, ["discriminator"]);
40
+ var _a, _b;
45
41
  return [
46
- ...Object.values(rest),
47
- ...discriminator ? Object.values(discriminator) : []
42
+ params == null ? void 0 : params.schema,
43
+ params == null ? void 0 : params.name,
44
+ (_a = params == null ? void 0 : params.discriminator) == null ? void 0 : _a.key,
45
+ (_b = params == null ? void 0 : params.discriminator) == null ? void 0 : _b.value
48
46
  ];
49
47
  }
50
48
  var FormSchemaContext = react.createContext(null);
51
49
  function useFormSchema(_params) {
52
- return react.useContext(
53
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
54
- FormSchemaContext
55
- );
50
+ return react.useContext(FormSchemaContext);
56
51
  }
57
52
  function FormSchemaProvider({
58
53
  schema,
@@ -63,7 +58,7 @@ function FormSchemaProvider({
63
58
  }
64
59
  function useIsRequiredField(params) {
65
60
  return react.useMemo(() => {
66
- if (!params.schema || !params.name) {
61
+ if (!params) {
67
62
  return false;
68
63
  }
69
64
  return isRequiredField(params);
@@ -77,13 +72,18 @@ function isRequiredField(params) {
77
72
  return core.requiresValidInput(field);
78
73
  }
79
74
  function useExtractFieldFromSchema(params) {
80
- return react.useMemo(
81
- () => core.extractFieldFromSchema(params),
82
- [...flattenFieldSelector(params)]
83
- );
75
+ return react.useMemo(() => {
76
+ if (!params) {
77
+ return void 0;
78
+ }
79
+ return core.extractFieldFromSchema(params);
80
+ }, [...flattenFieldSelector(params)]);
84
81
  }
85
82
  function useFieldChecks(params) {
86
83
  return react.useMemo(() => {
84
+ if (!params) {
85
+ return [];
86
+ }
87
87
  const field = core.extractFieldFromSchema(params);
88
88
  if (!field) return [];
89
89
  return core.getFieldChecks(field);
@@ -107,7 +107,6 @@ exports.FormSchemaContext = FormSchemaContext;
107
107
  exports.FormSchemaProvider = FormSchemaProvider;
108
108
  exports.flattenFieldSelector = flattenFieldSelector;
109
109
  exports.isRequiredField = isRequiredField;
110
- exports.toFormFieldSelector = toFormFieldSelector;
111
110
  exports.useExtractFieldFromSchema = useExtractFieldFromSchema;
112
111
  exports.useFieldChecks = useFieldChecks;
113
112
  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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBO,SAAS,oBAgBd,KAAA,EAYA;AACA,EAAA,MAAM,EAAE,MAAA,EAAQ,IAAA,EAAM,aAAA,EAAc,GAAI,KAAA;AAExC,EAAA,OAAO,EAAE,MAAA,EAAQ,IAAA,EAAM,aAAA,EAAc;AASvC;AAkBO,SAAS,qBAcd,MAAA,EAcA;AACA,EAAA,MAAmC,aAA3B,EAAA,aAAA,EAhHV,GAgHqC,EAAA,EAAT,IAAA,GAAA,SAAA,CAAS,IAAT,CAAlB,eAAA,CAAA,CAAA;AAER,EAAA,OAAO;AAAA,IACL,GAAG,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA;AAAA,IACrB,GAAI,aAAA,GAAgB,MAAA,CAAO,MAAA,CAAO,aAAa,IAAI;AAAC,GACtD;AACF;AChCO,IAAM,iBAAA,GAAoBA,oBAMvB,IAAI;AA6BP,SAAS,cAYd,OAAA,EAiCA;AACA,EAAA,OAAOC,gBAAA;AAAA;AAAA,IAEL;AAAA,GAiBF;AACF;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EA4BG;AACD,EAAA,uBACEC,cAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,mBAcd,MAAA,EAcS;AAET,EAAA,OAAOC,cAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,CAAO,MAAA,IAAU,CAAC,OAAO,IAAA,EAAM;AAClC,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,gBAAgB,MAAM,CAAA;AAAA,EAC/B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AA2CO,SAAS,gBAcd,MAAA,EAQS;AACT,EAAA,MAAM,KAAA,GAAQC,4BAOZ,MAAM,CAAA;AAER,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAOC,wBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,0BAiBd,MAAA,EAQ4D;AAE5D,EAAA,OAAOF,aAAA;AAAA,IACL,MACEC,4BAOE,MAAM,CAAA;AAAA,IACV,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC;AAAA,GAClC;AACF;AAwBO,SAAS,eAcd,MAAA,EAQiB;AAEjB,EAAA,OAAOD,cAAQ,MAAM;AACnB,IAAA,MAAM,KAAA,GAAQC,4BAOZ,MAAM,CAAA;AACR,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;ACnXO,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 {\n DiscriminatorKey,\n DiscriminatorValue,\n FieldSelector,\n ValidPaths,\n} from '@zod-utils/core';\nimport type { z } from 'zod';\nimport type {\n FormFieldSelector,\n InferredFieldValues,\n ValidFieldPaths,\n} from './types';\n\n/**\n * Extracts a FormFieldSelector from props containing schema, name, and optional discriminator.\n * Encapsulates type assertion so callers don't need eslint-disable.\n *\n * @param props - Object containing schema, name, and optional discriminator\n * @returns Properly typed FormFieldSelector\n *\n * @example\n * ```typescript\n * const selectorProps = toFormFieldSelector<TSchema, TPath, ...>(props);\n * ```\n */\nexport function toFormFieldSelector<\n TSchema extends z.ZodType,\n TPath extends ValidFieldPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFieldValues extends\n InferredFieldValues<TSchema> = InferredFieldValues<TSchema>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(props: {\n schema: z.ZodType;\n name: string;\n discriminator?: { key: string; value: unknown };\n}): FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n> {\n const { schema, name, discriminator } = props;\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return { schema, name, discriminator } as FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n >;\n}\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<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | {\n schema?: undefined;\n name?: undefined;\n discriminator?: undefined;\n },\n) {\n const { discriminator, ...rest } = params;\n\n return [\n ...Object.values(rest),\n ...(discriminator ? Object.values(discriminator) : []),\n ];\n}\n","'use client';\n\nimport {\n type Discriminator,\n type DiscriminatorKey,\n type DiscriminatorValue,\n type ExtractZodByPath,\n extractFieldFromSchema,\n type FieldSelector,\n getFieldChecks,\n requiresValidInput,\n type ValidPaths,\n type ZodUnionCheck,\n} from '@zod-utils/core';\nimport {\n type Context,\n createContext,\n type ReactNode,\n useContext,\n useMemo,\n} from 'react';\nimport type { z } from 'zod';\nimport type { SomeType } from 'zod/v4/core';\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 TFilterType = unknown,\n TStrict extends boolean = true,\n> = Context<Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\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 TFilterType = unknown,\n TStrict extends boolean = true,\n> = Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\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 TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n // Parameter used for type inference only, not at runtime\n _params?: TSchema extends z.ZodPipe<infer In, SomeType>\n ? In extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }\n : TSchema extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n },\n): FormSchemaContextValue<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n> {\n return useContext(\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n FormSchemaContext as Context<Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n > | null>,\n );\n}\n\n/**\n * Provider component that makes Zod schema available to all child components.\n *\n * Use this to wrap your form and provide schema context to nested components\n * like field labels and validation indicators.\n *\n * @example\n * Basic usage with ZodObject\n * ```tsx\n * const schema = z.object({\n * name: z.string(),\n * email: z.string().email().optional()\n * });\n *\n * <FormSchemaProvider schema={schema}>\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n *\n * @example\n * Usage with discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() })\n * ]);\n *\n * <FormSchemaProvider\n * schema={schema}\n * discriminator={{ key: 'mode', value: 'create' }}\n * >\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n */\nexport function FormSchemaProvider<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n discriminator,\n children,\n}: (TSchema extends z.ZodPipe<infer In, SomeType>\n ? In extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }\n : TSchema extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }) & {\n children: ReactNode;\n}) {\n return (\n <FormSchemaContext.Provider value={{ schema, discriminator }}>\n {children}\n </FormSchemaContext.Provider>\n );\n}\n\n/**\n * Hook to check if a field requires valid input based on the Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n *\n * @param params - Schema, name, and optional discriminator (schema and name are optional)\n * @returns true if the field requires valid input, false if it doesn't or if schema/name is not provided\n *\n * @example\n * ```tsx\n * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {\n * const isRequired = useIsRequiredField({ schema, name });\n *\n * return (\n * <label>\n * {name}\n * {isRequired && <span className=\"text-red-500\">*</span>}\n * </label>\n * );\n * }\n * ```\n */\nexport function useIsRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | {\n schema?: undefined;\n name?: undefined;\n discriminator?: undefined;\n },\n): boolean {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params.schema || !params.name) {\n return false;\n }\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 TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): boolean {\n const field = extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(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 TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\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: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(\n () =>\n extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params),\n [...flattenFieldSelector(params)],\n );\n}\n\n/**\n * Hook to get validation checks from a field's Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Combines field extraction and check retrieval in one cached operation.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns Array of validation checks (min, max, pattern, etc.) or empty array\n *\n * @example\n * ```tsx\n * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {\n * const checks = useFieldChecks({ schema, name });\n *\n * const maxLength = checks.find(c => c.check === 'max_length');\n * if (maxLength) {\n * return <span>Max {maxLength.maximum} characters</span>;\n * }\n * return null;\n * }\n * ```\n */\nexport function useFieldChecks<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): ZodUnionCheck[] {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n const field = extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(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 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"]}
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 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
@@ -35,23 +35,18 @@ var __objRest = (source, exclude) => {
35
35
  };
36
36
 
37
37
  // src/utils.ts
38
- function toFormFieldSelector(props) {
39
- const { schema, name, discriminator } = props;
40
- return { schema, name, discriminator };
41
- }
42
38
  function flattenFieldSelector(params) {
43
- const _a = params, { discriminator } = _a, rest = __objRest(_a, ["discriminator"]);
39
+ var _a, _b;
44
40
  return [
45
- ...Object.values(rest),
46
- ...discriminator ? Object.values(discriminator) : []
41
+ params == null ? void 0 : params.schema,
42
+ params == null ? void 0 : params.name,
43
+ (_a = params == null ? void 0 : params.discriminator) == null ? void 0 : _a.key,
44
+ (_b = params == null ? void 0 : params.discriminator) == null ? void 0 : _b.value
47
45
  ];
48
46
  }
49
47
  var FormSchemaContext = createContext(null);
50
48
  function useFormSchema(_params) {
51
- return useContext(
52
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
53
- FormSchemaContext
54
- );
49
+ return useContext(FormSchemaContext);
55
50
  }
56
51
  function FormSchemaProvider({
57
52
  schema,
@@ -62,7 +57,7 @@ function FormSchemaProvider({
62
57
  }
63
58
  function useIsRequiredField(params) {
64
59
  return useMemo(() => {
65
- if (!params.schema || !params.name) {
60
+ if (!params) {
66
61
  return false;
67
62
  }
68
63
  return isRequiredField(params);
@@ -76,13 +71,18 @@ function isRequiredField(params) {
76
71
  return requiresValidInput(field);
77
72
  }
78
73
  function useExtractFieldFromSchema(params) {
79
- return useMemo(
80
- () => extractFieldFromSchema(params),
81
- [...flattenFieldSelector(params)]
82
- );
74
+ return useMemo(() => {
75
+ if (!params) {
76
+ return void 0;
77
+ }
78
+ return extractFieldFromSchema(params);
79
+ }, [...flattenFieldSelector(params)]);
83
80
  }
84
81
  function useFieldChecks(params) {
85
82
  return useMemo(() => {
83
+ if (!params) {
84
+ return [];
85
+ }
86
86
  const field = extractFieldFromSchema(params);
87
87
  if (!field) return [];
88
88
  return getFieldChecks(field);
@@ -102,6 +102,6 @@ var useZodForm = (_a) => {
102
102
  }, formOptions));
103
103
  };
104
104
 
105
- export { FormSchemaContext, FormSchemaProvider, flattenFieldSelector, isRequiredField, toFormFieldSelector, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
105
+ export { FormSchemaContext, FormSchemaProvider, flattenFieldSelector, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
106
106
  //# sourceMappingURL=index.mjs.map
107
107
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/context.tsx","../src/use-zod-form.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBO,SAAS,oBAgBd,KAAA,EAYA;AACA,EAAA,MAAM,EAAE,MAAA,EAAQ,IAAA,EAAM,aAAA,EAAc,GAAI,KAAA;AAExC,EAAA,OAAO,EAAE,MAAA,EAAQ,IAAA,EAAM,aAAA,EAAc;AASvC;AAkBO,SAAS,qBAcd,MAAA,EAcA;AACA,EAAA,MAAmC,aAA3B,EAAA,aAAA,EAhHV,GAgHqC,EAAA,EAAT,IAAA,GAAA,SAAA,CAAS,IAAT,CAAlB,eAAA,CAAA,CAAA;AAER,EAAA,OAAO;AAAA,IACL,GAAG,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA;AAAA,IACrB,GAAI,aAAA,GAAgB,MAAA,CAAO,MAAA,CAAO,aAAa,IAAI;AAAC,GACtD;AACF;AChCO,IAAM,iBAAA,GAAoB,cAMvB,IAAI;AA6BP,SAAS,cAYd,OAAA,EAiCA;AACA,EAAA,OAAO,UAAA;AAAA;AAAA,IAEL;AAAA,GAiBF;AACF;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EA4BG;AACD,EAAA,uBACE,GAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,mBAcd,MAAA,EAcS;AAET,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,CAAO,MAAA,IAAU,CAAC,OAAO,IAAA,EAAM;AAClC,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,gBAAgB,MAAM,CAAA;AAAA,EAC/B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AA2CO,SAAS,gBAcd,MAAA,EAQS;AACT,EAAA,MAAM,KAAA,GAAQ,uBAOZ,MAAM,CAAA;AAER,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,mBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,0BAiBd,MAAA,EAQ4D;AAE5D,EAAA,OAAO,OAAA;AAAA,IACL,MACE,uBAOE,MAAM,CAAA;AAAA,IACV,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC;AAAA,GAClC;AACF;AAwBO,SAAS,eAcd,MAAA,EAQiB;AAEjB,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,MAAM,KAAA,GAAQ,uBAOZ,MAAM,CAAA;AACR,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;ACnXO,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 {\n DiscriminatorKey,\n DiscriminatorValue,\n FieldSelector,\n ValidPaths,\n} from '@zod-utils/core';\nimport type { z } from 'zod';\nimport type {\n FormFieldSelector,\n InferredFieldValues,\n ValidFieldPaths,\n} from './types';\n\n/**\n * Extracts a FormFieldSelector from props containing schema, name, and optional discriminator.\n * Encapsulates type assertion so callers don't need eslint-disable.\n *\n * @param props - Object containing schema, name, and optional discriminator\n * @returns Properly typed FormFieldSelector\n *\n * @example\n * ```typescript\n * const selectorProps = toFormFieldSelector<TSchema, TPath, ...>(props);\n * ```\n */\nexport function toFormFieldSelector<\n TSchema extends z.ZodType,\n TPath extends ValidFieldPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFieldValues extends\n InferredFieldValues<TSchema> = InferredFieldValues<TSchema>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(props: {\n schema: z.ZodType;\n name: string;\n discriminator?: { key: string; value: unknown };\n}): FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n> {\n const { schema, name, discriminator } = props;\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return { schema, name, discriminator } as FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n >;\n}\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<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | {\n schema?: undefined;\n name?: undefined;\n discriminator?: undefined;\n },\n) {\n const { discriminator, ...rest } = params;\n\n return [\n ...Object.values(rest),\n ...(discriminator ? Object.values(discriminator) : []),\n ];\n}\n","'use client';\n\nimport {\n type Discriminator,\n type DiscriminatorKey,\n type DiscriminatorValue,\n type ExtractZodByPath,\n extractFieldFromSchema,\n type FieldSelector,\n getFieldChecks,\n requiresValidInput,\n type ValidPaths,\n type ZodUnionCheck,\n} from '@zod-utils/core';\nimport {\n type Context,\n createContext,\n type ReactNode,\n useContext,\n useMemo,\n} from 'react';\nimport type { z } from 'zod';\nimport type { SomeType } from 'zod/v4/core';\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 TFilterType = unknown,\n TStrict extends boolean = true,\n> = Context<Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\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 TFilterType = unknown,\n TStrict extends boolean = true,\n> = Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\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 TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n // Parameter used for type inference only, not at runtime\n _params?: TSchema extends z.ZodPipe<infer In, SomeType>\n ? In extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }\n : TSchema extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n },\n): FormSchemaContextValue<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n> {\n return useContext(\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n FormSchemaContext as Context<Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n > | null>,\n );\n}\n\n/**\n * Provider component that makes Zod schema available to all child components.\n *\n * Use this to wrap your form and provide schema context to nested components\n * like field labels and validation indicators.\n *\n * @example\n * Basic usage with ZodObject\n * ```tsx\n * const schema = z.object({\n * name: z.string(),\n * email: z.string().email().optional()\n * });\n *\n * <FormSchemaProvider schema={schema}>\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n *\n * @example\n * Usage with discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() })\n * ]);\n *\n * <FormSchemaProvider\n * schema={schema}\n * discriminator={{ key: 'mode', value: 'create' }}\n * >\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n */\nexport function FormSchemaProvider<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n discriminator,\n children,\n}: (TSchema extends z.ZodPipe<infer In, SomeType>\n ? In extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }\n : TSchema extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }) & {\n children: ReactNode;\n}) {\n return (\n <FormSchemaContext.Provider value={{ schema, discriminator }}>\n {children}\n </FormSchemaContext.Provider>\n );\n}\n\n/**\n * Hook to check if a field requires valid input based on the Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n *\n * @param params - Schema, name, and optional discriminator (schema and name are optional)\n * @returns true if the field requires valid input, false if it doesn't or if schema/name is not provided\n *\n * @example\n * ```tsx\n * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {\n * const isRequired = useIsRequiredField({ schema, name });\n *\n * return (\n * <label>\n * {name}\n * {isRequired && <span className=\"text-red-500\">*</span>}\n * </label>\n * );\n * }\n * ```\n */\nexport function useIsRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | {\n schema?: undefined;\n name?: undefined;\n discriminator?: undefined;\n },\n): boolean {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params.schema || !params.name) {\n return false;\n }\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 TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): boolean {\n const field = extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(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 TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\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: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(\n () =>\n extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params),\n [...flattenFieldSelector(params)],\n );\n}\n\n/**\n * Hook to get validation checks from a field's Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Combines field extraction and check retrieval in one cached operation.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns Array of validation checks (min, max, pattern, etc.) or empty array\n *\n * @example\n * ```tsx\n * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {\n * const checks = useFieldChecks({ schema, name });\n *\n * const maxLength = checks.find(c => c.check === 'max_length');\n * if (maxLength) {\n * return <span>Max {maxLength.maximum} characters</span>;\n * }\n * return null;\n * }\n * ```\n */\nexport function useFieldChecks<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): ZodUnionCheck[] {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n const field = extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(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 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"]}
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 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": "5.0.0",
3
+ "version": "6.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",