@zod-utils/react-hook-form 0.10.0 → 0.12.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 +129 -2
- package/dist/index.d.mts +214 -6
- package/dist/index.d.ts +214 -6
- package/dist/index.js +55 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +52 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -63,6 +63,9 @@ npm install @zod-utils/react-hook-form zod react react-hook-form @hookform/resol
|
|
|
63
63
|
## Features
|
|
64
64
|
|
|
65
65
|
- 🎣 **useZodForm** - Automatic type transformation for form inputs (nullable/undefined) while preserving Zod schema validation
|
|
66
|
+
- 📋 **FormSchemaProvider** - React Context for providing schema to form components
|
|
67
|
+
- ✅ **useIsRequiredField** - Hook to check if a field requires valid input
|
|
68
|
+
- 🔄 **Discriminated Union Support** - Full type-safe support for discriminated unions
|
|
66
69
|
- 📦 **All core utilities** - Re-exports everything from `@zod-utils/core`
|
|
67
70
|
- ⚛️ **React-optimized** - Built specifically for React applications
|
|
68
71
|
|
|
@@ -136,6 +139,7 @@ const form = useZodForm({
|
|
|
136
139
|
- **Output validation**: Validated data matches your Zod schema exactly
|
|
137
140
|
- **Type inference**: No manual type annotations needed - everything is inferred from the schema
|
|
138
141
|
- **Zod integration**: Automatically sets up `zodResolver` for validation
|
|
142
|
+
- **Transform support**: Works with schemas that use `.transform()` - uses input types for form fields
|
|
139
143
|
|
|
140
144
|
#### Using Without Default Values
|
|
141
145
|
|
|
@@ -272,6 +276,116 @@ const form2 = useZodForm<
|
|
|
272
276
|
|
|
273
277
|
---
|
|
274
278
|
|
|
279
|
+
## Form Schema Context
|
|
280
|
+
|
|
281
|
+
The Form Schema Context system allows you to provide Zod schema context to deeply nested form components without prop drilling.
|
|
282
|
+
|
|
283
|
+
### `FormSchemaProvider`
|
|
284
|
+
|
|
285
|
+
Provides schema context to all child components. Use this to wrap your form.
|
|
286
|
+
|
|
287
|
+
```tsx
|
|
288
|
+
import { FormSchemaProvider } from "@zod-utils/react-hook-form";
|
|
289
|
+
import { z } from "zod";
|
|
290
|
+
|
|
291
|
+
const schema = z.object({
|
|
292
|
+
username: z.string().min(3).max(20),
|
|
293
|
+
email: z.string().email(),
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
function MyForm() {
|
|
297
|
+
return (
|
|
298
|
+
<FormSchemaProvider schema={schema}>
|
|
299
|
+
<form>
|
|
300
|
+
<UsernameField />
|
|
301
|
+
<EmailField />
|
|
302
|
+
</form>
|
|
303
|
+
</FormSchemaProvider>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
#### With Discriminated Union
|
|
309
|
+
|
|
310
|
+
For discriminated unions, pass the discriminator to enable type-safe field access:
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
const schema = z.discriminatedUnion("mode", [
|
|
314
|
+
z.object({ mode: z.literal("create"), name: z.string().min(1) }),
|
|
315
|
+
z.object({ mode: z.literal("edit"), id: z.number() }),
|
|
316
|
+
]);
|
|
317
|
+
|
|
318
|
+
function CreateModeForm() {
|
|
319
|
+
return (
|
|
320
|
+
<FormSchemaProvider
|
|
321
|
+
schema={schema}
|
|
322
|
+
discriminator={{ key: "mode", value: "create" }}
|
|
323
|
+
>
|
|
324
|
+
<NameField /> {/* Only fields from 'create' variant are available */}
|
|
325
|
+
</FormSchemaProvider>
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### `useFormSchema()`
|
|
331
|
+
|
|
332
|
+
Access the schema context from child components:
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
import { useFormSchema } from "@zod-utils/react-hook-form";
|
|
336
|
+
|
|
337
|
+
function FieldComponent() {
|
|
338
|
+
const context = useFormSchema();
|
|
339
|
+
if (!context) return null;
|
|
340
|
+
|
|
341
|
+
const { schema, discriminator } = context;
|
|
342
|
+
// Use schema for field-level logic
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### `useIsRequiredField({ schema, fieldName, discriminator? })`
|
|
347
|
+
|
|
348
|
+
Hook to check if a field requires valid input (shows validation errors on submit).
|
|
349
|
+
The schema parameter is used for type inference only - the actual schema is retrieved from context.
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
import { useIsRequiredField } from "@zod-utils/react-hook-form";
|
|
353
|
+
|
|
354
|
+
function FormLabel({ name, schema }: { name: string; schema: z.ZodType }) {
|
|
355
|
+
const isRequired = useIsRequiredField({ schema, fieldName: name });
|
|
356
|
+
|
|
357
|
+
return (
|
|
358
|
+
<label>
|
|
359
|
+
{name}
|
|
360
|
+
{isRequired && <span className="text-red-500">*</span>}
|
|
361
|
+
</label>
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### `isRequiredField({ schema, fieldName, discriminator? })`
|
|
367
|
+
|
|
368
|
+
Standalone function to check if a field requires valid input:
|
|
369
|
+
|
|
370
|
+
```tsx
|
|
371
|
+
import { isRequiredField } from "@zod-utils/react-hook-form";
|
|
372
|
+
import { z } from "zod";
|
|
373
|
+
|
|
374
|
+
const schema = z.object({
|
|
375
|
+
username: z.string().min(1), // Required - min(1) rejects empty
|
|
376
|
+
email: z.string(), // Not required - accepts empty string
|
|
377
|
+
age: z.number(), // Required - numbers reject empty input
|
|
378
|
+
bio: z.string().optional(), // Not required - optional
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
isRequiredField({ schema, fieldName: "username" }); // true
|
|
382
|
+
isRequiredField({ schema, fieldName: "email" }); // false
|
|
383
|
+
isRequiredField({ schema, fieldName: "age" }); // true
|
|
384
|
+
isRequiredField({ schema, fieldName: "bio" }); // false
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
---
|
|
388
|
+
|
|
275
389
|
## Core Utilities (Re-exported)
|
|
276
390
|
|
|
277
391
|
All utilities from `@zod-utils/core` are re-exported for convenience:
|
|
@@ -283,12 +397,25 @@ import {
|
|
|
283
397
|
requiresValidInput,
|
|
284
398
|
getPrimitiveType,
|
|
285
399
|
removeDefault,
|
|
286
|
-
|
|
400
|
+
extractDefaultValue,
|
|
287
401
|
type Simplify,
|
|
402
|
+
type ZodUnionCheck,
|
|
403
|
+
|
|
404
|
+
// Form schema context
|
|
405
|
+
FormSchemaContext,
|
|
406
|
+
FormSchemaProvider,
|
|
407
|
+
useFormSchema,
|
|
408
|
+
useIsRequiredField,
|
|
409
|
+
isRequiredField,
|
|
288
410
|
|
|
289
|
-
// Type utilities
|
|
411
|
+
// Type utilities
|
|
290
412
|
type PartialWithNullableObjects,
|
|
291
413
|
type PartialWithAllNullables,
|
|
414
|
+
type Discriminator,
|
|
415
|
+
type DiscriminatorKey,
|
|
416
|
+
type DiscriminatorValue,
|
|
417
|
+
type InferredFieldValues,
|
|
418
|
+
type ValidFieldName,
|
|
292
419
|
} from "@zod-utils/react-hook-form";
|
|
293
420
|
```
|
|
294
421
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,9 +1,185 @@
|
|
|
1
|
-
import { Simplify } from '@zod-utils/core';
|
|
1
|
+
import { DiscriminatorKey, DiscriminatorValue, Discriminator, Simplify } from '@zod-utils/core';
|
|
2
2
|
export * from '@zod-utils/core';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
import { Context, ReactNode } from 'react';
|
|
5
|
+
import { z } from 'zod';
|
|
3
6
|
import * as react_hook_form from 'react-hook-form';
|
|
4
|
-
import { FieldValues, UseFormProps } from 'react-hook-form';
|
|
7
|
+
import { FieldValues, Path, UseFormProps } from 'react-hook-form';
|
|
5
8
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
6
|
-
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Type for the FormSchemaContext with full generic support.
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
type FormSchemaContextType<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = Context<{
|
|
15
|
+
schema: TSchema;
|
|
16
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
17
|
+
} | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Context value type for FormSchemaContext.
|
|
20
|
+
*/
|
|
21
|
+
type FormSchemaContextValue<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = {
|
|
22
|
+
schema: TSchema;
|
|
23
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
24
|
+
} | null;
|
|
25
|
+
/**
|
|
26
|
+
* React Context for providing Zod schema to form components.
|
|
27
|
+
*
|
|
28
|
+
* Use with {@link FormSchemaProvider} to provide schema context, and
|
|
29
|
+
* {@link useFormSchema} to consume it in child components.
|
|
30
|
+
*/
|
|
31
|
+
declare const FormSchemaContext: Context<{
|
|
32
|
+
schema: z.ZodType;
|
|
33
|
+
discriminator?: {
|
|
34
|
+
key: unknown;
|
|
35
|
+
value: unknown;
|
|
36
|
+
};
|
|
37
|
+
} | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Hook to access the form schema from context.
|
|
40
|
+
*
|
|
41
|
+
* The optional `_params` argument is used for TypeScript type inference only.
|
|
42
|
+
* Pass your schema to get proper type narrowing of the context value.
|
|
43
|
+
*
|
|
44
|
+
* @param _params - Optional params for type inference (not used at runtime)
|
|
45
|
+
* @returns The schema context value or null if not within a provider
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* // Without type params (returns generic context)
|
|
50
|
+
* function MyFormField() {
|
|
51
|
+
* const context = useFormSchema();
|
|
52
|
+
* if (!context) return null;
|
|
53
|
+
*
|
|
54
|
+
* const { schema, discriminator } = context;
|
|
55
|
+
* // Use schema for validation or field extraction
|
|
56
|
+
* }
|
|
57
|
+
*
|
|
58
|
+
* // With type params (for type-safe schema access)
|
|
59
|
+
* function TypedFormField() {
|
|
60
|
+
* const context = useFormSchema({ schema: mySchema });
|
|
61
|
+
* // context.schema is now typed as typeof mySchema
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = DiscriminatorValue<TSchema, TDiscriminatorKey>>(_params?: {
|
|
66
|
+
schema: TSchema;
|
|
67
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
68
|
+
}): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
69
|
+
/**
|
|
70
|
+
* Provider component that makes Zod schema available to all child components.
|
|
71
|
+
*
|
|
72
|
+
* Use this to wrap your form and provide schema context to nested components
|
|
73
|
+
* like field labels and validation indicators.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* Basic usage with ZodObject
|
|
77
|
+
* ```tsx
|
|
78
|
+
* const schema = z.object({
|
|
79
|
+
* name: z.string(),
|
|
80
|
+
* email: z.string().email().optional()
|
|
81
|
+
* });
|
|
82
|
+
*
|
|
83
|
+
* <FormSchemaProvider schema={schema}>
|
|
84
|
+
* <YourFormComponents />
|
|
85
|
+
* </FormSchemaProvider>
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* Usage with discriminated union
|
|
90
|
+
* ```tsx
|
|
91
|
+
* const schema = z.discriminatedUnion('mode', [
|
|
92
|
+
* z.object({ mode: z.literal('create'), name: z.string() }),
|
|
93
|
+
* z.object({ mode: z.literal('edit'), id: z.number() })
|
|
94
|
+
* ]);
|
|
95
|
+
*
|
|
96
|
+
* <FormSchemaProvider
|
|
97
|
+
* schema={schema}
|
|
98
|
+
* discriminator={{ key: 'mode', value: 'create' }}
|
|
99
|
+
* >
|
|
100
|
+
* <YourFormComponents />
|
|
101
|
+
* </FormSchemaProvider>
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, discriminator, children, }: {
|
|
105
|
+
schema: TSchema;
|
|
106
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
107
|
+
children: ReactNode;
|
|
108
|
+
}): react_jsx_runtime.JSX.Element;
|
|
109
|
+
/**
|
|
110
|
+
* Hook to check if a field requires valid input based on the Zod schema.
|
|
111
|
+
*
|
|
112
|
+
* Uses the schema from {@link FormSchemaContext} to determine if a field
|
|
113
|
+
* will show validation errors when submitted with empty/invalid input.
|
|
114
|
+
*
|
|
115
|
+
* @param params - Schema, field name, and optional discriminator (schema used for type inference)
|
|
116
|
+
* @returns true if the field requires valid input, false otherwise
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```tsx
|
|
120
|
+
* function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {
|
|
121
|
+
* const isRequired = useIsRequiredField({ schema, fieldName: name });
|
|
122
|
+
*
|
|
123
|
+
* return (
|
|
124
|
+
* <label>
|
|
125
|
+
* {name}
|
|
126
|
+
* {isRequired && <span className="text-red-500">*</span>}
|
|
127
|
+
* </label>
|
|
128
|
+
* );
|
|
129
|
+
* }
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
declare function useIsRequiredField<TSchema extends z.ZodType, TName extends keyof Extract<Required<z.input<TSchema>>, Record<TDiscriminatorKey, TDiscriminatorValue>>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ fieldName, ...props }: {
|
|
133
|
+
schema: TSchema;
|
|
134
|
+
fieldName: TName;
|
|
135
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
136
|
+
}): boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Determines if a field requires valid input (will show validation errors on empty/invalid input).
|
|
139
|
+
*
|
|
140
|
+
* Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after
|
|
141
|
+
* removing defaults. This tells you if the field will error when user submits empty input.
|
|
142
|
+
*
|
|
143
|
+
* Returns false if the underlying field accepts:
|
|
144
|
+
* - `undefined` (via `.optional()`)
|
|
145
|
+
* - `null` (via `.nullable()`)
|
|
146
|
+
* - Empty strings (plain `z.string()` without `.min(1)`)
|
|
147
|
+
* - Empty arrays (plain `z.array()` without `.min(1)`)
|
|
148
|
+
*
|
|
149
|
+
* @param options - Schema, field name, and optional discriminator
|
|
150
|
+
* @returns true if the field requires valid input, false otherwise
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* const schema = z.object({
|
|
155
|
+
* name: z.string().min(1),
|
|
156
|
+
* bio: z.string().optional(),
|
|
157
|
+
* });
|
|
158
|
+
*
|
|
159
|
+
* isRequiredField({ schema, fieldName: 'name' }); // true
|
|
160
|
+
* isRequiredField({ schema, fieldName: 'bio' }); // false
|
|
161
|
+
* ```
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* With discriminated union
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const schema = z.discriminatedUnion('mode', [
|
|
167
|
+
* z.object({ mode: z.literal('create'), name: z.string().min(1) }),
|
|
168
|
+
* z.object({ mode: z.literal('edit'), id: z.number() }),
|
|
169
|
+
* ]);
|
|
170
|
+
*
|
|
171
|
+
* isRequiredField({
|
|
172
|
+
* schema,
|
|
173
|
+
* fieldName: 'name',
|
|
174
|
+
* discriminator: { key: 'mode', value: 'create' },
|
|
175
|
+
* }); // true
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
declare function isRequiredField<TSchema extends z.ZodType, TName extends keyof Extract<Required<z.input<TSchema>>, Record<TDiscriminatorKey, TDiscriminatorValue>>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, fieldName, discriminator, }: {
|
|
179
|
+
schema: TSchema;
|
|
180
|
+
fieldName: TName;
|
|
181
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
182
|
+
}): boolean;
|
|
7
183
|
|
|
8
184
|
/**
|
|
9
185
|
* Helper type that adds `null` to object-type fields only (excludes arrays).
|
|
@@ -52,6 +228,38 @@ type PartialWithNullableObjects<T> = Simplify<Partial<AddNullToObjects<T>>>;
|
|
|
52
228
|
type PartialWithAllNullables<T> = {
|
|
53
229
|
[K in keyof T]?: T[K] | null;
|
|
54
230
|
};
|
|
231
|
+
/**
|
|
232
|
+
* Infers field values from a Zod schema compatible with React Hook Form's FieldValues.
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```typescript
|
|
236
|
+
* const schema = z.object({ name: z.string() });
|
|
237
|
+
* type Values = InferredFieldValues<typeof schema>;
|
|
238
|
+
* // { name: string } & FieldValues
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
type InferredFieldValues<TSchema extends z.ZodType> = z.input<TSchema> & FieldValues;
|
|
242
|
+
/**
|
|
243
|
+
* Type-safe field names for a specific discriminator value.
|
|
244
|
+
*
|
|
245
|
+
* Narrows field names to only those that exist for the given discriminator value
|
|
246
|
+
* in a discriminated union schema.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```typescript
|
|
250
|
+
* const schema = z.discriminatedUnion('mode', [
|
|
251
|
+
* z.object({ mode: z.literal('create'), name: z.string() }),
|
|
252
|
+
* z.object({ mode: z.literal('edit'), id: z.number() }),
|
|
253
|
+
* ]);
|
|
254
|
+
*
|
|
255
|
+
* type CreateFields = ValidFieldName<typeof schema, 'mode', 'create'>;
|
|
256
|
+
* // "mode" | "name"
|
|
257
|
+
*
|
|
258
|
+
* type EditFields = ValidFieldName<typeof schema, 'mode', 'edit'>;
|
|
259
|
+
* // "mode" | "id"
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
type ValidFieldName<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>> = keyof Extract<Required<z.input<TSchema>>, Record<TDiscriminatorKey, TDiscriminatorValue>> & Path<TFieldValues>;
|
|
55
263
|
|
|
56
264
|
/**
|
|
57
265
|
* Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.
|
|
@@ -217,10 +425,10 @@ type PartialWithAllNullables<T> = {
|
|
|
217
425
|
* @see https://zod.dev for Zod schema documentation
|
|
218
426
|
* @since 0.1.0
|
|
219
427
|
*/
|
|
220
|
-
declare const useZodForm: <TOutput extends FieldValues, TFormInput extends PartialWithAllNullables<
|
|
428
|
+
declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValues, TFormInput extends PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>>({ schema, zodResolverOptions, ...formOptions }: {
|
|
221
429
|
schema: z.ZodType<TOutput, TInput>;
|
|
222
|
-
defaultValues?:
|
|
430
|
+
defaultValues?: TFormInput;
|
|
223
431
|
zodResolverOptions?: Parameters<typeof zodResolver>[1];
|
|
224
432
|
} & Omit<UseFormProps<TFormInput, unknown, TOutput>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<TFormInput, unknown, TOutput>;
|
|
225
433
|
|
|
226
|
-
export { type PartialWithAllNullables, type PartialWithNullableObjects, useZodForm };
|
|
434
|
+
export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldName, isRequiredField, useFormSchema, useIsRequiredField, useZodForm };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,185 @@
|
|
|
1
|
-
import { Simplify } from '@zod-utils/core';
|
|
1
|
+
import { DiscriminatorKey, DiscriminatorValue, Discriminator, Simplify } from '@zod-utils/core';
|
|
2
2
|
export * from '@zod-utils/core';
|
|
3
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
4
|
+
import { Context, ReactNode } from 'react';
|
|
5
|
+
import { z } from 'zod';
|
|
3
6
|
import * as react_hook_form from 'react-hook-form';
|
|
4
|
-
import { FieldValues, UseFormProps } from 'react-hook-form';
|
|
7
|
+
import { FieldValues, Path, UseFormProps } from 'react-hook-form';
|
|
5
8
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
6
|
-
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Type for the FormSchemaContext with full generic support.
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
type FormSchemaContextType<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = Context<{
|
|
15
|
+
schema: TSchema;
|
|
16
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
17
|
+
} | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Context value type for FormSchemaContext.
|
|
20
|
+
*/
|
|
21
|
+
type FormSchemaContextValue<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>> = {
|
|
22
|
+
schema: TSchema;
|
|
23
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
24
|
+
} | null;
|
|
25
|
+
/**
|
|
26
|
+
* React Context for providing Zod schema to form components.
|
|
27
|
+
*
|
|
28
|
+
* Use with {@link FormSchemaProvider} to provide schema context, and
|
|
29
|
+
* {@link useFormSchema} to consume it in child components.
|
|
30
|
+
*/
|
|
31
|
+
declare const FormSchemaContext: Context<{
|
|
32
|
+
schema: z.ZodType;
|
|
33
|
+
discriminator?: {
|
|
34
|
+
key: unknown;
|
|
35
|
+
value: unknown;
|
|
36
|
+
};
|
|
37
|
+
} | null>;
|
|
38
|
+
/**
|
|
39
|
+
* Hook to access the form schema from context.
|
|
40
|
+
*
|
|
41
|
+
* The optional `_params` argument is used for TypeScript type inference only.
|
|
42
|
+
* Pass your schema to get proper type narrowing of the context value.
|
|
43
|
+
*
|
|
44
|
+
* @param _params - Optional params for type inference (not used at runtime)
|
|
45
|
+
* @returns The schema context value or null if not within a provider
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* // Without type params (returns generic context)
|
|
50
|
+
* function MyFormField() {
|
|
51
|
+
* const context = useFormSchema();
|
|
52
|
+
* if (!context) return null;
|
|
53
|
+
*
|
|
54
|
+
* const { schema, discriminator } = context;
|
|
55
|
+
* // Use schema for validation or field extraction
|
|
56
|
+
* }
|
|
57
|
+
*
|
|
58
|
+
* // With type params (for type-safe schema access)
|
|
59
|
+
* function TypedFormField() {
|
|
60
|
+
* const context = useFormSchema({ schema: mySchema });
|
|
61
|
+
* // context.schema is now typed as typeof mySchema
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
declare function useFormSchema<TSchema extends z.ZodType = z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey> = DiscriminatorValue<TSchema, TDiscriminatorKey>>(_params?: {
|
|
66
|
+
schema: TSchema;
|
|
67
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
68
|
+
}): FormSchemaContextValue<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
69
|
+
/**
|
|
70
|
+
* Provider component that makes Zod schema available to all child components.
|
|
71
|
+
*
|
|
72
|
+
* Use this to wrap your form and provide schema context to nested components
|
|
73
|
+
* like field labels and validation indicators.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* Basic usage with ZodObject
|
|
77
|
+
* ```tsx
|
|
78
|
+
* const schema = z.object({
|
|
79
|
+
* name: z.string(),
|
|
80
|
+
* email: z.string().email().optional()
|
|
81
|
+
* });
|
|
82
|
+
*
|
|
83
|
+
* <FormSchemaProvider schema={schema}>
|
|
84
|
+
* <YourFormComponents />
|
|
85
|
+
* </FormSchemaProvider>
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* Usage with discriminated union
|
|
90
|
+
* ```tsx
|
|
91
|
+
* const schema = z.discriminatedUnion('mode', [
|
|
92
|
+
* z.object({ mode: z.literal('create'), name: z.string() }),
|
|
93
|
+
* z.object({ mode: z.literal('edit'), id: z.number() })
|
|
94
|
+
* ]);
|
|
95
|
+
*
|
|
96
|
+
* <FormSchemaProvider
|
|
97
|
+
* schema={schema}
|
|
98
|
+
* discriminator={{ key: 'mode', value: 'create' }}
|
|
99
|
+
* >
|
|
100
|
+
* <YourFormComponents />
|
|
101
|
+
* </FormSchemaProvider>
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, discriminator, children, }: {
|
|
105
|
+
schema: TSchema;
|
|
106
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
107
|
+
children: ReactNode;
|
|
108
|
+
}): react_jsx_runtime.JSX.Element;
|
|
109
|
+
/**
|
|
110
|
+
* Hook to check if a field requires valid input based on the Zod schema.
|
|
111
|
+
*
|
|
112
|
+
* Uses the schema from {@link FormSchemaContext} to determine if a field
|
|
113
|
+
* will show validation errors when submitted with empty/invalid input.
|
|
114
|
+
*
|
|
115
|
+
* @param params - Schema, field name, and optional discriminator (schema used for type inference)
|
|
116
|
+
* @returns true if the field requires valid input, false otherwise
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```tsx
|
|
120
|
+
* function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {
|
|
121
|
+
* const isRequired = useIsRequiredField({ schema, fieldName: name });
|
|
122
|
+
*
|
|
123
|
+
* return (
|
|
124
|
+
* <label>
|
|
125
|
+
* {name}
|
|
126
|
+
* {isRequired && <span className="text-red-500">*</span>}
|
|
127
|
+
* </label>
|
|
128
|
+
* );
|
|
129
|
+
* }
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
declare function useIsRequiredField<TSchema extends z.ZodType, TName extends keyof Extract<Required<z.input<TSchema>>, Record<TDiscriminatorKey, TDiscriminatorValue>>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ fieldName, ...props }: {
|
|
133
|
+
schema: TSchema;
|
|
134
|
+
fieldName: TName;
|
|
135
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
136
|
+
}): boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Determines if a field requires valid input (will show validation errors on empty/invalid input).
|
|
139
|
+
*
|
|
140
|
+
* Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after
|
|
141
|
+
* removing defaults. This tells you if the field will error when user submits empty input.
|
|
142
|
+
*
|
|
143
|
+
* Returns false if the underlying field accepts:
|
|
144
|
+
* - `undefined` (via `.optional()`)
|
|
145
|
+
* - `null` (via `.nullable()`)
|
|
146
|
+
* - Empty strings (plain `z.string()` without `.min(1)`)
|
|
147
|
+
* - Empty arrays (plain `z.array()` without `.min(1)`)
|
|
148
|
+
*
|
|
149
|
+
* @param options - Schema, field name, and optional discriminator
|
|
150
|
+
* @returns true if the field requires valid input, false otherwise
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* const schema = z.object({
|
|
155
|
+
* name: z.string().min(1),
|
|
156
|
+
* bio: z.string().optional(),
|
|
157
|
+
* });
|
|
158
|
+
*
|
|
159
|
+
* isRequiredField({ schema, fieldName: 'name' }); // true
|
|
160
|
+
* isRequiredField({ schema, fieldName: 'bio' }); // false
|
|
161
|
+
* ```
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* With discriminated union
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const schema = z.discriminatedUnion('mode', [
|
|
167
|
+
* z.object({ mode: z.literal('create'), name: z.string().min(1) }),
|
|
168
|
+
* z.object({ mode: z.literal('edit'), id: z.number() }),
|
|
169
|
+
* ]);
|
|
170
|
+
*
|
|
171
|
+
* isRequiredField({
|
|
172
|
+
* schema,
|
|
173
|
+
* fieldName: 'name',
|
|
174
|
+
* discriminator: { key: 'mode', value: 'create' },
|
|
175
|
+
* }); // true
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
declare function isRequiredField<TSchema extends z.ZodType, TName extends keyof Extract<Required<z.input<TSchema>>, Record<TDiscriminatorKey, TDiscriminatorValue>>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, fieldName, discriminator, }: {
|
|
179
|
+
schema: TSchema;
|
|
180
|
+
fieldName: TName;
|
|
181
|
+
discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
|
|
182
|
+
}): boolean;
|
|
7
183
|
|
|
8
184
|
/**
|
|
9
185
|
* Helper type that adds `null` to object-type fields only (excludes arrays).
|
|
@@ -52,6 +228,38 @@ type PartialWithNullableObjects<T> = Simplify<Partial<AddNullToObjects<T>>>;
|
|
|
52
228
|
type PartialWithAllNullables<T> = {
|
|
53
229
|
[K in keyof T]?: T[K] | null;
|
|
54
230
|
};
|
|
231
|
+
/**
|
|
232
|
+
* Infers field values from a Zod schema compatible with React Hook Form's FieldValues.
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* ```typescript
|
|
236
|
+
* const schema = z.object({ name: z.string() });
|
|
237
|
+
* type Values = InferredFieldValues<typeof schema>;
|
|
238
|
+
* // { name: string } & FieldValues
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
type InferredFieldValues<TSchema extends z.ZodType> = z.input<TSchema> & FieldValues;
|
|
242
|
+
/**
|
|
243
|
+
* Type-safe field names for a specific discriminator value.
|
|
244
|
+
*
|
|
245
|
+
* Narrows field names to only those that exist for the given discriminator value
|
|
246
|
+
* in a discriminated union schema.
|
|
247
|
+
*
|
|
248
|
+
* @example
|
|
249
|
+
* ```typescript
|
|
250
|
+
* const schema = z.discriminatedUnion('mode', [
|
|
251
|
+
* z.object({ mode: z.literal('create'), name: z.string() }),
|
|
252
|
+
* z.object({ mode: z.literal('edit'), id: z.number() }),
|
|
253
|
+
* ]);
|
|
254
|
+
*
|
|
255
|
+
* type CreateFields = ValidFieldName<typeof schema, 'mode', 'create'>;
|
|
256
|
+
* // "mode" | "name"
|
|
257
|
+
*
|
|
258
|
+
* type EditFields = ValidFieldName<typeof schema, 'mode', 'edit'>;
|
|
259
|
+
* // "mode" | "id"
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
type ValidFieldName<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>> = keyof Extract<Required<z.input<TSchema>>, Record<TDiscriminatorKey, TDiscriminatorValue>> & Path<TFieldValues>;
|
|
55
263
|
|
|
56
264
|
/**
|
|
57
265
|
* Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.
|
|
@@ -217,10 +425,10 @@ type PartialWithAllNullables<T> = {
|
|
|
217
425
|
* @see https://zod.dev for Zod schema documentation
|
|
218
426
|
* @since 0.1.0
|
|
219
427
|
*/
|
|
220
|
-
declare const useZodForm: <TOutput extends FieldValues, TFormInput extends PartialWithAllNullables<
|
|
428
|
+
declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValues, TFormInput extends PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>>({ schema, zodResolverOptions, ...formOptions }: {
|
|
221
429
|
schema: z.ZodType<TOutput, TInput>;
|
|
222
|
-
defaultValues?:
|
|
430
|
+
defaultValues?: TFormInput;
|
|
223
431
|
zodResolverOptions?: Parameters<typeof zodResolver>[1];
|
|
224
432
|
} & Omit<UseFormProps<TFormInput, unknown, TOutput>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<TFormInput, unknown, TOutput>;
|
|
225
433
|
|
|
226
|
-
export { type PartialWithAllNullables, type PartialWithNullableObjects, useZodForm };
|
|
434
|
+
export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldName, isRequiredField, useFormSchema, useIsRequiredField, useZodForm };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var core = require('@zod-utils/core');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
4
6
|
var zod = require('@hookform/resolvers/zod');
|
|
5
7
|
var reactHookForm = require('react-hook-form');
|
|
6
8
|
|
|
7
9
|
var __defProp = Object.defineProperty;
|
|
8
|
-
var __defProps = Object.defineProperties;
|
|
9
|
-
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
10
10
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
11
11
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
12
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
@@ -22,7 +22,6 @@ var __spreadValues = (a, b) => {
|
|
|
22
22
|
}
|
|
23
23
|
return a;
|
|
24
24
|
};
|
|
25
|
-
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
26
25
|
var __objRest = (source, exclude) => {
|
|
27
26
|
var target = {};
|
|
28
27
|
for (var prop in source)
|
|
@@ -35,6 +34,51 @@ var __objRest = (source, exclude) => {
|
|
|
35
34
|
}
|
|
36
35
|
return target;
|
|
37
36
|
};
|
|
37
|
+
var FormSchemaContext = react.createContext(null);
|
|
38
|
+
function useFormSchema(_params) {
|
|
39
|
+
return react.useContext(
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
41
|
+
FormSchemaContext
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
function FormSchemaProvider({
|
|
45
|
+
schema,
|
|
46
|
+
discriminator,
|
|
47
|
+
children
|
|
48
|
+
}) {
|
|
49
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FormSchemaContext.Provider, { value: { schema, discriminator }, children });
|
|
50
|
+
}
|
|
51
|
+
function useIsRequiredField(_a) {
|
|
52
|
+
var _b = _a, {
|
|
53
|
+
fieldName
|
|
54
|
+
} = _b; __objRest(_b, [
|
|
55
|
+
"fieldName"
|
|
56
|
+
]);
|
|
57
|
+
const context = useFormSchema();
|
|
58
|
+
if (!context) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return isRequiredField({
|
|
62
|
+
schema: context.schema,
|
|
63
|
+
fieldName,
|
|
64
|
+
discriminator: context.discriminator
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function isRequiredField({
|
|
68
|
+
schema,
|
|
69
|
+
fieldName,
|
|
70
|
+
discriminator
|
|
71
|
+
}) {
|
|
72
|
+
const field = core.extractFieldFromSchema({
|
|
73
|
+
schema,
|
|
74
|
+
fieldName,
|
|
75
|
+
discriminator
|
|
76
|
+
});
|
|
77
|
+
if (!field) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
return core.requiresValidInput(field);
|
|
81
|
+
}
|
|
38
82
|
var useZodForm = (_a) => {
|
|
39
83
|
var _b = _a, {
|
|
40
84
|
schema,
|
|
@@ -43,18 +87,17 @@ var useZodForm = (_a) => {
|
|
|
43
87
|
"schema",
|
|
44
88
|
"zodResolverOptions"
|
|
45
89
|
]);
|
|
46
|
-
const resolver = zod.zodResolver(
|
|
47
|
-
|
|
48
|
-
zodResolverOptions
|
|
49
|
-
);
|
|
50
|
-
const defaultValues = formOptions.defaultValues;
|
|
51
|
-
return reactHookForm.useForm(__spreadProps(__spreadValues({
|
|
90
|
+
const resolver = zod.zodResolver(schema, zodResolverOptions);
|
|
91
|
+
return reactHookForm.useForm(__spreadValues({
|
|
52
92
|
resolver
|
|
53
|
-
}, formOptions)
|
|
54
|
-
defaultValues
|
|
55
|
-
}));
|
|
93
|
+
}, formOptions));
|
|
56
94
|
};
|
|
57
95
|
|
|
96
|
+
exports.FormSchemaContext = FormSchemaContext;
|
|
97
|
+
exports.FormSchemaProvider = FormSchemaProvider;
|
|
98
|
+
exports.isRequiredField = isRequiredField;
|
|
99
|
+
exports.useFormSchema = useFormSchema;
|
|
100
|
+
exports.useIsRequiredField = useIsRequiredField;
|
|
58
101
|
exports.useZodForm = useZodForm;
|
|
59
102
|
Object.keys(core).forEach(function (k) {
|
|
60
103
|
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/use-zod-form.ts"],"names":["zodResolver","useForm"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiLO,IAAM,UAAA,GAAa,CAMxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAzLF,GAuLE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAUA,EAAA,MAAM,QAAA,GAAWA,eAAA;AAAA,IACf,MAAA;AAAA,IACA;AAAA,GACF;AAIA,EAAA,MAAM,gBAAgB,WAAA,CAAY,aAAA;AAElC,EAAA,OAAOC,qBAAA,CAAQ,aAAA,CAAA,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CAAA,EAFU;AAAA,IAGb;AAAA,GACF,CAAC,CAAA;AACH","file":"index.js","sourcesContent":["import { zodResolver } from '@hookform/resolvers/zod';\nimport {\n type DefaultValues,\n type FieldValues,\n type UseFormProps,\n useForm,\n} from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`\n * @param options.defaultValues - Default form values (shallow partial - nested objects must be complete if provided)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * Without default values (all fields are optional during editing)\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * age: z.number(),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * // ✅ No defaultValues needed - fields are optional during editing\n * const form = useZodForm({ schema });\n *\n * // Form fields can be set individually as user types\n * form.setValue('name', 'John');\n * form.setValue('email', 'john@example.com');\n * form.setValue('age', 25);\n *\n * // All fields must be valid on submit (per schema validation)\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n TOutput extends FieldValues,\n TFormInput extends\n PartialWithAllNullables<TOutput> = PartialWithNullableObjects<TOutput>,\n TInput extends TFormInput = TFormInput,\n TDefault extends TFormInput = TFormInput,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TDefault;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<\n UseFormProps<TFormInput, unknown, TOutput>,\n 'resolver' | 'defaultValues'\n>) => {\n const resolver = zodResolver<TFormInput, unknown, TOutput>(\n schema,\n zodResolverOptions,\n );\n\n // can't help but to assert here - because DefaultValues<TDefault> makes object fields DeepPartial\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n const defaultValues = formOptions.defaultValues as DefaultValues<TDefault>;\n\n return useForm({\n resolver,\n ...formOptions,\n defaultValues,\n });\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/context.tsx","../src/use-zod-form.ts"],"names":["createContext","useContext","jsx","extractFieldFromSchema","requiresValidInput","zodResolver","useForm"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDO,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;AAyBO,SAAS,mBAQd,EAAA,EAWU;AAXV,EAAA,IAAA,EAAA,GAAA,EAAA,CAAA,CACA;AAAA,IAAA;AAAA,GAjNF,GAgNE,EAAA,CAAA,CAEG,SAAA,CAFH,EAAA,EAEG;AAAA,IADH;AAAA,GAAA;AAWA,EAAA,MAAM,OAAA,GAAU,cAIT,CAAA;AAEP,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,eAAA,CAAgB;AAAA,IACrB,QAAQ,OAAA,CAAQ,MAAA;AAAA,IAChB,SAAA;AAAA,IACA,eAAe,OAAA,CAAQ;AAAA,GACxB,CAAA;AACH;AA2CO,SAAS,eAAA,CAQd;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,EAQY;AACV,EAAA,MAAM,QAAQC,2BAAA,CAAuB;AAAA,IACnC,MAAA;AAAA,IACA,SAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAOC,wBAAmB,KAAK,CAAA;AACjC;AC1IO,IAAM,UAAA,GAAa,CAKxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAnLF,GAiLE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAUA,EAAA,MAAM,QAAA,GAAWC,eAAA,CAAY,MAAA,EAAQ,kBAAkB,CAAA;AAGvD,EAAA,OAAOC,qBAAA,CAAQ,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CACqD,CAAA;AAC5D","file":"index.js","sourcesContent":["'use client';\n\nimport {\n type Discriminator,\n type DiscriminatorKey,\n type DiscriminatorValue,\n extractFieldFromSchema,\n requiresValidInput,\n} from '@zod-utils/core';\nimport { type Context, createContext, type ReactNode, useContext } 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 * Uses the schema from {@link FormSchemaContext} to determine if a field\n * will show validation errors when submitted with empty/invalid input.\n *\n * @param params - Schema, field name, and optional discriminator (schema used for type inference)\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```tsx\n * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {\n * const isRequired = useIsRequiredField({ schema, fieldName: 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 TName extends keyof Extract<\n Required<z.input<TSchema>>,\n Record<TDiscriminatorKey, TDiscriminatorValue>\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n fieldName,\n ...props\n}: {\n schema: TSchema;\n fieldName: TName;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): boolean {\n const context = useFormSchema<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >(props);\n\n if (!context) {\n return false;\n }\n\n return isRequiredField({\n schema: context.schema,\n fieldName,\n discriminator: context.discriminator,\n });\n}\n\n/**\n * Determines if a field requires valid input (will show validation errors on empty/invalid input).\n *\n * Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after\n * removing defaults. This tells you if the field will error when user submits empty input.\n *\n * Returns false if the underlying field accepts:\n * - `undefined` (via `.optional()`)\n * - `null` (via `.nullable()`)\n * - Empty strings (plain `z.string()` without `.min(1)`)\n * - Empty arrays (plain `z.array()` without `.min(1)`)\n *\n * @param options - Schema, field name, and optional discriminator\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * bio: z.string().optional(),\n * });\n *\n * isRequiredField({ schema, fieldName: 'name' }); // true\n * isRequiredField({ schema, fieldName: '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 * fieldName: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * }); // true\n * ```\n */\nexport function isRequiredField<\n TSchema extends z.ZodType,\n TName extends keyof Extract<\n Required<z.input<TSchema>>,\n Record<TDiscriminatorKey, TDiscriminatorValue>\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n fieldName,\n discriminator,\n}: {\n schema: TSchema;\n fieldName: TName;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): boolean {\n const field = extractFieldFromSchema({\n schema,\n fieldName,\n discriminator,\n });\n\n if (!field) {\n return false;\n }\n\n return requiresValidInput(field);\n}\n","import { zodResolver } from '@hookform/resolvers/zod';\nimport { type FieldValues, type UseFormProps, useForm } from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`\n * @param options.defaultValues - Default form values (shallow partial - nested objects must be complete if provided)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * Without default values (all fields are optional during editing)\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * age: z.number(),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * // ✅ No defaultValues needed - fields are optional during editing\n * const form = useZodForm({ schema });\n *\n * // Form fields can be set individually as user types\n * form.setValue('name', 'John');\n * form.setValue('email', 'john@example.com');\n * form.setValue('age', 25);\n *\n * // All fields must be valid on submit (per schema validation)\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n TInput extends FieldValues,\n TOutput extends FieldValues,\n TFormInput extends\n PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TFormInput;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<\n UseFormProps<TFormInput, unknown, TOutput>,\n 'resolver' | 'defaultValues'\n>) => {\n const resolver = zodResolver(schema, zodResolverOptions);\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useForm({\n resolver,\n ...formOptions,\n } as unknown as UseFormProps<TFormInput, unknown, TOutput>);\n};\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { extractFieldFromSchema, requiresValidInput } from '@zod-utils/core';
|
|
1
2
|
export * from '@zod-utils/core';
|
|
3
|
+
import { createContext, useContext } from 'react';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
5
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
3
6
|
import { useForm } from 'react-hook-form';
|
|
4
7
|
|
|
5
8
|
var __defProp = Object.defineProperty;
|
|
6
|
-
var __defProps = Object.defineProperties;
|
|
7
|
-
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
8
9
|
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
9
10
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
11
|
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
@@ -20,7 +21,6 @@ var __spreadValues = (a, b) => {
|
|
|
20
21
|
}
|
|
21
22
|
return a;
|
|
22
23
|
};
|
|
23
|
-
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
24
24
|
var __objRest = (source, exclude) => {
|
|
25
25
|
var target = {};
|
|
26
26
|
for (var prop in source)
|
|
@@ -33,6 +33,51 @@ var __objRest = (source, exclude) => {
|
|
|
33
33
|
}
|
|
34
34
|
return target;
|
|
35
35
|
};
|
|
36
|
+
var FormSchemaContext = createContext(null);
|
|
37
|
+
function useFormSchema(_params) {
|
|
38
|
+
return useContext(
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
40
|
+
FormSchemaContext
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
function FormSchemaProvider({
|
|
44
|
+
schema,
|
|
45
|
+
discriminator,
|
|
46
|
+
children
|
|
47
|
+
}) {
|
|
48
|
+
return /* @__PURE__ */ jsx(FormSchemaContext.Provider, { value: { schema, discriminator }, children });
|
|
49
|
+
}
|
|
50
|
+
function useIsRequiredField(_a) {
|
|
51
|
+
var _b = _a, {
|
|
52
|
+
fieldName
|
|
53
|
+
} = _b; __objRest(_b, [
|
|
54
|
+
"fieldName"
|
|
55
|
+
]);
|
|
56
|
+
const context = useFormSchema();
|
|
57
|
+
if (!context) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return isRequiredField({
|
|
61
|
+
schema: context.schema,
|
|
62
|
+
fieldName,
|
|
63
|
+
discriminator: context.discriminator
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function isRequiredField({
|
|
67
|
+
schema,
|
|
68
|
+
fieldName,
|
|
69
|
+
discriminator
|
|
70
|
+
}) {
|
|
71
|
+
const field = extractFieldFromSchema({
|
|
72
|
+
schema,
|
|
73
|
+
fieldName,
|
|
74
|
+
discriminator
|
|
75
|
+
});
|
|
76
|
+
if (!field) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
return requiresValidInput(field);
|
|
80
|
+
}
|
|
36
81
|
var useZodForm = (_a) => {
|
|
37
82
|
var _b = _a, {
|
|
38
83
|
schema,
|
|
@@ -41,18 +86,12 @@ var useZodForm = (_a) => {
|
|
|
41
86
|
"schema",
|
|
42
87
|
"zodResolverOptions"
|
|
43
88
|
]);
|
|
44
|
-
const resolver = zodResolver(
|
|
45
|
-
|
|
46
|
-
zodResolverOptions
|
|
47
|
-
);
|
|
48
|
-
const defaultValues = formOptions.defaultValues;
|
|
49
|
-
return useForm(__spreadProps(__spreadValues({
|
|
89
|
+
const resolver = zodResolver(schema, zodResolverOptions);
|
|
90
|
+
return useForm(__spreadValues({
|
|
50
91
|
resolver
|
|
51
|
-
}, formOptions)
|
|
52
|
-
defaultValues
|
|
53
|
-
}));
|
|
92
|
+
}, formOptions));
|
|
54
93
|
};
|
|
55
94
|
|
|
56
|
-
export { useZodForm };
|
|
95
|
+
export { FormSchemaContext, FormSchemaProvider, isRequiredField, useFormSchema, useIsRequiredField, useZodForm };
|
|
57
96
|
//# sourceMappingURL=index.mjs.map
|
|
58
97
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/use-zod-form.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiLO,IAAM,UAAA,GAAa,CAMxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAzLF,GAuLE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAUA,EAAA,MAAM,QAAA,GAAW,WAAA;AAAA,IACf,MAAA;AAAA,IACA;AAAA,GACF;AAIA,EAAA,MAAM,gBAAgB,WAAA,CAAY,aAAA;AAElC,EAAA,OAAO,OAAA,CAAQ,aAAA,CAAA,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CAAA,EAFU;AAAA,IAGb;AAAA,GACF,CAAC,CAAA;AACH","file":"index.mjs","sourcesContent":["import { zodResolver } from '@hookform/resolvers/zod';\nimport {\n type DefaultValues,\n type FieldValues,\n type UseFormProps,\n useForm,\n} from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`\n * @param options.defaultValues - Default form values (shallow partial - nested objects must be complete if provided)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * Without default values (all fields are optional during editing)\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * age: z.number(),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * // ✅ No defaultValues needed - fields are optional during editing\n * const form = useZodForm({ schema });\n *\n * // Form fields can be set individually as user types\n * form.setValue('name', 'John');\n * form.setValue('email', 'john@example.com');\n * form.setValue('age', 25);\n *\n * // All fields must be valid on submit (per schema validation)\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n TOutput extends FieldValues,\n TFormInput extends\n PartialWithAllNullables<TOutput> = PartialWithNullableObjects<TOutput>,\n TInput extends TFormInput = TFormInput,\n TDefault extends TFormInput = TFormInput,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TDefault;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<\n UseFormProps<TFormInput, unknown, TOutput>,\n 'resolver' | 'defaultValues'\n>) => {\n const resolver = zodResolver<TFormInput, unknown, TOutput>(\n schema,\n zodResolverOptions,\n );\n\n // can't help but to assert here - because DefaultValues<TDefault> makes object fields DeepPartial\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n const defaultValues = formOptions.defaultValues as DefaultValues<TDefault>;\n\n return useForm({\n resolver,\n ...formOptions,\n defaultValues,\n });\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/context.tsx","../src/use-zod-form.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDO,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;AAyBO,SAAS,mBAQd,EAAA,EAWU;AAXV,EAAA,IAAA,EAAA,GAAA,EAAA,CAAA,CACA;AAAA,IAAA;AAAA,GAjNF,GAgNE,EAAA,CAAA,CAEG,SAAA,CAFH,EAAA,EAEG;AAAA,IADH;AAAA,GAAA;AAWA,EAAA,MAAM,OAAA,GAAU,cAIT,CAAA;AAEP,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,eAAA,CAAgB;AAAA,IACrB,QAAQ,OAAA,CAAQ,MAAA;AAAA,IAChB,SAAA;AAAA,IACA,eAAe,OAAA,CAAQ;AAAA,GACxB,CAAA;AACH;AA2CO,SAAS,eAAA,CAQd;AAAA,EACA,MAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,EAQY;AACV,EAAA,MAAM,QAAQ,sBAAA,CAAuB;AAAA,IACnC,MAAA;AAAA,IACA,SAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,mBAAmB,KAAK,CAAA;AACjC;AC1IO,IAAM,UAAA,GAAa,CAKxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAnLF,GAiLE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAUA,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,MAAA,EAAQ,kBAAkB,CAAA;AAGvD,EAAA,OAAO,OAAA,CAAQ,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CACqD,CAAA;AAC5D","file":"index.mjs","sourcesContent":["'use client';\n\nimport {\n type Discriminator,\n type DiscriminatorKey,\n type DiscriminatorValue,\n extractFieldFromSchema,\n requiresValidInput,\n} from '@zod-utils/core';\nimport { type Context, createContext, type ReactNode, useContext } 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 * Uses the schema from {@link FormSchemaContext} to determine if a field\n * will show validation errors when submitted with empty/invalid input.\n *\n * @param params - Schema, field name, and optional discriminator (schema used for type inference)\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```tsx\n * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {\n * const isRequired = useIsRequiredField({ schema, fieldName: 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 TName extends keyof Extract<\n Required<z.input<TSchema>>,\n Record<TDiscriminatorKey, TDiscriminatorValue>\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n fieldName,\n ...props\n}: {\n schema: TSchema;\n fieldName: TName;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): boolean {\n const context = useFormSchema<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >(props);\n\n if (!context) {\n return false;\n }\n\n return isRequiredField({\n schema: context.schema,\n fieldName,\n discriminator: context.discriminator,\n });\n}\n\n/**\n * Determines if a field requires valid input (will show validation errors on empty/invalid input).\n *\n * Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after\n * removing defaults. This tells you if the field will error when user submits empty input.\n *\n * Returns false if the underlying field accepts:\n * - `undefined` (via `.optional()`)\n * - `null` (via `.nullable()`)\n * - Empty strings (plain `z.string()` without `.min(1)`)\n * - Empty arrays (plain `z.array()` without `.min(1)`)\n *\n * @param options - Schema, field name, and optional discriminator\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * bio: z.string().optional(),\n * });\n *\n * isRequiredField({ schema, fieldName: 'name' }); // true\n * isRequiredField({ schema, fieldName: '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 * fieldName: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * }); // true\n * ```\n */\nexport function isRequiredField<\n TSchema extends z.ZodType,\n TName extends keyof Extract<\n Required<z.input<TSchema>>,\n Record<TDiscriminatorKey, TDiscriminatorValue>\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n fieldName,\n discriminator,\n}: {\n schema: TSchema;\n fieldName: TName;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n}): boolean {\n const field = extractFieldFromSchema({\n schema,\n fieldName,\n discriminator,\n });\n\n if (!field) {\n return false;\n }\n\n return requiresValidInput(field);\n}\n","import { zodResolver } from '@hookform/resolvers/zod';\nimport { type FieldValues, type UseFormProps, useForm } from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`\n * @param options.defaultValues - Default form values (shallow partial - nested objects must be complete if provided)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * Without default values (all fields are optional during editing)\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * age: z.number(),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * // ✅ No defaultValues needed - fields are optional during editing\n * const form = useZodForm({ schema });\n *\n * // Form fields can be set individually as user types\n * form.setValue('name', 'John');\n * form.setValue('email', 'john@example.com');\n * form.setValue('age', 25);\n *\n * // All fields must be valid on submit (per schema validation)\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n TInput extends FieldValues,\n TOutput extends FieldValues,\n TFormInput extends\n PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TFormInput;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<\n UseFormProps<TFormInput, unknown, TOutput>,\n 'resolver' | 'defaultValues'\n>) => {\n const resolver = zodResolver(schema, zodResolverOptions);\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useForm({\n resolver,\n ...formOptions,\n } as unknown as UseFormProps<TFormInput, unknown, TOutput>);\n};\n"]}
|