@zod-utils/react-hook-form 2.0.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -450,6 +450,10 @@ import {
450
450
  useExtractFieldFromSchema,
451
451
  useFieldChecks,
452
452
 
453
+ // Form field utilities
454
+ toFormFieldSelector,
455
+ flattenFieldSelector,
456
+
453
457
  // Type utilities
454
458
  type PartialWithNullableObjects,
455
459
  type PartialWithAllNullables,
@@ -458,7 +462,7 @@ import {
458
462
  type DiscriminatorValue,
459
463
  type InferredFieldValues,
460
464
  type ValidFieldPaths,
461
- type ValidFieldPathsOfType,
465
+ type FormFieldSelector,
462
466
  } from "@zod-utils/react-hook-form";
463
467
  ```
464
468
 
@@ -526,12 +530,12 @@ Use this when all fields need to accept `null`, not just objects/arrays.
526
530
 
527
531
  ---
528
532
 
529
- #### `ValidFieldPathsOfType<TSchema, TValueConstraint, TDiscriminatorKey?, TDiscriminatorValue?, TFieldValues?>`
533
+ #### `ValidFieldPaths<TSchema, TDiscriminatorKey?, TDiscriminatorValue?, TFieldValues?, TFilterType?, TStrict?>`
530
534
 
531
- Extracts field paths where the value type matches a constraint. Useful for building type-safe form components that only accept paths of specific types.
535
+ Type-safe field paths for React Hook Form with optional type filtering and discriminated union support.
532
536
 
533
537
  ```typescript
534
- import type { ValidFieldPathsOfType } from "@zod-utils/react-hook-form";
538
+ import type { ValidFieldPaths } from "@zod-utils/react-hook-form";
535
539
  import { z } from "zod";
536
540
 
537
541
  const schema = z.object({
@@ -541,12 +545,16 @@ const schema = z.object({
541
545
  active: z.boolean(),
542
546
  });
543
547
 
544
- // Only accept number field paths
545
- type NumberPaths = ValidFieldPathsOfType<typeof schema, number>;
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>;
546
554
  // "age" | "score"
547
555
 
548
556
  // Only accept boolean field paths
549
- type BooleanPaths = ValidFieldPathsOfType<typeof schema, boolean>;
557
+ type BooleanPaths = ValidFieldPaths<typeof schema, never, never, never, boolean>;
550
558
  // "active"
551
559
  ```
552
560
 
@@ -556,7 +564,7 @@ type BooleanPaths = ValidFieldPathsOfType<typeof schema, boolean>;
556
564
  // NumberFormField only accepts paths to number fields
557
565
  function NumberFormField<
558
566
  TSchema extends z.ZodType,
559
- TPath extends ValidFieldPathsOfType<TSchema, number>
567
+ TPath extends ValidFieldPaths<TSchema, never, never, never, number>
560
568
  >({ schema, name }: { schema: TSchema; name: TPath }) {
561
569
  // name is guaranteed to be a path to a number field
562
570
  return <input type="number" name={name} />;
@@ -578,17 +586,87 @@ const formSchema = z.discriminatedUnion("mode", [
578
586
  ]);
579
587
 
580
588
  // Number paths for 'edit' variant
581
- type EditNumberPaths = ValidFieldPathsOfType<
589
+ type EditNumberPaths = ValidFieldPaths<
582
590
  typeof formSchema,
583
- number,
584
591
  "mode",
585
- "edit"
592
+ "edit",
593
+ never,
594
+ number
586
595
  >;
587
596
  // "id" | "rating"
588
597
  ```
589
598
 
590
599
  ---
591
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
+ #### `flattenFieldSelector(params)`
655
+
656
+ Flattens a `FieldSelector` into an array of primitive values for use in React dependency arrays.
657
+
658
+ ```tsx
659
+ import { flattenFieldSelector, extractFieldFromSchema } from "@zod-utils/react-hook-form";
660
+
661
+ function useFieldSchema(params) {
662
+ return useMemo(() => {
663
+ return extractFieldFromSchema(params);
664
+ }, flattenFieldSelector(params));
665
+ }
666
+ ```
667
+
668
+ ---
669
+
592
670
  ## Complete Example
593
671
 
594
672
  ```typescript
@@ -691,6 +769,65 @@ form.register("nonexistent.field");
691
769
 
692
770
  ---
693
771
 
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
+
694
831
  ## License
695
832
 
696
833
  MIT
package/dist/index.d.mts CHANGED
@@ -1,8 +1,9 @@
1
- import { DiscriminatorKey, DiscriminatorValue, Discriminator, ValidPaths, ExtractZodByPath, ZodUnionCheck, Simplify, ValidPathsOfType } from '@zod-utils/core';
1
+ import { DiscriminatorKey, DiscriminatorValue, FieldSelector, ValidPaths, Discriminator, ExtractZodByPath, ZodUnionCheck, Simplify, InnerFieldSelector } 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';
6
7
  import * as react_hook_form from 'react-hook-form';
7
8
  import { FieldValues, Path, UseFormProps } from 'react-hook-form';
8
9
  import { zodResolver } from '@hookform/resolvers/zod';
@@ -11,17 +12,11 @@ import { zodResolver } from '@hookform/resolvers/zod';
11
12
  * Type for the FormSchemaContext with full generic support.
12
13
  * @internal
13
14
  */
14
- type FormSchemaContextType<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = Context<{
15
- schema: TSchema;
16
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
17
- } | null>;
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>;
18
16
  /**
19
17
  * Context value type for FormSchemaContext.
20
18
  */
21
- type FormSchemaContextValue<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = {
22
- schema: TSchema;
23
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
24
- } | null;
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;
25
20
  /**
26
21
  * React Context for providing Zod schema to form components.
27
22
  *
@@ -62,10 +57,19 @@ declare const FormSchemaContext: Context<{
62
57
  * }
63
58
  * ```
64
59
  */
65
- declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = DiscriminatorValue<TSchema, TDiscriminatorKey>>(_params?: {
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 ? {
66
67
  schema: TSchema;
67
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
68
- }): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
68
+ discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
69
+ } : {
70
+ schema: TSchema;
71
+ discriminator?: undefined;
72
+ }): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>;
69
73
  /**
70
74
  * Provider component that makes Zod schema available to all child components.
71
75
  *
@@ -101,9 +105,19 @@ declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscrimin
101
105
  * </FormSchemaProvider>
102
106
  * ```
103
107
  */
104
- declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, discriminator, children, }: {
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
+ } : {
105
118
  schema: TSchema;
106
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
119
+ discriminator?: undefined;
120
+ }) & {
107
121
  children: ReactNode;
108
122
  }): react_jsx_runtime.JSX.Element;
109
123
  /**
@@ -128,10 +142,10 @@ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey
128
142
  * }
129
143
  * ```
130
144
  */
131
- declare function useIsRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
132
- schema?: TSchema;
133
- name?: TPath;
134
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
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;
135
149
  }): boolean;
136
150
  /**
137
151
  * Determines if a field requires valid input (will show validation errors on empty/invalid input).
@@ -174,11 +188,7 @@ declare function useIsRequiredField<TSchema extends z.ZodType, TPath extends Val
174
188
  * }); // true
175
189
  * ```
176
190
  */
177
- declare function isRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
178
- schema: TSchema;
179
- name: TPath;
180
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
181
- }): boolean;
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;
182
192
  /**
183
193
  * React hook to extract a field's Zod schema from a parent schema.
184
194
  *
@@ -216,11 +226,7 @@ declare function isRequiredField<TSchema extends z.ZodType, TPath extends ValidP
216
226
  * // Returns z.string() schema
217
227
  * ```
218
228
  */
219
- declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
220
- schema: TSchema;
221
- name: TPath;
222
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
223
- }): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined;
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;
224
230
  /**
225
231
  * Hook to get validation checks from a field's Zod schema.
226
232
  *
@@ -243,11 +249,7 @@ declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TPath exte
243
249
  * }
244
250
  * ```
245
251
  */
246
- declare function useFieldChecks<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
247
- schema: TSchema;
248
- name: TPath;
249
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
250
- }): ZodUnionCheck[];
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[];
251
253
 
252
254
  /**
253
255
  * Helper type that adds `null` to object-type fields only (excludes arrays).
@@ -308,12 +310,45 @@ type PartialWithAllNullables<T> = {
308
310
  */
309
311
  type InferredFieldValues<TSchema extends z.ZodType> = z.input<TSchema> & FieldValues;
310
312
  /**
311
- * Type-safe field names for a specific discriminator value.
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)
312
324
  *
313
- * Narrows field names to only those that exist for the given discriminator value
314
- * in a discriminated union schema.
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
+ * ```
315
336
  *
316
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
317
352
  * ```typescript
318
353
  * const schema = z.discriminatedUnion('mode', [
319
354
  * z.object({ mode: z.literal('create'), name: z.string() }),
@@ -323,47 +358,38 @@ type InferredFieldValues<TSchema extends z.ZodType> = z.input<TSchema> & FieldVa
323
358
  * type CreateFields = ValidFieldPaths<typeof schema, 'mode', 'create'>;
324
359
  * // "mode" | "name"
325
360
  *
326
- * type EditFields = ValidFieldPaths<typeof schema, 'mode', 'edit'>;
327
- * // "mode" | "id"
361
+ * type EditNumberFields = ValidFieldPaths<typeof schema, 'mode', 'edit', number>;
362
+ * // "id"
328
363
  * ```
364
+ *
365
+ * @see {@link ValidPaths} for the base type without react-hook-form Path intersection
329
366
  */
330
- type ValidFieldPaths<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>> = ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue> & Path<TFieldValues>;
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>;
331
368
  /**
332
- * Type-safe field paths filtered by value type for React Hook Form.
369
+ * Type-safe field selector for React Hook Form with discriminated union support.
333
370
  *
334
- * Combines `ValidPathsOfType` (filters by value type) with React Hook Form's
335
- * `Path` type for full compatibility with form field APIs.
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`
336
375
  *
337
376
  * @template TSchema - The Zod schema type
338
- * @template TValueConstraint - The value type to filter by
377
+ * @template TPath - The field path
339
378
  * @template TDiscriminatorKey - The discriminator key (for discriminated unions)
340
379
  * @template TDiscriminatorValue - The discriminator value (for discriminated unions)
341
- * @template TFieldValues - The form field values type (inferred from schema)
380
+ * @template TFilterType - The value type to filter by (default: unknown = no filtering)
381
+ * @template TStrict - Whether to use strict type matching (default: true)
342
382
  *
343
383
  * @example
344
- * Get all string field paths for form
384
+ * Basic usage with regular schema
345
385
  * ```typescript
346
386
  * const schema = z.object({
347
387
  * name: z.string(),
348
388
  * age: z.number(),
349
- * email: z.string().optional(),
350
- * });
351
- *
352
- * type StringFields = ValidFieldPathsOfType<typeof schema, string>;
353
- * // "name" | "email" - compatible with react-hook-form Path
354
- * ```
355
- *
356
- * @example
357
- * Get all number field paths for form
358
- * ```typescript
359
- * const schema = z.object({
360
- * id: z.number(),
361
- * name: z.string(),
362
- * count: z.number().optional(),
363
389
  * });
364
390
  *
365
- * type NumberFields = ValidFieldPathsOfType<typeof schema, number>;
366
- * // "id" | "count"
391
+ * type NameSelector = FormFieldSelector<typeof schema, 'name'>;
392
+ * // { schema: typeof schema; name: 'name' }
367
393
  * ```
368
394
  *
369
395
  * @example
@@ -374,15 +400,13 @@ type ValidFieldPaths<TSchema extends z.ZodType, TDiscriminatorKey extends Discri
374
400
  * z.object({ mode: z.literal('edit'), id: z.number() }),
375
401
  * ]);
376
402
  *
377
- * type EditNumberFields = ValidFieldPathsOfType<typeof schema, number, 'mode', 'edit'>;
378
- * // "id"
403
+ * type CreateNameSelector = FormFieldSelector<typeof schema, 'name', 'mode', 'create'>;
404
+ * // { schema: typeof schema; name: 'name'; discriminator: { key: 'mode'; value: 'create' } }
379
405
  * ```
380
406
  *
381
- * @see {@link ValidPathsOfType} for the base type without react-hook-form Path intersection
382
- * @see {@link ValidFieldPaths} for discriminated union field filtering
383
- * @since 0.5.0
407
+ * @see {@link FieldSelector} from `@zod-utils/core` for the base type
384
408
  */
385
- type ValidFieldPathsOfType<TSchema extends z.ZodType, TValueConstraint, TDiscriminatorKey extends DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>> = ValidPathsOfType<TSchema, TValueConstraint, TDiscriminatorKey, TDiscriminatorValue> & Path<TFieldValues>;
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>;
386
410
 
387
411
  /**
388
412
  * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.
@@ -556,4 +580,46 @@ declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValu
556
580
  zodResolverOptions?: Parameters<typeof zodResolver>[1];
557
581
  } & Omit<UseFormProps<TFormInput, unknown, TOutput>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<TFormInput, unknown, TOutput>;
558
582
 
559
- export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, type ValidFieldPathsOfType, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
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
+ /**
604
+ * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.
605
+ *
606
+ * This is useful for `useMemo` and `useCallback` dependencies where you want to avoid
607
+ * re-running when object references change but values stay the same.
608
+ *
609
+ * @param params - The FieldSelector containing schema, name, and optional discriminator
610
+ * @returns An array of primitive values suitable for React dependency arrays
611
+ *
612
+ * @example
613
+ * ```tsx
614
+ * const memoizedValue = useMemo(() => {
615
+ * return extractFieldFromSchema(params);
616
+ * }, flattenFieldSelector(params));
617
+ * ```
618
+ */
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[];
624
+
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 };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- import { DiscriminatorKey, DiscriminatorValue, Discriminator, ValidPaths, ExtractZodByPath, ZodUnionCheck, Simplify, ValidPathsOfType } from '@zod-utils/core';
1
+ import { DiscriminatorKey, DiscriminatorValue, FieldSelector, ValidPaths, Discriminator, ExtractZodByPath, ZodUnionCheck, Simplify, InnerFieldSelector } 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';
6
7
  import * as react_hook_form from 'react-hook-form';
7
8
  import { FieldValues, Path, UseFormProps } from 'react-hook-form';
8
9
  import { zodResolver } from '@hookform/resolvers/zod';
@@ -11,17 +12,11 @@ import { zodResolver } from '@hookform/resolvers/zod';
11
12
  * Type for the FormSchemaContext with full generic support.
12
13
  * @internal
13
14
  */
14
- type FormSchemaContextType<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = Context<{
15
- schema: TSchema;
16
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
17
- } | null>;
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>;
18
16
  /**
19
17
  * Context value type for FormSchemaContext.
20
18
  */
21
- type FormSchemaContextValue<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = {
22
- schema: TSchema;
23
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
24
- } | null;
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;
25
20
  /**
26
21
  * React Context for providing Zod schema to form components.
27
22
  *
@@ -62,10 +57,19 @@ declare const FormSchemaContext: Context<{
62
57
  * }
63
58
  * ```
64
59
  */
65
- declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = DiscriminatorValue<TSchema, TDiscriminatorKey>>(_params?: {
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 ? {
66
67
  schema: TSchema;
67
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
68
- }): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
68
+ discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
69
+ } : {
70
+ schema: TSchema;
71
+ discriminator?: undefined;
72
+ }): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>;
69
73
  /**
70
74
  * Provider component that makes Zod schema available to all child components.
71
75
  *
@@ -101,9 +105,19 @@ declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscrimin
101
105
  * </FormSchemaProvider>
102
106
  * ```
103
107
  */
104
- declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, discriminator, children, }: {
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
+ } : {
105
118
  schema: TSchema;
106
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
119
+ discriminator?: undefined;
120
+ }) & {
107
121
  children: ReactNode;
108
122
  }): react_jsx_runtime.JSX.Element;
109
123
  /**
@@ -128,10 +142,10 @@ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey
128
142
  * }
129
143
  * ```
130
144
  */
131
- declare function useIsRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
132
- schema?: TSchema;
133
- name?: TPath;
134
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
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;
135
149
  }): boolean;
136
150
  /**
137
151
  * Determines if a field requires valid input (will show validation errors on empty/invalid input).
@@ -174,11 +188,7 @@ declare function useIsRequiredField<TSchema extends z.ZodType, TPath extends Val
174
188
  * }); // true
175
189
  * ```
176
190
  */
177
- declare function isRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
178
- schema: TSchema;
179
- name: TPath;
180
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
181
- }): boolean;
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;
182
192
  /**
183
193
  * React hook to extract a field's Zod schema from a parent schema.
184
194
  *
@@ -216,11 +226,7 @@ declare function isRequiredField<TSchema extends z.ZodType, TPath extends ValidP
216
226
  * // Returns z.string() schema
217
227
  * ```
218
228
  */
219
- declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
220
- schema: TSchema;
221
- name: TPath;
222
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
223
- }): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined;
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;
224
230
  /**
225
231
  * Hook to get validation checks from a field's Zod schema.
226
232
  *
@@ -243,11 +249,7 @@ declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TPath exte
243
249
  * }
244
250
  * ```
245
251
  */
246
- declare function useFieldChecks<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
247
- schema: TSchema;
248
- name: TPath;
249
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
250
- }): ZodUnionCheck[];
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[];
251
253
 
252
254
  /**
253
255
  * Helper type that adds `null` to object-type fields only (excludes arrays).
@@ -308,12 +310,45 @@ type PartialWithAllNullables<T> = {
308
310
  */
309
311
  type InferredFieldValues<TSchema extends z.ZodType> = z.input<TSchema> & FieldValues;
310
312
  /**
311
- * Type-safe field names for a specific discriminator value.
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)
312
324
  *
313
- * Narrows field names to only those that exist for the given discriminator value
314
- * in a discriminated union schema.
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
+ * ```
315
336
  *
316
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
317
352
  * ```typescript
318
353
  * const schema = z.discriminatedUnion('mode', [
319
354
  * z.object({ mode: z.literal('create'), name: z.string() }),
@@ -323,47 +358,38 @@ type InferredFieldValues<TSchema extends z.ZodType> = z.input<TSchema> & FieldVa
323
358
  * type CreateFields = ValidFieldPaths<typeof schema, 'mode', 'create'>;
324
359
  * // "mode" | "name"
325
360
  *
326
- * type EditFields = ValidFieldPaths<typeof schema, 'mode', 'edit'>;
327
- * // "mode" | "id"
361
+ * type EditNumberFields = ValidFieldPaths<typeof schema, 'mode', 'edit', number>;
362
+ * // "id"
328
363
  * ```
364
+ *
365
+ * @see {@link ValidPaths} for the base type without react-hook-form Path intersection
329
366
  */
330
- type ValidFieldPaths<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>> = ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue> & Path<TFieldValues>;
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>;
331
368
  /**
332
- * Type-safe field paths filtered by value type for React Hook Form.
369
+ * Type-safe field selector for React Hook Form with discriminated union support.
333
370
  *
334
- * Combines `ValidPathsOfType` (filters by value type) with React Hook Form's
335
- * `Path` type for full compatibility with form field APIs.
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`
336
375
  *
337
376
  * @template TSchema - The Zod schema type
338
- * @template TValueConstraint - The value type to filter by
377
+ * @template TPath - The field path
339
378
  * @template TDiscriminatorKey - The discriminator key (for discriminated unions)
340
379
  * @template TDiscriminatorValue - The discriminator value (for discriminated unions)
341
- * @template TFieldValues - The form field values type (inferred from schema)
380
+ * @template TFilterType - The value type to filter by (default: unknown = no filtering)
381
+ * @template TStrict - Whether to use strict type matching (default: true)
342
382
  *
343
383
  * @example
344
- * Get all string field paths for form
384
+ * Basic usage with regular schema
345
385
  * ```typescript
346
386
  * const schema = z.object({
347
387
  * name: z.string(),
348
388
  * age: z.number(),
349
- * email: z.string().optional(),
350
- * });
351
- *
352
- * type StringFields = ValidFieldPathsOfType<typeof schema, string>;
353
- * // "name" | "email" - compatible with react-hook-form Path
354
- * ```
355
- *
356
- * @example
357
- * Get all number field paths for form
358
- * ```typescript
359
- * const schema = z.object({
360
- * id: z.number(),
361
- * name: z.string(),
362
- * count: z.number().optional(),
363
389
  * });
364
390
  *
365
- * type NumberFields = ValidFieldPathsOfType<typeof schema, number>;
366
- * // "id" | "count"
391
+ * type NameSelector = FormFieldSelector<typeof schema, 'name'>;
392
+ * // { schema: typeof schema; name: 'name' }
367
393
  * ```
368
394
  *
369
395
  * @example
@@ -374,15 +400,13 @@ type ValidFieldPaths<TSchema extends z.ZodType, TDiscriminatorKey extends Discri
374
400
  * z.object({ mode: z.literal('edit'), id: z.number() }),
375
401
  * ]);
376
402
  *
377
- * type EditNumberFields = ValidFieldPathsOfType<typeof schema, number, 'mode', 'edit'>;
378
- * // "id"
403
+ * type CreateNameSelector = FormFieldSelector<typeof schema, 'name', 'mode', 'create'>;
404
+ * // { schema: typeof schema; name: 'name'; discriminator: { key: 'mode'; value: 'create' } }
379
405
  * ```
380
406
  *
381
- * @see {@link ValidPathsOfType} for the base type without react-hook-form Path intersection
382
- * @see {@link ValidFieldPaths} for discriminated union field filtering
383
- * @since 0.5.0
407
+ * @see {@link FieldSelector} from `@zod-utils/core` for the base type
384
408
  */
385
- type ValidFieldPathsOfType<TSchema extends z.ZodType, TValueConstraint, TDiscriminatorKey extends DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>> = ValidPathsOfType<TSchema, TValueConstraint, TDiscriminatorKey, TDiscriminatorValue> & Path<TFieldValues>;
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>;
386
410
 
387
411
  /**
388
412
  * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.
@@ -556,4 +580,46 @@ declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValu
556
580
  zodResolverOptions?: Parameters<typeof zodResolver>[1];
557
581
  } & Omit<UseFormProps<TFormInput, unknown, TOutput>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<TFormInput, unknown, TOutput>;
558
582
 
559
- export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, type ValidFieldPathsOfType, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
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
+ /**
604
+ * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.
605
+ *
606
+ * This is useful for `useMemo` and `useCallback` dependencies where you want to avoid
607
+ * re-running when object references change but values stay the same.
608
+ *
609
+ * @param params - The FieldSelector containing schema, name, and optional discriminator
610
+ * @returns An array of primitive values suitable for React dependency arrays
611
+ *
612
+ * @example
613
+ * ```tsx
614
+ * const memoizedValue = useMemo(() => {
615
+ * return extractFieldFromSchema(params);
616
+ * }, flattenFieldSelector(params));
617
+ * ```
618
+ */
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[];
624
+
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 };
package/dist/index.js CHANGED
@@ -34,6 +34,19 @@ var __objRest = (source, exclude) => {
34
34
  }
35
35
  return target;
36
36
  };
37
+
38
+ // src/utils.ts
39
+ function toFormFieldSelector(props) {
40
+ const { schema, name, discriminator } = props;
41
+ return { schema, name, discriminator };
42
+ }
43
+ function flattenFieldSelector(params) {
44
+ const _a = params, { discriminator } = _a, rest = __objRest(_a, ["discriminator"]);
45
+ return [
46
+ ...Object.values(rest),
47
+ ...discriminator ? Object.values(discriminator) : []
48
+ ];
49
+ }
37
50
  var FormSchemaContext = react.createContext(null);
38
51
  function useFormSchema(_params) {
39
52
  return react.useContext(
@@ -48,53 +61,33 @@ function FormSchemaProvider({
48
61
  }) {
49
62
  return /* @__PURE__ */ jsxRuntime.jsx(FormSchemaContext.Provider, { value: { schema, discriminator }, children });
50
63
  }
51
- function useIsRequiredField({
52
- schema,
53
- name,
54
- discriminator
55
- }) {
64
+ function useIsRequiredField(params) {
56
65
  return react.useMemo(() => {
57
- if (!schema || !name) {
66
+ if (!params.schema || !params.name) {
58
67
  return false;
59
68
  }
60
- return isRequiredField({ schema, name, discriminator });
61
- }, [schema, name, discriminator]);
69
+ return isRequiredField(params);
70
+ }, [...flattenFieldSelector(params)]);
62
71
  }
63
- function isRequiredField({
64
- schema,
65
- name,
66
- discriminator
67
- }) {
68
- const field = core.extractFieldFromSchema({
69
- schema,
70
- name,
71
- discriminator
72
- });
72
+ function isRequiredField(params) {
73
+ const field = core.extractFieldFromSchema(params);
73
74
  if (!field) {
74
75
  return false;
75
76
  }
76
77
  return core.requiresValidInput(field);
77
78
  }
78
- function useExtractFieldFromSchema({
79
- schema,
80
- name,
81
- discriminator
82
- }) {
79
+ function useExtractFieldFromSchema(params) {
83
80
  return react.useMemo(
84
- () => core.extractFieldFromSchema({ schema, name, discriminator }),
85
- [schema, name, discriminator]
81
+ () => core.extractFieldFromSchema(params),
82
+ [...flattenFieldSelector(params)]
86
83
  );
87
84
  }
88
- function useFieldChecks({
89
- schema,
90
- name,
91
- discriminator
92
- }) {
85
+ function useFieldChecks(params) {
93
86
  return react.useMemo(() => {
94
- const field = core.extractFieldFromSchema({ schema, name, discriminator });
87
+ const field = core.extractFieldFromSchema(params);
95
88
  if (!field) return [];
96
89
  return core.getFieldChecks(field);
97
- }, [schema, name, discriminator]);
90
+ }, [...flattenFieldSelector(params)]);
98
91
  }
99
92
  var useZodForm = (_a) => {
100
93
  var _b = _a, {
@@ -112,7 +105,9 @@ var useZodForm = (_a) => {
112
105
 
113
106
  exports.FormSchemaContext = FormSchemaContext;
114
107
  exports.FormSchemaProvider = FormSchemaProvider;
108
+ exports.flattenFieldSelector = flattenFieldSelector;
115
109
  exports.isRequiredField = isRequiredField;
110
+ exports.toFormFieldSelector = toFormFieldSelector;
116
111
  exports.useExtractFieldFromSchema = useExtractFieldFromSchema;
117
112
  exports.useFieldChecks = useFieldChecks;
118
113
  exports.useFormSchema = useFormSchema;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context.tsx","../src/use-zod-form.ts"],"names":["createContext","useContext","jsx","useMemo","extractFieldFromSchema","requiresValidInput","getFieldChecks","zodResolver","useForm"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DO,IAAM,iBAAA,GAAoBA,oBAMvB,IAAI;AA6BP,SAAS,cAUd,OAAA,EAQyE;AACzE,EAAA,OAAOC,gBAAA;AAAA;AAAA,IAEL;AAAA,GAQF;AACF;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EAQG;AACD,EAAA,uBACEC,cAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,kBAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQY;AACV,EAAA,OAAOC,cAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,IAAU,CAAC,IAAA,EAAM;AACpB,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,eAAA,CAAgB,EAAE,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAA;AAAA,EACxD,CAAA,EAAG,CAAC,MAAA,EAAQ,IAAA,EAAM,aAAa,CAAC,CAAA;AAClC;AA2CO,SAAS,eAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQY;AACV,EAAA,MAAM,QAAQC,2BAAA,CAAuB;AAAA,IACnC,MAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAOC,wBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,yBAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQ+D;AAC7D,EAAA,OAAOF,aAAA;AAAA,IACL,MAAMC,2BAAA,CAAuB,EAAE,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAA;AAAA,IAC5D,CAAC,MAAA,EAAQ,IAAA,EAAM,aAAa;AAAA,GAC9B;AACF;AAwBO,SAAS,cAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQoB;AAClB,EAAA,OAAOD,cAAQ,MAAM;AACnB,IAAA,MAAM,QAAQC,2BAAA,CAAuB,EAAE,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAA;AACpE,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAOE,oBAAe,KAAK,CAAA;AAAA,EAC7B,CAAA,EAAG,CAAC,MAAA,EAAQ,IAAA,EAAM,aAAa,CAAC,CAAA;AAClC;AChPO,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":["'use client';\n\nimport {\n type Discriminator,\n type DiscriminatorKey,\n type DiscriminatorValue,\n type ExtractZodByPath,\n extractFieldFromSchema,\n getFieldChecks,\n requiresValidInput,\n type ValidPaths,\n type ZodUnionCheck,\n} from '@zod-utils/core';\nimport {\n type Context,\n createContext,\n type ReactNode,\n useContext,\n useMemo,\n} from 'react';\nimport type { z } from 'zod';\n\n/**\n * Type for the FormSchemaContext with full generic support.\n * @internal\n */\nexport type FormSchemaContextType<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n> = Context<{\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n} | null>;\n\n/**\n * Context value type for FormSchemaContext.\n */\nexport type FormSchemaContextValue<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n> = {\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n} | null;\n\n/**\n * React Context for providing Zod schema to form components.\n *\n * Use with {@link FormSchemaProvider} to provide schema context, and\n * {@link useFormSchema} to consume it in child components.\n */\nexport const FormSchemaContext = createContext<{\n schema: z.ZodType;\n discriminator?: {\n key: unknown;\n value: unknown;\n };\n} | null>(null);\n\n/**\n * Hook to access the form schema from context.\n *\n * The optional `_params` argument is used for TypeScript type inference only.\n * Pass your schema to get proper type narrowing of the context value.\n *\n * @param _params - Optional params for type inference (not used at runtime)\n * @returns The schema context value or null if not within a provider\n *\n * @example\n * ```tsx\n * // Without type params (returns generic context)\n * function MyFormField() {\n * const context = useFormSchema();\n * if (!context) return null;\n *\n * const { schema, discriminator } = context;\n * // Use schema for validation or field extraction\n * }\n *\n * // With type params (for type-safe schema access)\n * function TypedFormField() {\n * const context = useFormSchema({ schema: mySchema });\n * // context.schema is now typed as typeof mySchema\n * }\n * ```\n */\nexport function useFormSchema<\n TSchema extends z.ZodType = z.ZodType,\n TDiscriminatorKey extends\n DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>(\n // Parameter used for type inference only, not at runtime\n _params?: {\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n },\n): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue> {\n return useContext(\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n FormSchemaContext as Context<{\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n } | null>,\n );\n}\n\n/**\n * Provider component that makes Zod schema available to all child components.\n *\n * Use this to wrap your form and provide schema context to nested components\n * like field labels and validation indicators.\n *\n * @example\n * Basic usage with ZodObject\n * ```tsx\n * const schema = z.object({\n * name: z.string(),\n * email: z.string().email().optional()\n * });\n *\n * <FormSchemaProvider schema={schema}>\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n *\n * @example\n * Usage with discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() })\n * ]);\n *\n * <FormSchemaProvider\n * schema={schema}\n * discriminator={{ key: 'mode', value: 'create' }}\n * >\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n */\nexport function FormSchemaProvider<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n discriminator,\n children,\n}: {\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n children: ReactNode;\n}) {\n return (\n <FormSchemaContext.Provider value={{ schema, discriminator }}>\n {children}\n </FormSchemaContext.Provider>\n );\n}\n\n/**\n * Hook to check if a field requires valid input based on the Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n *\n * @param params - Schema, name, and optional discriminator (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<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema?: TSchema;\n name?: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): boolean {\n return useMemo(() => {\n if (!schema || !name) {\n return false;\n }\n\n return isRequiredField({ schema, name, discriminator });\n }, [schema, name, discriminator]);\n}\n\n/**\n * Determines if a field requires valid input (will show validation errors on empty/invalid input).\n *\n * Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after\n * removing defaults. This tells you if the field will error when user submits empty input.\n *\n * Returns false if the underlying field accepts:\n * - `undefined` (via `.optional()`)\n * - `null` (via `.nullable()`)\n * - Empty strings (plain `z.string()` without `.min(1)`)\n * - Empty arrays (plain `z.array()` without `.min(1)`)\n *\n * @param options - Schema, field name, and optional discriminator\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * bio: z.string().optional(),\n * });\n *\n * isRequiredField({ schema, name: 'name' }); // true\n * isRequiredField({ schema, name: 'bio' }); // false\n * ```\n *\n * @example\n * With discriminated union\n * ```typescript\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string().min(1) }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * isRequiredField({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * }); // true\n * ```\n */\nexport function isRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): boolean {\n const field = extractFieldFromSchema({\n schema,\n name,\n discriminator,\n });\n\n if (!field) {\n return false;\n }\n\n return requiresValidInput(field);\n}\n\n/**\n * React hook to extract a field's Zod schema from a parent schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Supports nested paths and discriminated unions.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns The Zod schema for the field, or undefined if not found\n *\n * @example\n * ```tsx\n * function MyFieldInfo({ name, schema }: { name: string; schema: z.ZodType }) {\n * const fieldSchema = useExtractFieldFromSchema({ schema, name });\n *\n * if (!fieldSchema) return null;\n *\n * // Use fieldSchema for custom validation or field info\n * return <span>{fieldSchema._zod.typeName}</span>;\n * }\n * ```\n *\n * @example\n * With discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * const fieldSchema = useExtractFieldFromSchema({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * });\n * // Returns z.string() schema\n * ```\n */\nexport function useExtractFieldFromSchema<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined {\n return useMemo(\n () => extractFieldFromSchema({ schema, name, discriminator }),\n [schema, name, discriminator],\n );\n}\n\n/**\n * Hook to get validation checks from a field's Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Combines field extraction and check retrieval in one cached operation.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns Array of validation checks (min, max, pattern, etc.) or empty array\n *\n * @example\n * ```tsx\n * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {\n * const checks = useFieldChecks({ schema, name });\n *\n * const maxLength = checks.find(c => c.check === 'max_length');\n * if (maxLength) {\n * return <span>Max {maxLength.maximum} characters</span>;\n * }\n * return null;\n * }\n * ```\n */\nexport function useFieldChecks<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): ZodUnionCheck[] {\n return useMemo(() => {\n const field = extractFieldFromSchema({ schema, name, discriminator });\n if (!field) return [];\n return getFieldChecks(field);\n }, [schema, name, discriminator]);\n}\n","import { zodResolver } from '@hookform/resolvers/zod';\nimport { type FieldValues, type UseFormProps, useForm } from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"]}
package/dist/index.mjs CHANGED
@@ -33,6 +33,19 @@ var __objRest = (source, exclude) => {
33
33
  }
34
34
  return target;
35
35
  };
36
+
37
+ // src/utils.ts
38
+ function toFormFieldSelector(props) {
39
+ const { schema, name, discriminator } = props;
40
+ return { schema, name, discriminator };
41
+ }
42
+ function flattenFieldSelector(params) {
43
+ const _a = params, { discriminator } = _a, rest = __objRest(_a, ["discriminator"]);
44
+ return [
45
+ ...Object.values(rest),
46
+ ...discriminator ? Object.values(discriminator) : []
47
+ ];
48
+ }
36
49
  var FormSchemaContext = createContext(null);
37
50
  function useFormSchema(_params) {
38
51
  return useContext(
@@ -47,53 +60,33 @@ function FormSchemaProvider({
47
60
  }) {
48
61
  return /* @__PURE__ */ jsx(FormSchemaContext.Provider, { value: { schema, discriminator }, children });
49
62
  }
50
- function useIsRequiredField({
51
- schema,
52
- name,
53
- discriminator
54
- }) {
63
+ function useIsRequiredField(params) {
55
64
  return useMemo(() => {
56
- if (!schema || !name) {
65
+ if (!params.schema || !params.name) {
57
66
  return false;
58
67
  }
59
- return isRequiredField({ schema, name, discriminator });
60
- }, [schema, name, discriminator]);
68
+ return isRequiredField(params);
69
+ }, [...flattenFieldSelector(params)]);
61
70
  }
62
- function isRequiredField({
63
- schema,
64
- name,
65
- discriminator
66
- }) {
67
- const field = extractFieldFromSchema({
68
- schema,
69
- name,
70
- discriminator
71
- });
71
+ function isRequiredField(params) {
72
+ const field = extractFieldFromSchema(params);
72
73
  if (!field) {
73
74
  return false;
74
75
  }
75
76
  return requiresValidInput(field);
76
77
  }
77
- function useExtractFieldFromSchema({
78
- schema,
79
- name,
80
- discriminator
81
- }) {
78
+ function useExtractFieldFromSchema(params) {
82
79
  return useMemo(
83
- () => extractFieldFromSchema({ schema, name, discriminator }),
84
- [schema, name, discriminator]
80
+ () => extractFieldFromSchema(params),
81
+ [...flattenFieldSelector(params)]
85
82
  );
86
83
  }
87
- function useFieldChecks({
88
- schema,
89
- name,
90
- discriminator
91
- }) {
84
+ function useFieldChecks(params) {
92
85
  return useMemo(() => {
93
- const field = extractFieldFromSchema({ schema, name, discriminator });
86
+ const field = extractFieldFromSchema(params);
94
87
  if (!field) return [];
95
88
  return getFieldChecks(field);
96
- }, [schema, name, discriminator]);
89
+ }, [...flattenFieldSelector(params)]);
97
90
  }
98
91
  var useZodForm = (_a) => {
99
92
  var _b = _a, {
@@ -109,6 +102,6 @@ var useZodForm = (_a) => {
109
102
  }, formOptions));
110
103
  };
111
104
 
112
- export { FormSchemaContext, FormSchemaProvider, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
105
+ export { FormSchemaContext, FormSchemaProvider, flattenFieldSelector, isRequiredField, toFormFieldSelector, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
113
106
  //# sourceMappingURL=index.mjs.map
114
107
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context.tsx","../src/use-zod-form.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DO,IAAM,iBAAA,GAAoB,cAMvB,IAAI;AA6BP,SAAS,cAUd,OAAA,EAQyE;AACzE,EAAA,OAAO,UAAA;AAAA;AAAA,IAEL;AAAA,GAQF;AACF;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EAQG;AACD,EAAA,uBACE,GAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,kBAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQY;AACV,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,IAAU,CAAC,IAAA,EAAM;AACpB,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,eAAA,CAAgB,EAAE,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAA;AAAA,EACxD,CAAA,EAAG,CAAC,MAAA,EAAQ,IAAA,EAAM,aAAa,CAAC,CAAA;AAClC;AA2CO,SAAS,eAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQY;AACV,EAAA,MAAM,QAAQ,sBAAA,CAAuB;AAAA,IACnC,MAAA;AAAA,IACA,IAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,mBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,yBAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQ+D;AAC7D,EAAA,OAAO,OAAA;AAAA,IACL,MAAM,sBAAA,CAAuB,EAAE,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAA;AAAA,IAC5D,CAAC,MAAA,EAAQ,IAAA,EAAM,aAAa;AAAA,GAC9B;AACF;AAwBO,SAAS,cAAA,CAKd;AAAA,EACA,MAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAQoB;AAClB,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,MAAM,QAAQ,sBAAA,CAAuB,EAAE,MAAA,EAAQ,IAAA,EAAM,eAAe,CAAA;AACpE,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAO,eAAe,KAAK,CAAA;AAAA,EAC7B,CAAA,EAAG,CAAC,MAAA,EAAQ,IAAA,EAAM,aAAa,CAAC,CAAA;AAClC;AChPO,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":["'use client';\n\nimport {\n type Discriminator,\n type DiscriminatorKey,\n type DiscriminatorValue,\n type ExtractZodByPath,\n extractFieldFromSchema,\n getFieldChecks,\n requiresValidInput,\n type ValidPaths,\n type ZodUnionCheck,\n} from '@zod-utils/core';\nimport {\n type Context,\n createContext,\n type ReactNode,\n useContext,\n useMemo,\n} from 'react';\nimport type { z } from 'zod';\n\n/**\n * Type for the FormSchemaContext with full generic support.\n * @internal\n */\nexport type FormSchemaContextType<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n> = Context<{\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n} | null>;\n\n/**\n * Context value type for FormSchemaContext.\n */\nexport type FormSchemaContextValue<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n> = {\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n} | null;\n\n/**\n * React Context for providing Zod schema to form components.\n *\n * Use with {@link FormSchemaProvider} to provide schema context, and\n * {@link useFormSchema} to consume it in child components.\n */\nexport const FormSchemaContext = createContext<{\n schema: z.ZodType;\n discriminator?: {\n key: unknown;\n value: unknown;\n };\n} | null>(null);\n\n/**\n * Hook to access the form schema from context.\n *\n * The optional `_params` argument is used for TypeScript type inference only.\n * Pass your schema to get proper type narrowing of the context value.\n *\n * @param _params - Optional params for type inference (not used at runtime)\n * @returns The schema context value or null if not within a provider\n *\n * @example\n * ```tsx\n * // Without type params (returns generic context)\n * function MyFormField() {\n * const context = useFormSchema();\n * if (!context) return null;\n *\n * const { schema, discriminator } = context;\n * // Use schema for validation or field extraction\n * }\n *\n * // With type params (for type-safe schema access)\n * function TypedFormField() {\n * const context = useFormSchema({ schema: mySchema });\n * // context.schema is now typed as typeof mySchema\n * }\n * ```\n */\nexport function useFormSchema<\n TSchema extends z.ZodType = z.ZodType,\n TDiscriminatorKey extends\n DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>(\n // Parameter used for type inference only, not at runtime\n _params?: {\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n },\n): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue> {\n return useContext(\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n FormSchemaContext as Context<{\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n } | null>,\n );\n}\n\n/**\n * Provider component that makes Zod schema available to all child components.\n *\n * Use this to wrap your form and provide schema context to nested components\n * like field labels and validation indicators.\n *\n * @example\n * Basic usage with ZodObject\n * ```tsx\n * const schema = z.object({\n * name: z.string(),\n * email: z.string().email().optional()\n * });\n *\n * <FormSchemaProvider schema={schema}>\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n *\n * @example\n * Usage with discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() })\n * ]);\n *\n * <FormSchemaProvider\n * schema={schema}\n * discriminator={{ key: 'mode', value: 'create' }}\n * >\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n */\nexport function FormSchemaProvider<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n discriminator,\n children,\n}: {\n schema: TSchema;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n children: ReactNode;\n}) {\n return (\n <FormSchemaContext.Provider value={{ schema, discriminator }}>\n {children}\n </FormSchemaContext.Provider>\n );\n}\n\n/**\n * Hook to check if a field requires valid input based on the Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n *\n * @param params - Schema, name, and optional discriminator (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<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema?: TSchema;\n name?: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): boolean {\n return useMemo(() => {\n if (!schema || !name) {\n return false;\n }\n\n return isRequiredField({ schema, name, discriminator });\n }, [schema, name, discriminator]);\n}\n\n/**\n * Determines if a field requires valid input (will show validation errors on empty/invalid input).\n *\n * Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after\n * removing defaults. This tells you if the field will error when user submits empty input.\n *\n * Returns false if the underlying field accepts:\n * - `undefined` (via `.optional()`)\n * - `null` (via `.nullable()`)\n * - Empty strings (plain `z.string()` without `.min(1)`)\n * - Empty arrays (plain `z.array()` without `.min(1)`)\n *\n * @param options - Schema, field name, and optional discriminator\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * bio: z.string().optional(),\n * });\n *\n * isRequiredField({ schema, name: 'name' }); // true\n * isRequiredField({ schema, name: 'bio' }); // false\n * ```\n *\n * @example\n * With discriminated union\n * ```typescript\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string().min(1) }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * isRequiredField({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * }); // true\n * ```\n */\nexport function isRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): boolean {\n const field = extractFieldFromSchema({\n schema,\n name,\n discriminator,\n });\n\n if (!field) {\n return false;\n }\n\n return requiresValidInput(field);\n}\n\n/**\n * React hook to extract a field's Zod schema from a parent schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Supports nested paths and discriminated unions.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns The Zod schema for the field, or undefined if not found\n *\n * @example\n * ```tsx\n * function MyFieldInfo({ name, schema }: { name: string; schema: z.ZodType }) {\n * const fieldSchema = useExtractFieldFromSchema({ schema, name });\n *\n * if (!fieldSchema) return null;\n *\n * // Use fieldSchema for custom validation or field info\n * return <span>{fieldSchema._zod.typeName}</span>;\n * }\n * ```\n *\n * @example\n * With discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * const fieldSchema = useExtractFieldFromSchema({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * });\n * // Returns z.string() schema\n * ```\n */\nexport function useExtractFieldFromSchema<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined {\n return useMemo(\n () => extractFieldFromSchema({ schema, name, discriminator }),\n [schema, name, discriminator],\n );\n}\n\n/**\n * Hook to get validation checks from a field's Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Combines field extraction and check retrieval in one cached operation.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns Array of validation checks (min, max, pattern, etc.) or empty array\n *\n * @example\n * ```tsx\n * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {\n * const checks = useFieldChecks({ schema, name });\n *\n * const maxLength = checks.find(c => c.check === 'max_length');\n * if (maxLength) {\n * return <span>Max {maxLength.maximum} characters</span>;\n * }\n * return null;\n * }\n * ```\n */\nexport function useFieldChecks<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n name,\n discriminator,\n}: {\n schema: TSchema;\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): ZodUnionCheck[] {\n return useMemo(() => {\n const field = extractFieldFromSchema({ schema, name, discriminator });\n if (!field) return [];\n return getFieldChecks(field);\n }, [schema, name, discriminator]);\n}\n","import { zodResolver } from '@hookform/resolvers/zod';\nimport { type FieldValues, type UseFormProps, useForm } from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zod-utils/react-hook-form",
3
- "version": "2.0.2",
3
+ "version": "4.0.0",
4
4
  "description": "React Hook Form integration and utilities for Zod schemas",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",