@zod-utils/react-hook-form 2.0.2 → 3.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 +122 -11
- package/dist/index.d.mts +139 -68
- package/dist/index.d.ts +139 -68
- package/dist/index.js +26 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +25 -33
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -450,6 +450,10 @@ import {
|
|
|
450
450
|
useExtractFieldFromSchema,
|
|
451
451
|
useFieldChecks,
|
|
452
452
|
|
|
453
|
+
// Form field utilities
|
|
454
|
+
mergeFormFieldSelectorProps,
|
|
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
|
|
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
|
-
#### `
|
|
533
|
+
#### `ValidFieldPaths<TSchema, TDiscriminatorKey?, TDiscriminatorValue?, TFieldValues?, TFilterType?, TStrict?>`
|
|
530
534
|
|
|
531
|
-
|
|
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 {
|
|
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
|
-
//
|
|
545
|
-
type
|
|
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 =
|
|
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
|
|
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,84 @@ const formSchema = z.discriminatedUnion("mode", [
|
|
|
578
586
|
]);
|
|
579
587
|
|
|
580
588
|
// Number paths for 'edit' variant
|
|
581
|
-
type EditNumberPaths =
|
|
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
|
+
#### `mergeFormFieldSelectorProps(factoryProps, componentProps)`
|
|
631
|
+
|
|
632
|
+
Merges factory props with component props into a `FormFieldSelector`. Useful for creating form field component factories.
|
|
633
|
+
|
|
634
|
+
```typescript
|
|
635
|
+
import { mergeFormFieldSelectorProps } from "@zod-utils/react-hook-form";
|
|
636
|
+
|
|
637
|
+
// In a factory function
|
|
638
|
+
function createInputFormField<TSchema extends z.ZodType>({ schema }: { schema: TSchema }) {
|
|
639
|
+
return function InputFormField({ name, discriminator }) {
|
|
640
|
+
const selectorProps = mergeFormFieldSelectorProps(
|
|
641
|
+
{ schema },
|
|
642
|
+
{ name, discriminator }
|
|
643
|
+
);
|
|
644
|
+
// Use selectorProps for extractFieldFromSchema, useIsRequiredField, etc.
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
---
|
|
650
|
+
|
|
651
|
+
#### `flattenFieldSelector(params)`
|
|
652
|
+
|
|
653
|
+
Flattens a `FieldSelector` into an array of primitive values for use in React dependency arrays.
|
|
654
|
+
|
|
655
|
+
```tsx
|
|
656
|
+
import { flattenFieldSelector, extractFieldFromSchema } from "@zod-utils/react-hook-form";
|
|
657
|
+
|
|
658
|
+
function useFieldSchema(params) {
|
|
659
|
+
return useMemo(() => {
|
|
660
|
+
return extractFieldFromSchema(params);
|
|
661
|
+
}, flattenFieldSelector(params));
|
|
662
|
+
}
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
---
|
|
666
|
+
|
|
592
667
|
## Complete Example
|
|
593
668
|
|
|
594
669
|
```typescript
|
|
@@ -691,6 +766,42 @@ form.register("nonexistent.field");
|
|
|
691
766
|
|
|
692
767
|
---
|
|
693
768
|
|
|
769
|
+
## Migration Guide
|
|
770
|
+
|
|
771
|
+
### Migrating to v3.0.0
|
|
772
|
+
|
|
773
|
+
#### `ValidFieldPathsOfType` removed → Use `ValidFieldPaths` with type filtering
|
|
774
|
+
|
|
775
|
+
The `ValidFieldPathsOfType` type has been removed and consolidated into `ValidFieldPaths` with a new `TFilterType` parameter.
|
|
776
|
+
|
|
777
|
+
**Before (v2.x):**
|
|
778
|
+
```typescript
|
|
779
|
+
import type { ValidFieldPathsOfType } from "@zod-utils/react-hook-form";
|
|
780
|
+
|
|
781
|
+
// Get number field paths
|
|
782
|
+
type NumberPaths = ValidFieldPathsOfType<typeof schema, number>;
|
|
783
|
+
|
|
784
|
+
// With discriminated union
|
|
785
|
+
type EditNumberPaths = ValidFieldPathsOfType<typeof schema, number, "mode", "edit">;
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
**After (v3.x):**
|
|
789
|
+
```typescript
|
|
790
|
+
import type { ValidFieldPaths } from "@zod-utils/react-hook-form";
|
|
791
|
+
|
|
792
|
+
// Get number field paths - TFilterType is now the 5th parameter
|
|
793
|
+
type NumberPaths = ValidFieldPaths<typeof schema, never, never, never, number>;
|
|
794
|
+
|
|
795
|
+
// With discriminated union
|
|
796
|
+
type EditNumberPaths = ValidFieldPaths<typeof schema, "mode", "edit", never, number>;
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
**Parameter order change:**
|
|
800
|
+
- v2.x: `ValidFieldPathsOfType<TSchema, TValueConstraint, TDiscriminatorKey, TDiscriminatorValue, TFieldValues>`
|
|
801
|
+
- v3.x: `ValidFieldPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>`
|
|
802
|
+
|
|
803
|
+
---
|
|
804
|
+
|
|
694
805
|
## License
|
|
695
806
|
|
|
696
807
|
MIT
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { DiscriminatorKey, DiscriminatorValue,
|
|
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
|
|
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
|
|
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 ? {
|
|
66
61
|
schema: TSchema;
|
|
67
|
-
discriminator
|
|
68
|
-
}
|
|
62
|
+
discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
63
|
+
} : {
|
|
64
|
+
schema: TSchema;
|
|
65
|
+
discriminator?: undefined;
|
|
66
|
+
} : TSchema extends z.ZodDiscriminatedUnion ? {
|
|
67
|
+
schema: TSchema;
|
|
68
|
+
discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
69
|
+
} : {
|
|
70
|
+
schema: TSchema;
|
|
71
|
+
discriminator?: undefined;
|
|
72
|
+
}): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>;
|
|
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 ? {
|
|
105
109
|
schema: TSchema;
|
|
106
|
-
discriminator
|
|
110
|
+
discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
111
|
+
} : {
|
|
112
|
+
schema: TSchema;
|
|
113
|
+
discriminator?: undefined;
|
|
114
|
+
} : TSchema extends z.ZodDiscriminatedUnion ? {
|
|
115
|
+
schema: TSchema;
|
|
116
|
+
discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
117
|
+
} : {
|
|
118
|
+
schema: TSchema;
|
|
119
|
+
discriminator?: undefined;
|
|
120
|
+
}) & {
|
|
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
|
|
132
|
-
schema?:
|
|
133
|
-
name?:
|
|
134
|
-
discriminator?:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
313
|
+
* Type-safe field paths for React Hook Form with optional filtering and discriminated union support.
|
|
314
|
+
*
|
|
315
|
+
* Combines `ValidPaths` from `@zod-utils/core` with React Hook Form's `Path` type
|
|
316
|
+
* for full compatibility with form field APIs.
|
|
317
|
+
*
|
|
318
|
+
* @template TSchema - The Zod schema type
|
|
319
|
+
* @template TDiscriminatorKey - The discriminator key (for discriminated unions)
|
|
320
|
+
* @template TDiscriminatorValue - The discriminator value (for discriminated unions)
|
|
321
|
+
* @template TFilterType - The value type to filter by (default: unknown = no filtering)
|
|
322
|
+
* @template TStrict - Whether to use strict type matching (default: true)
|
|
323
|
+
* @template TFieldValues - The form field values type (inferred from schema)
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* Basic usage - all field paths
|
|
327
|
+
* ```typescript
|
|
328
|
+
* const schema = z.object({
|
|
329
|
+
* name: z.string(),
|
|
330
|
+
* age: z.number(),
|
|
331
|
+
* });
|
|
332
|
+
*
|
|
333
|
+
* type AllFields = ValidFieldPaths<typeof schema>;
|
|
334
|
+
* // "name" | "age"
|
|
335
|
+
* ```
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* Filter by type - string fields only
|
|
339
|
+
* ```typescript
|
|
340
|
+
* const schema = z.object({
|
|
341
|
+
* name: z.string(),
|
|
342
|
+
* age: z.number(),
|
|
343
|
+
* email: z.string().optional(),
|
|
344
|
+
* });
|
|
312
345
|
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
346
|
+
* type StringFields = ValidFieldPaths<typeof schema, never, never, string>;
|
|
347
|
+
* // "name" | "email"
|
|
348
|
+
* ```
|
|
315
349
|
*
|
|
316
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
|
|
327
|
-
* // "
|
|
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
|
|
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
|
|
369
|
+
* Type-safe field selector for React Hook Form with discriminated union support.
|
|
333
370
|
*
|
|
334
|
-
*
|
|
335
|
-
* `
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
366
|
-
* //
|
|
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
|
|
378
|
-
* //
|
|
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
|
|
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
|
|
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,51 @@ 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
|
-
|
|
583
|
+
/**
|
|
584
|
+
* Merges factory props with component props into a FormFieldSelector.
|
|
585
|
+
* Encapsulates type assertion so callers don't need eslint-disable.
|
|
586
|
+
*
|
|
587
|
+
* This is the React Hook Form specific version that uses ValidFieldPaths
|
|
588
|
+
* (which includes RHF's Path type constraint).
|
|
589
|
+
*
|
|
590
|
+
* @param factoryProps - Props from factory function (contains schema)
|
|
591
|
+
* @param componentProps - Props from component call (contains name, discriminator)
|
|
592
|
+
* @returns Properly typed FormFieldSelector
|
|
593
|
+
*
|
|
594
|
+
* @example
|
|
595
|
+
* ```typescript
|
|
596
|
+
* const selectorProps = mergeFormFieldSelectorProps(
|
|
597
|
+
* { schema: mySchema },
|
|
598
|
+
* { name: 'fieldName', discriminator: { key: 'mode', value: 'create' } }
|
|
599
|
+
* );
|
|
600
|
+
* ```
|
|
601
|
+
*/
|
|
602
|
+
declare function mergeFormFieldSelectorProps<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>(factoryProps: {
|
|
603
|
+
schema: TSchema;
|
|
604
|
+
}, componentProps: {
|
|
605
|
+
name: TPath;
|
|
606
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
607
|
+
}): FormFieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>;
|
|
608
|
+
/**
|
|
609
|
+
* Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.
|
|
610
|
+
*
|
|
611
|
+
* This is useful for `useMemo` and `useCallback` dependencies where you want to avoid
|
|
612
|
+
* re-running when object references change but values stay the same.
|
|
613
|
+
*
|
|
614
|
+
* @param params - The FieldSelector containing schema, name, and optional discriminator
|
|
615
|
+
* @returns An array of primitive values suitable for React dependency arrays
|
|
616
|
+
*
|
|
617
|
+
* @example
|
|
618
|
+
* ```tsx
|
|
619
|
+
* const memoizedValue = useMemo(() => {
|
|
620
|
+
* return extractFieldFromSchema(params);
|
|
621
|
+
* }, flattenFieldSelector(params));
|
|
622
|
+
* ```
|
|
623
|
+
*/
|
|
624
|
+
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> | {
|
|
625
|
+
schema?: undefined;
|
|
626
|
+
name?: undefined;
|
|
627
|
+
discriminator?: undefined;
|
|
628
|
+
}): any[];
|
|
629
|
+
|
|
630
|
+
export { type FormFieldSelector, FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, flattenFieldSelector, isRequiredField, mergeFormFieldSelectorProps, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { DiscriminatorKey, DiscriminatorValue,
|
|
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
|
|
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
|
|
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 ? {
|
|
66
61
|
schema: TSchema;
|
|
67
|
-
discriminator
|
|
68
|
-
}
|
|
62
|
+
discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
63
|
+
} : {
|
|
64
|
+
schema: TSchema;
|
|
65
|
+
discriminator?: undefined;
|
|
66
|
+
} : TSchema extends z.ZodDiscriminatedUnion ? {
|
|
67
|
+
schema: TSchema;
|
|
68
|
+
discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
69
|
+
} : {
|
|
70
|
+
schema: TSchema;
|
|
71
|
+
discriminator?: undefined;
|
|
72
|
+
}): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFilterType, TStrict>;
|
|
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 ? {
|
|
105
109
|
schema: TSchema;
|
|
106
|
-
discriminator
|
|
110
|
+
discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
111
|
+
} : {
|
|
112
|
+
schema: TSchema;
|
|
113
|
+
discriminator?: undefined;
|
|
114
|
+
} : TSchema extends z.ZodDiscriminatedUnion ? {
|
|
115
|
+
schema: TSchema;
|
|
116
|
+
discriminator: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
117
|
+
} : {
|
|
118
|
+
schema: TSchema;
|
|
119
|
+
discriminator?: undefined;
|
|
120
|
+
}) & {
|
|
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
|
|
132
|
-
schema?:
|
|
133
|
-
name?:
|
|
134
|
-
discriminator?:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
313
|
+
* Type-safe field paths for React Hook Form with optional filtering and discriminated union support.
|
|
314
|
+
*
|
|
315
|
+
* Combines `ValidPaths` from `@zod-utils/core` with React Hook Form's `Path` type
|
|
316
|
+
* for full compatibility with form field APIs.
|
|
317
|
+
*
|
|
318
|
+
* @template TSchema - The Zod schema type
|
|
319
|
+
* @template TDiscriminatorKey - The discriminator key (for discriminated unions)
|
|
320
|
+
* @template TDiscriminatorValue - The discriminator value (for discriminated unions)
|
|
321
|
+
* @template TFilterType - The value type to filter by (default: unknown = no filtering)
|
|
322
|
+
* @template TStrict - Whether to use strict type matching (default: true)
|
|
323
|
+
* @template TFieldValues - The form field values type (inferred from schema)
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* Basic usage - all field paths
|
|
327
|
+
* ```typescript
|
|
328
|
+
* const schema = z.object({
|
|
329
|
+
* name: z.string(),
|
|
330
|
+
* age: z.number(),
|
|
331
|
+
* });
|
|
332
|
+
*
|
|
333
|
+
* type AllFields = ValidFieldPaths<typeof schema>;
|
|
334
|
+
* // "name" | "age"
|
|
335
|
+
* ```
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* Filter by type - string fields only
|
|
339
|
+
* ```typescript
|
|
340
|
+
* const schema = z.object({
|
|
341
|
+
* name: z.string(),
|
|
342
|
+
* age: z.number(),
|
|
343
|
+
* email: z.string().optional(),
|
|
344
|
+
* });
|
|
312
345
|
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
346
|
+
* type StringFields = ValidFieldPaths<typeof schema, never, never, string>;
|
|
347
|
+
* // "name" | "email"
|
|
348
|
+
* ```
|
|
315
349
|
*
|
|
316
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
|
|
327
|
-
* // "
|
|
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
|
|
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
|
|
369
|
+
* Type-safe field selector for React Hook Form with discriminated union support.
|
|
333
370
|
*
|
|
334
|
-
*
|
|
335
|
-
* `
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
366
|
-
* //
|
|
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
|
|
378
|
-
* //
|
|
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
|
|
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
|
|
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,51 @@ 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
|
-
|
|
583
|
+
/**
|
|
584
|
+
* Merges factory props with component props into a FormFieldSelector.
|
|
585
|
+
* Encapsulates type assertion so callers don't need eslint-disable.
|
|
586
|
+
*
|
|
587
|
+
* This is the React Hook Form specific version that uses ValidFieldPaths
|
|
588
|
+
* (which includes RHF's Path type constraint).
|
|
589
|
+
*
|
|
590
|
+
* @param factoryProps - Props from factory function (contains schema)
|
|
591
|
+
* @param componentProps - Props from component call (contains name, discriminator)
|
|
592
|
+
* @returns Properly typed FormFieldSelector
|
|
593
|
+
*
|
|
594
|
+
* @example
|
|
595
|
+
* ```typescript
|
|
596
|
+
* const selectorProps = mergeFormFieldSelectorProps(
|
|
597
|
+
* { schema: mySchema },
|
|
598
|
+
* { name: 'fieldName', discriminator: { key: 'mode', value: 'create' } }
|
|
599
|
+
* );
|
|
600
|
+
* ```
|
|
601
|
+
*/
|
|
602
|
+
declare function mergeFormFieldSelectorProps<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>(factoryProps: {
|
|
603
|
+
schema: TSchema;
|
|
604
|
+
}, componentProps: {
|
|
605
|
+
name: TPath;
|
|
606
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
607
|
+
}): FormFieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>;
|
|
608
|
+
/**
|
|
609
|
+
* Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.
|
|
610
|
+
*
|
|
611
|
+
* This is useful for `useMemo` and `useCallback` dependencies where you want to avoid
|
|
612
|
+
* re-running when object references change but values stay the same.
|
|
613
|
+
*
|
|
614
|
+
* @param params - The FieldSelector containing schema, name, and optional discriminator
|
|
615
|
+
* @returns An array of primitive values suitable for React dependency arrays
|
|
616
|
+
*
|
|
617
|
+
* @example
|
|
618
|
+
* ```tsx
|
|
619
|
+
* const memoizedValue = useMemo(() => {
|
|
620
|
+
* return extractFieldFromSchema(params);
|
|
621
|
+
* }, flattenFieldSelector(params));
|
|
622
|
+
* ```
|
|
623
|
+
*/
|
|
624
|
+
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> | {
|
|
625
|
+
schema?: undefined;
|
|
626
|
+
name?: undefined;
|
|
627
|
+
discriminator?: undefined;
|
|
628
|
+
}): any[];
|
|
629
|
+
|
|
630
|
+
export { type FormFieldSelector, FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, flattenFieldSelector, isRequiredField, mergeFormFieldSelectorProps, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,18 @@ var __objRest = (source, exclude) => {
|
|
|
34
34
|
}
|
|
35
35
|
return target;
|
|
36
36
|
};
|
|
37
|
+
|
|
38
|
+
// src/utils.ts
|
|
39
|
+
function mergeFormFieldSelectorProps(factoryProps, componentProps) {
|
|
40
|
+
return __spreadValues(__spreadValues({}, factoryProps), componentProps);
|
|
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
|
+
}
|
|
37
49
|
var FormSchemaContext = react.createContext(null);
|
|
38
50
|
function useFormSchema(_params) {
|
|
39
51
|
return react.useContext(
|
|
@@ -48,53 +60,33 @@ function FormSchemaProvider({
|
|
|
48
60
|
}) {
|
|
49
61
|
return /* @__PURE__ */ jsxRuntime.jsx(FormSchemaContext.Provider, { value: { schema, discriminator }, children });
|
|
50
62
|
}
|
|
51
|
-
function useIsRequiredField({
|
|
52
|
-
schema,
|
|
53
|
-
name,
|
|
54
|
-
discriminator
|
|
55
|
-
}) {
|
|
63
|
+
function useIsRequiredField(params) {
|
|
56
64
|
return react.useMemo(() => {
|
|
57
|
-
if (!schema || !name) {
|
|
65
|
+
if (!params.schema || !params.name) {
|
|
58
66
|
return false;
|
|
59
67
|
}
|
|
60
|
-
return isRequiredField(
|
|
61
|
-
}, [
|
|
68
|
+
return isRequiredField(params);
|
|
69
|
+
}, [...flattenFieldSelector(params)]);
|
|
62
70
|
}
|
|
63
|
-
function isRequiredField({
|
|
64
|
-
|
|
65
|
-
name,
|
|
66
|
-
discriminator
|
|
67
|
-
}) {
|
|
68
|
-
const field = core.extractFieldFromSchema({
|
|
69
|
-
schema,
|
|
70
|
-
name,
|
|
71
|
-
discriminator
|
|
72
|
-
});
|
|
71
|
+
function isRequiredField(params) {
|
|
72
|
+
const field = core.extractFieldFromSchema(params);
|
|
73
73
|
if (!field) {
|
|
74
74
|
return false;
|
|
75
75
|
}
|
|
76
76
|
return core.requiresValidInput(field);
|
|
77
77
|
}
|
|
78
|
-
function useExtractFieldFromSchema({
|
|
79
|
-
schema,
|
|
80
|
-
name,
|
|
81
|
-
discriminator
|
|
82
|
-
}) {
|
|
78
|
+
function useExtractFieldFromSchema(params) {
|
|
83
79
|
return react.useMemo(
|
|
84
|
-
() => core.extractFieldFromSchema(
|
|
85
|
-
[
|
|
80
|
+
() => core.extractFieldFromSchema(params),
|
|
81
|
+
[...flattenFieldSelector(params)]
|
|
86
82
|
);
|
|
87
83
|
}
|
|
88
|
-
function useFieldChecks({
|
|
89
|
-
schema,
|
|
90
|
-
name,
|
|
91
|
-
discriminator
|
|
92
|
-
}) {
|
|
84
|
+
function useFieldChecks(params) {
|
|
93
85
|
return react.useMemo(() => {
|
|
94
|
-
const field = core.extractFieldFromSchema(
|
|
86
|
+
const field = core.extractFieldFromSchema(params);
|
|
95
87
|
if (!field) return [];
|
|
96
88
|
return core.getFieldChecks(field);
|
|
97
|
-
}, [
|
|
89
|
+
}, [...flattenFieldSelector(params)]);
|
|
98
90
|
}
|
|
99
91
|
var useZodForm = (_a) => {
|
|
100
92
|
var _b = _a, {
|
|
@@ -112,7 +104,9 @@ var useZodForm = (_a) => {
|
|
|
112
104
|
|
|
113
105
|
exports.FormSchemaContext = FormSchemaContext;
|
|
114
106
|
exports.FormSchemaProvider = FormSchemaProvider;
|
|
107
|
+
exports.flattenFieldSelector = flattenFieldSelector;
|
|
115
108
|
exports.isRequiredField = isRequiredField;
|
|
109
|
+
exports.mergeFormFieldSelectorProps = mergeFormFieldSelectorProps;
|
|
116
110
|
exports.useExtractFieldFromSchema = useExtractFieldFromSchema;
|
|
117
111
|
exports.useFieldChecks = useFieldChecks;
|
|
118
112
|
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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCO,SAAS,2BAAA,CAiBd,cACA,cAAA,EAgBA;AAEA,EAAA,OAAO,kCAAK,YAAA,CAAA,EAAiB,cAAA,CAAA;AAS/B;AAkBO,SAAS,qBAcd,MAAA,EAcA;AACA,EAAA,MAAmC,aAA3B,EAAA,aAAA,EA7HV,GA6HqC,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;AC7CO,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 Discriminator,\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 * Merges factory props with component props into a FormFieldSelector.\n * Encapsulates type assertion so callers don't need eslint-disable.\n *\n * This is the React Hook Form specific version that uses ValidFieldPaths\n * (which includes RHF's Path type constraint).\n *\n * @param factoryProps - Props from factory function (contains schema)\n * @param componentProps - Props from component call (contains name, discriminator)\n * @returns Properly typed FormFieldSelector\n *\n * @example\n * ```typescript\n * const selectorProps = mergeFormFieldSelectorProps(\n * { schema: mySchema },\n * { name: 'fieldName', discriminator: { key: 'mode', value: 'create' } }\n * );\n * ```\n */\nexport function mergeFormFieldSelectorProps<\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>(\n factoryProps: { schema: TSchema },\n componentProps: {\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n },\n): FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n> {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return { ...factoryProps, ...componentProps } 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,18 @@ var __objRest = (source, exclude) => {
|
|
|
33
33
|
}
|
|
34
34
|
return target;
|
|
35
35
|
};
|
|
36
|
+
|
|
37
|
+
// src/utils.ts
|
|
38
|
+
function mergeFormFieldSelectorProps(factoryProps, componentProps) {
|
|
39
|
+
return __spreadValues(__spreadValues({}, factoryProps), componentProps);
|
|
40
|
+
}
|
|
41
|
+
function flattenFieldSelector(params) {
|
|
42
|
+
const _a = params, { discriminator } = _a, rest = __objRest(_a, ["discriminator"]);
|
|
43
|
+
return [
|
|
44
|
+
...Object.values(rest),
|
|
45
|
+
...discriminator ? Object.values(discriminator) : []
|
|
46
|
+
];
|
|
47
|
+
}
|
|
36
48
|
var FormSchemaContext = createContext(null);
|
|
37
49
|
function useFormSchema(_params) {
|
|
38
50
|
return useContext(
|
|
@@ -47,53 +59,33 @@ function FormSchemaProvider({
|
|
|
47
59
|
}) {
|
|
48
60
|
return /* @__PURE__ */ jsx(FormSchemaContext.Provider, { value: { schema, discriminator }, children });
|
|
49
61
|
}
|
|
50
|
-
function useIsRequiredField({
|
|
51
|
-
schema,
|
|
52
|
-
name,
|
|
53
|
-
discriminator
|
|
54
|
-
}) {
|
|
62
|
+
function useIsRequiredField(params) {
|
|
55
63
|
return useMemo(() => {
|
|
56
|
-
if (!schema || !name) {
|
|
64
|
+
if (!params.schema || !params.name) {
|
|
57
65
|
return false;
|
|
58
66
|
}
|
|
59
|
-
return isRequiredField(
|
|
60
|
-
}, [
|
|
67
|
+
return isRequiredField(params);
|
|
68
|
+
}, [...flattenFieldSelector(params)]);
|
|
61
69
|
}
|
|
62
|
-
function isRequiredField({
|
|
63
|
-
|
|
64
|
-
name,
|
|
65
|
-
discriminator
|
|
66
|
-
}) {
|
|
67
|
-
const field = extractFieldFromSchema({
|
|
68
|
-
schema,
|
|
69
|
-
name,
|
|
70
|
-
discriminator
|
|
71
|
-
});
|
|
70
|
+
function isRequiredField(params) {
|
|
71
|
+
const field = extractFieldFromSchema(params);
|
|
72
72
|
if (!field) {
|
|
73
73
|
return false;
|
|
74
74
|
}
|
|
75
75
|
return requiresValidInput(field);
|
|
76
76
|
}
|
|
77
|
-
function useExtractFieldFromSchema({
|
|
78
|
-
schema,
|
|
79
|
-
name,
|
|
80
|
-
discriminator
|
|
81
|
-
}) {
|
|
77
|
+
function useExtractFieldFromSchema(params) {
|
|
82
78
|
return useMemo(
|
|
83
|
-
() => extractFieldFromSchema(
|
|
84
|
-
[
|
|
79
|
+
() => extractFieldFromSchema(params),
|
|
80
|
+
[...flattenFieldSelector(params)]
|
|
85
81
|
);
|
|
86
82
|
}
|
|
87
|
-
function useFieldChecks({
|
|
88
|
-
schema,
|
|
89
|
-
name,
|
|
90
|
-
discriminator
|
|
91
|
-
}) {
|
|
83
|
+
function useFieldChecks(params) {
|
|
92
84
|
return useMemo(() => {
|
|
93
|
-
const field = extractFieldFromSchema(
|
|
85
|
+
const field = extractFieldFromSchema(params);
|
|
94
86
|
if (!field) return [];
|
|
95
87
|
return getFieldChecks(field);
|
|
96
|
-
}, [
|
|
88
|
+
}, [...flattenFieldSelector(params)]);
|
|
97
89
|
}
|
|
98
90
|
var useZodForm = (_a) => {
|
|
99
91
|
var _b = _a, {
|
|
@@ -109,6 +101,6 @@ var useZodForm = (_a) => {
|
|
|
109
101
|
}, formOptions));
|
|
110
102
|
};
|
|
111
103
|
|
|
112
|
-
export { FormSchemaContext, FormSchemaProvider, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
|
|
104
|
+
export { FormSchemaContext, FormSchemaProvider, flattenFieldSelector, isRequiredField, mergeFormFieldSelectorProps, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
|
|
113
105
|
//# sourceMappingURL=index.mjs.map
|
|
114
106
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCO,SAAS,2BAAA,CAiBd,cACA,cAAA,EAgBA;AAEA,EAAA,OAAO,kCAAK,YAAA,CAAA,EAAiB,cAAA,CAAA;AAS/B;AAkBO,SAAS,qBAcd,MAAA,EAcA;AACA,EAAA,MAAmC,aAA3B,EAAA,aAAA,EA7HV,GA6HqC,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;AC7CO,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 Discriminator,\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 * Merges factory props with component props into a FormFieldSelector.\n * Encapsulates type assertion so callers don't need eslint-disable.\n *\n * This is the React Hook Form specific version that uses ValidFieldPaths\n * (which includes RHF's Path type constraint).\n *\n * @param factoryProps - Props from factory function (contains schema)\n * @param componentProps - Props from component call (contains name, discriminator)\n * @returns Properly typed FormFieldSelector\n *\n * @example\n * ```typescript\n * const selectorProps = mergeFormFieldSelectorProps(\n * { schema: mySchema },\n * { name: 'fieldName', discriminator: { key: 'mode', value: 'create' } }\n * );\n * ```\n */\nexport function mergeFormFieldSelectorProps<\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>(\n factoryProps: { schema: TSchema },\n componentProps: {\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n },\n): FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n> {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return { ...factoryProps, ...componentProps } 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"]}
|