@zod-utils/react-hook-form 0.12.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -343,16 +343,16 @@ function FieldComponent() {
343
343
  }
344
344
  ```
345
345
 
346
- ### `useIsRequiredField({ schema, fieldName, discriminator? })`
346
+ ### `useIsRequiredField({ schema?, name?, discriminator? })`
347
347
 
348
348
  Hook to check if a field requires valid input (shows validation errors on submit).
349
- The schema parameter is used for type inference only - the actual schema is retrieved from context.
349
+ Returns `false` if schema or name is not provided, making it safe to use in conditional contexts.
350
350
 
351
351
  ```tsx
352
352
  import { useIsRequiredField } from "@zod-utils/react-hook-form";
353
353
 
354
354
  function FormLabel({ name, schema }: { name: string; schema: z.ZodType }) {
355
- const isRequired = useIsRequiredField({ schema, fieldName: name });
355
+ const isRequired = useIsRequiredField({ schema, name });
356
356
 
357
357
  return (
358
358
  <label>
@@ -363,7 +363,7 @@ function FormLabel({ name, schema }: { name: string; schema: z.ZodType }) {
363
363
  }
364
364
  ```
365
365
 
366
- ### `isRequiredField({ schema, fieldName, discriminator? })`
366
+ ### `isRequiredField({ schema, name, discriminator? })`
367
367
 
368
368
  Standalone function to check if a field requires valid input:
369
369
 
@@ -378,12 +378,49 @@ const schema = z.object({
378
378
  bio: z.string().optional(), // Not required - optional
379
379
  });
380
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
381
+ isRequiredField({ schema, name: "username" }); // true
382
+ isRequiredField({ schema, name: "email" }); // false
383
+ isRequiredField({ schema, name: "age" }); // true
384
+ isRequiredField({ schema, name: "bio" }); // false
385
385
  ```
386
386
 
387
+ ### `useExtractFieldFromSchema({ schema, name, discriminator? })`
388
+
389
+ Hook to extract a field's Zod schema from a parent schema. Memoized for performance.
390
+
391
+ ```tsx
392
+ import { useExtractFieldFromSchema } from "@zod-utils/react-hook-form";
393
+
394
+ function FieldInfo({ schema, name }: { schema: z.ZodType; name: string }) {
395
+ const fieldSchema = useExtractFieldFromSchema({ schema, name });
396
+
397
+ if (!fieldSchema) return null;
398
+
399
+ // Use fieldSchema for custom validation or field info
400
+ return <span>{fieldSchema._zod.def.typeName}</span>;
401
+ }
402
+ ```
403
+
404
+ ### `useFieldChecks({ schema, name, discriminator? })`
405
+
406
+ Hook to get validation checks from a field's Zod schema. Useful for displaying validation hints like max length or min/max values.
407
+
408
+ ```tsx
409
+ import { useFieldChecks } from "@zod-utils/react-hook-form";
410
+
411
+ function FieldHint({ schema, name }: { schema: z.ZodType; name: string }) {
412
+ const checks = useFieldChecks({ schema, name });
413
+
414
+ const maxLength = checks.find((c) => c.check === "max_length");
415
+ if (maxLength) {
416
+ return <span>Max {maxLength.maximum} characters</span>;
417
+ }
418
+ return null;
419
+ }
420
+ ```
421
+
422
+ **Supported check types:** `min_length`, `max_length`, `greater_than`, `less_than`, `string_format`, and more.
423
+
387
424
  ---
388
425
 
389
426
  ## Core Utilities (Re-exported)
@@ -398,15 +435,20 @@ import {
398
435
  getPrimitiveType,
399
436
  removeDefault,
400
437
  extractDefaultValue,
438
+ extendWithMeta,
439
+ extractFieldFromSchema,
440
+ getFieldChecks,
401
441
  type Simplify,
402
442
  type ZodUnionCheck,
403
443
 
404
- // Form schema context
444
+ // Form schema context & hooks
405
445
  FormSchemaContext,
406
446
  FormSchemaProvider,
407
447
  useFormSchema,
408
448
  useIsRequiredField,
409
449
  isRequiredField,
450
+ useExtractFieldFromSchema,
451
+ useFieldChecks,
410
452
 
411
453
  // Type utilities
412
454
  type PartialWithNullableObjects,
@@ -415,7 +457,7 @@ import {
415
457
  type DiscriminatorKey,
416
458
  type DiscriminatorValue,
417
459
  type InferredFieldValues,
418
- type ValidFieldName,
460
+ type ValidFieldPaths,
419
461
  } from "@zod-utils/react-hook-form";
420
462
  ```
421
463
 
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { DiscriminatorKey, DiscriminatorValue, Discriminator, Simplify } from '@zod-utils/core';
1
+ import { DiscriminatorKey, DiscriminatorValue, Discriminator, ValidPaths, ExtractZodByPath, ZodUnionCheck, Simplify } from '@zod-utils/core';
2
2
  export * from '@zod-utils/core';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { Context, ReactNode } from 'react';
@@ -109,16 +109,15 @@ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey
109
109
  /**
110
110
  * Hook to check if a field requires valid input based on the Zod schema.
111
111
  *
112
- * Uses the schema from {@link FormSchemaContext} to determine if a field
113
- * will show validation errors when submitted with empty/invalid input.
112
+ * Memoized - only recalculates when schema, name, or discriminator changes.
114
113
  *
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
114
+ * @param params - Schema, name, and optional discriminator (schema and name are optional)
115
+ * @returns true if the field requires valid input, false if it doesn't or if schema/name is not provided
117
116
  *
118
117
  * @example
119
118
  * ```tsx
120
119
  * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {
121
- * const isRequired = useIsRequiredField({ schema, fieldName: name });
120
+ * const isRequired = useIsRequiredField({ schema, name });
122
121
  *
123
122
  * return (
124
123
  * <label>
@@ -129,9 +128,9 @@ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey
129
128
  * }
130
129
  * ```
131
130
  */
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;
131
+ declare function useIsRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
132
+ schema?: TSchema;
133
+ name?: TPath;
135
134
  discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
136
135
  }): boolean;
137
136
  /**
@@ -156,8 +155,8 @@ declare function useIsRequiredField<TSchema extends z.ZodType, TName extends key
156
155
  * bio: z.string().optional(),
157
156
  * });
158
157
  *
159
- * isRequiredField({ schema, fieldName: 'name' }); // true
160
- * isRequiredField({ schema, fieldName: 'bio' }); // false
158
+ * isRequiredField({ schema, name: 'name' }); // true
159
+ * isRequiredField({ schema, name: 'bio' }); // false
161
160
  * ```
162
161
  *
163
162
  * @example
@@ -170,16 +169,85 @@ declare function useIsRequiredField<TSchema extends z.ZodType, TName extends key
170
169
  *
171
170
  * isRequiredField({
172
171
  * schema,
173
- * fieldName: 'name',
172
+ * name: 'name',
174
173
  * discriminator: { key: 'mode', value: 'create' },
175
174
  * }); // true
176
175
  * ```
177
176
  */
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, }: {
177
+ declare function isRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
179
178
  schema: TSchema;
180
- fieldName: TName;
179
+ name: TPath;
181
180
  discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
182
181
  }): boolean;
182
+ /**
183
+ * React hook to extract a field's Zod schema from a parent schema.
184
+ *
185
+ * Memoized - only recalculates when schema, name, or discriminator changes.
186
+ * Supports nested paths and discriminated unions.
187
+ *
188
+ * @param params - Schema, name, and optional discriminator
189
+ * @returns The Zod schema for the field, or undefined if not found
190
+ *
191
+ * @example
192
+ * ```tsx
193
+ * function MyFieldInfo({ name, schema }: { name: string; schema: z.ZodType }) {
194
+ * const fieldSchema = useExtractFieldFromSchema({ schema, name });
195
+ *
196
+ * if (!fieldSchema) return null;
197
+ *
198
+ * // Use fieldSchema for custom validation or field info
199
+ * return <span>{fieldSchema._zod.typeName}</span>;
200
+ * }
201
+ * ```
202
+ *
203
+ * @example
204
+ * With discriminated union
205
+ * ```tsx
206
+ * const schema = z.discriminatedUnion('mode', [
207
+ * z.object({ mode: z.literal('create'), name: z.string() }),
208
+ * z.object({ mode: z.literal('edit'), id: z.number() }),
209
+ * ]);
210
+ *
211
+ * const fieldSchema = useExtractFieldFromSchema({
212
+ * schema,
213
+ * name: 'name',
214
+ * discriminator: { key: 'mode', value: 'create' },
215
+ * });
216
+ * // Returns z.string() schema
217
+ * ```
218
+ */
219
+ declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
220
+ schema: TSchema;
221
+ name: TPath;
222
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
223
+ }): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined;
224
+ /**
225
+ * Hook to get validation checks from a field's Zod schema.
226
+ *
227
+ * Memoized - only recalculates when schema, name, or discriminator changes.
228
+ * Combines field extraction and check retrieval in one cached operation.
229
+ *
230
+ * @param params - Schema, name, and optional discriminator
231
+ * @returns Array of validation checks (min, max, pattern, etc.) or empty array
232
+ *
233
+ * @example
234
+ * ```tsx
235
+ * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {
236
+ * const checks = useFieldChecks({ schema, name });
237
+ *
238
+ * const maxLength = checks.find(c => c.check === 'max_length');
239
+ * if (maxLength) {
240
+ * return <span>Max {maxLength.maximum} characters</span>;
241
+ * }
242
+ * return null;
243
+ * }
244
+ * ```
245
+ */
246
+ declare function useFieldChecks<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
247
+ schema: TSchema;
248
+ name: TPath;
249
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
250
+ }): ZodUnionCheck[];
183
251
 
184
252
  /**
185
253
  * Helper type that adds `null` to object-type fields only (excludes arrays).
@@ -252,14 +320,14 @@ type InferredFieldValues<TSchema extends z.ZodType> = z.input<TSchema> & FieldVa
252
320
  * z.object({ mode: z.literal('edit'), id: z.number() }),
253
321
  * ]);
254
322
  *
255
- * type CreateFields = ValidFieldName<typeof schema, 'mode', 'create'>;
323
+ * type CreateFields = ValidFieldPaths<typeof schema, 'mode', 'create'>;
256
324
  * // "mode" | "name"
257
325
  *
258
- * type EditFields = ValidFieldName<typeof schema, 'mode', 'edit'>;
326
+ * type EditFields = ValidFieldPaths<typeof schema, 'mode', 'edit'>;
259
327
  * // "mode" | "id"
260
328
  * ```
261
329
  */
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>;
330
+ type ValidFieldPaths<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>> = ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue> & Path<TFieldValues>;
263
331
 
264
332
  /**
265
333
  * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.
@@ -275,8 +343,10 @@ type ValidFieldName<TSchema extends z.ZodType, TDiscriminatorKey extends Discrim
275
343
  * - ✅ Validated output is still type-safe with exact Zod schema types
276
344
  * - ✅ Automatic zodResolver setup - no manual configuration needed
277
345
  *
278
- * @template TOutput - The Zod schema output type (extends FieldValues)
279
346
  * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)
347
+ * @template TOutput - The Zod schema output type (extends FieldValues)
348
+ * @template TFormInput - The form input type (defaults to PartialWithNullableObjects<TInput>)
349
+ * @template TDefaultValues - The type of default values (inferred from usage for better type safety)
280
350
  *
281
351
  * @param options - Configuration object
282
352
  * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`
@@ -425,10 +495,10 @@ type ValidFieldName<TSchema extends z.ZodType, TDiscriminatorKey extends Discrim
425
495
  * @see https://zod.dev for Zod schema documentation
426
496
  * @since 0.1.0
427
497
  */
428
- declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValues, TFormInput extends PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>>({ schema, zodResolverOptions, ...formOptions }: {
498
+ declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValues, TFormInput extends PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>, TDefaultValues extends Partial<TFormInput> | undefined = undefined>({ schema, zodResolverOptions, ...formOptions }: {
429
499
  schema: z.ZodType<TOutput, TInput>;
430
- defaultValues?: TFormInput;
500
+ defaultValues?: TDefaultValues;
431
501
  zodResolverOptions?: Parameters<typeof zodResolver>[1];
432
502
  } & Omit<UseFormProps<TFormInput, unknown, TOutput>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<TFormInput, unknown, TOutput>;
433
503
 
434
- export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldName, isRequiredField, useFormSchema, useIsRequiredField, useZodForm };
504
+ export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { DiscriminatorKey, DiscriminatorValue, Discriminator, Simplify } from '@zod-utils/core';
1
+ import { DiscriminatorKey, DiscriminatorValue, Discriminator, ValidPaths, ExtractZodByPath, ZodUnionCheck, Simplify } from '@zod-utils/core';
2
2
  export * from '@zod-utils/core';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
  import { Context, ReactNode } from 'react';
@@ -109,16 +109,15 @@ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey
109
109
  /**
110
110
  * Hook to check if a field requires valid input based on the Zod schema.
111
111
  *
112
- * Uses the schema from {@link FormSchemaContext} to determine if a field
113
- * will show validation errors when submitted with empty/invalid input.
112
+ * Memoized - only recalculates when schema, name, or discriminator changes.
114
113
  *
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
114
+ * @param params - Schema, name, and optional discriminator (schema and name are optional)
115
+ * @returns true if the field requires valid input, false if it doesn't or if schema/name is not provided
117
116
  *
118
117
  * @example
119
118
  * ```tsx
120
119
  * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {
121
- * const isRequired = useIsRequiredField({ schema, fieldName: name });
120
+ * const isRequired = useIsRequiredField({ schema, name });
122
121
  *
123
122
  * return (
124
123
  * <label>
@@ -129,9 +128,9 @@ declare function FormSchemaProvider<TSchema extends z.ZodType, TDiscriminatorKey
129
128
  * }
130
129
  * ```
131
130
  */
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;
131
+ declare function useIsRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
132
+ schema?: TSchema;
133
+ name?: TPath;
135
134
  discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
136
135
  }): boolean;
137
136
  /**
@@ -156,8 +155,8 @@ declare function useIsRequiredField<TSchema extends z.ZodType, TName extends key
156
155
  * bio: z.string().optional(),
157
156
  * });
158
157
  *
159
- * isRequiredField({ schema, fieldName: 'name' }); // true
160
- * isRequiredField({ schema, fieldName: 'bio' }); // false
158
+ * isRequiredField({ schema, name: 'name' }); // true
159
+ * isRequiredField({ schema, name: 'bio' }); // false
161
160
  * ```
162
161
  *
163
162
  * @example
@@ -170,16 +169,85 @@ declare function useIsRequiredField<TSchema extends z.ZodType, TName extends key
170
169
  *
171
170
  * isRequiredField({
172
171
  * schema,
173
- * fieldName: 'name',
172
+ * name: 'name',
174
173
  * discriminator: { key: 'mode', value: 'create' },
175
174
  * }); // true
176
175
  * ```
177
176
  */
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, }: {
177
+ declare function isRequiredField<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
179
178
  schema: TSchema;
180
- fieldName: TName;
179
+ name: TPath;
181
180
  discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
182
181
  }): boolean;
182
+ /**
183
+ * React hook to extract a field's Zod schema from a parent schema.
184
+ *
185
+ * Memoized - only recalculates when schema, name, or discriminator changes.
186
+ * Supports nested paths and discriminated unions.
187
+ *
188
+ * @param params - Schema, name, and optional discriminator
189
+ * @returns The Zod schema for the field, or undefined if not found
190
+ *
191
+ * @example
192
+ * ```tsx
193
+ * function MyFieldInfo({ name, schema }: { name: string; schema: z.ZodType }) {
194
+ * const fieldSchema = useExtractFieldFromSchema({ schema, name });
195
+ *
196
+ * if (!fieldSchema) return null;
197
+ *
198
+ * // Use fieldSchema for custom validation or field info
199
+ * return <span>{fieldSchema._zod.typeName}</span>;
200
+ * }
201
+ * ```
202
+ *
203
+ * @example
204
+ * With discriminated union
205
+ * ```tsx
206
+ * const schema = z.discriminatedUnion('mode', [
207
+ * z.object({ mode: z.literal('create'), name: z.string() }),
208
+ * z.object({ mode: z.literal('edit'), id: z.number() }),
209
+ * ]);
210
+ *
211
+ * const fieldSchema = useExtractFieldFromSchema({
212
+ * schema,
213
+ * name: 'name',
214
+ * discriminator: { key: 'mode', value: 'create' },
215
+ * });
216
+ * // Returns z.string() schema
217
+ * ```
218
+ */
219
+ declare function useExtractFieldFromSchema<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
220
+ schema: TSchema;
221
+ name: TPath;
222
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
223
+ }): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined;
224
+ /**
225
+ * Hook to get validation checks from a field's Zod schema.
226
+ *
227
+ * Memoized - only recalculates when schema, name, or discriminator changes.
228
+ * Combines field extraction and check retrieval in one cached operation.
229
+ *
230
+ * @param params - Schema, name, and optional discriminator
231
+ * @returns Array of validation checks (min, max, pattern, etc.) or empty array
232
+ *
233
+ * @example
234
+ * ```tsx
235
+ * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {
236
+ * const checks = useFieldChecks({ schema, name });
237
+ *
238
+ * const maxLength = checks.find(c => c.check === 'max_length');
239
+ * if (maxLength) {
240
+ * return <span>Max {maxLength.maximum} characters</span>;
241
+ * }
242
+ * return null;
243
+ * }
244
+ * ```
245
+ */
246
+ declare function useFieldChecks<TSchema extends z.ZodType, TPath extends ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>>({ schema, name, discriminator, }: {
247
+ schema: TSchema;
248
+ name: TPath;
249
+ discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
250
+ }): ZodUnionCheck[];
183
251
 
184
252
  /**
185
253
  * Helper type that adds `null` to object-type fields only (excludes arrays).
@@ -252,14 +320,14 @@ type InferredFieldValues<TSchema extends z.ZodType> = z.input<TSchema> & FieldVa
252
320
  * z.object({ mode: z.literal('edit'), id: z.number() }),
253
321
  * ]);
254
322
  *
255
- * type CreateFields = ValidFieldName<typeof schema, 'mode', 'create'>;
323
+ * type CreateFields = ValidFieldPaths<typeof schema, 'mode', 'create'>;
256
324
  * // "mode" | "name"
257
325
  *
258
- * type EditFields = ValidFieldName<typeof schema, 'mode', 'edit'>;
326
+ * type EditFields = ValidFieldPaths<typeof schema, 'mode', 'edit'>;
259
327
  * // "mode" | "id"
260
328
  * ```
261
329
  */
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>;
330
+ type ValidFieldPaths<TSchema extends z.ZodType, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>> = ValidPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue> & Path<TFieldValues>;
263
331
 
264
332
  /**
265
333
  * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.
@@ -275,8 +343,10 @@ type ValidFieldName<TSchema extends z.ZodType, TDiscriminatorKey extends Discrim
275
343
  * - ✅ Validated output is still type-safe with exact Zod schema types
276
344
  * - ✅ Automatic zodResolver setup - no manual configuration needed
277
345
  *
278
- * @template TOutput - The Zod schema output type (extends FieldValues)
279
346
  * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)
347
+ * @template TOutput - The Zod schema output type (extends FieldValues)
348
+ * @template TFormInput - The form input type (defaults to PartialWithNullableObjects<TInput>)
349
+ * @template TDefaultValues - The type of default values (inferred from usage for better type safety)
280
350
  *
281
351
  * @param options - Configuration object
282
352
  * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`
@@ -425,10 +495,10 @@ type ValidFieldName<TSchema extends z.ZodType, TDiscriminatorKey extends Discrim
425
495
  * @see https://zod.dev for Zod schema documentation
426
496
  * @since 0.1.0
427
497
  */
428
- declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValues, TFormInput extends PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>>({ schema, zodResolverOptions, ...formOptions }: {
498
+ declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValues, TFormInput extends PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>, TDefaultValues extends Partial<TFormInput> | undefined = undefined>({ schema, zodResolverOptions, ...formOptions }: {
429
499
  schema: z.ZodType<TOutput, TInput>;
430
- defaultValues?: TFormInput;
500
+ defaultValues?: TDefaultValues;
431
501
  zodResolverOptions?: Parameters<typeof zodResolver>[1];
432
502
  } & Omit<UseFormProps<TFormInput, unknown, TOutput>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<TFormInput, unknown, TOutput>;
433
503
 
434
- export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldName, isRequiredField, useFormSchema, useIsRequiredField, useZodForm };
504
+ export { FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
package/dist/index.js CHANGED
@@ -48,30 +48,26 @@ function FormSchemaProvider({
48
48
  }) {
49
49
  return /* @__PURE__ */ jsxRuntime.jsx(FormSchemaContext.Provider, { value: { schema, discriminator }, children });
50
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
- });
51
+ function useIsRequiredField({
52
+ schema,
53
+ name,
54
+ discriminator
55
+ }) {
56
+ return react.useMemo(() => {
57
+ if (!schema || !name) {
58
+ return false;
59
+ }
60
+ return isRequiredField({ schema, name, discriminator });
61
+ }, [schema, name, discriminator]);
66
62
  }
67
63
  function isRequiredField({
68
64
  schema,
69
- fieldName,
65
+ name,
70
66
  discriminator
71
67
  }) {
72
68
  const field = core.extractFieldFromSchema({
73
69
  schema,
74
- fieldName,
70
+ name,
75
71
  discriminator
76
72
  });
77
73
  if (!field) {
@@ -79,6 +75,27 @@ function isRequiredField({
79
75
  }
80
76
  return core.requiresValidInput(field);
81
77
  }
78
+ function useExtractFieldFromSchema({
79
+ schema,
80
+ name,
81
+ discriminator
82
+ }) {
83
+ return react.useMemo(
84
+ () => core.extractFieldFromSchema({ schema, name, discriminator }),
85
+ [schema, name, discriminator]
86
+ );
87
+ }
88
+ function useFieldChecks({
89
+ schema,
90
+ name,
91
+ discriminator
92
+ }) {
93
+ return react.useMemo(() => {
94
+ const field = core.extractFieldFromSchema({ schema, name, discriminator });
95
+ if (!field) return [];
96
+ return core.getFieldChecks(field);
97
+ }, [schema, name, discriminator]);
98
+ }
82
99
  var useZodForm = (_a) => {
83
100
  var _b = _a, {
84
101
  schema,
@@ -96,6 +113,8 @@ var useZodForm = (_a) => {
96
113
  exports.FormSchemaContext = FormSchemaContext;
97
114
  exports.FormSchemaProvider = FormSchemaProvider;
98
115
  exports.isRequiredField = isRequiredField;
116
+ exports.useExtractFieldFromSchema = useExtractFieldFromSchema;
117
+ exports.useFieldChecks = useFieldChecks;
99
118
  exports.useFormSchema = useFormSchema;
100
119
  exports.useIsRequiredField = useIsRequiredField;
101
120
  exports.useZodForm = useZodForm;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
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"]}
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"]}
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { extractFieldFromSchema, requiresValidInput } from '@zod-utils/core';
1
+ import { extractFieldFromSchema, requiresValidInput, getFieldChecks } from '@zod-utils/core';
2
2
  export * from '@zod-utils/core';
3
- import { createContext, useContext } from 'react';
3
+ import { createContext, useContext, useMemo } from 'react';
4
4
  import { jsx } from 'react/jsx-runtime';
5
5
  import { zodResolver } from '@hookform/resolvers/zod';
6
6
  import { useForm } from 'react-hook-form';
@@ -47,30 +47,26 @@ function FormSchemaProvider({
47
47
  }) {
48
48
  return /* @__PURE__ */ jsx(FormSchemaContext.Provider, { value: { schema, discriminator }, children });
49
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
- });
50
+ function useIsRequiredField({
51
+ schema,
52
+ name,
53
+ discriminator
54
+ }) {
55
+ return useMemo(() => {
56
+ if (!schema || !name) {
57
+ return false;
58
+ }
59
+ return isRequiredField({ schema, name, discriminator });
60
+ }, [schema, name, discriminator]);
65
61
  }
66
62
  function isRequiredField({
67
63
  schema,
68
- fieldName,
64
+ name,
69
65
  discriminator
70
66
  }) {
71
67
  const field = extractFieldFromSchema({
72
68
  schema,
73
- fieldName,
69
+ name,
74
70
  discriminator
75
71
  });
76
72
  if (!field) {
@@ -78,6 +74,27 @@ function isRequiredField({
78
74
  }
79
75
  return requiresValidInput(field);
80
76
  }
77
+ function useExtractFieldFromSchema({
78
+ schema,
79
+ name,
80
+ discriminator
81
+ }) {
82
+ return useMemo(
83
+ () => extractFieldFromSchema({ schema, name, discriminator }),
84
+ [schema, name, discriminator]
85
+ );
86
+ }
87
+ function useFieldChecks({
88
+ schema,
89
+ name,
90
+ discriminator
91
+ }) {
92
+ return useMemo(() => {
93
+ const field = extractFieldFromSchema({ schema, name, discriminator });
94
+ if (!field) return [];
95
+ return getFieldChecks(field);
96
+ }, [schema, name, discriminator]);
97
+ }
81
98
  var useZodForm = (_a) => {
82
99
  var _b = _a, {
83
100
  schema,
@@ -92,6 +109,6 @@ var useZodForm = (_a) => {
92
109
  }, formOptions));
93
110
  };
94
111
 
95
- export { FormSchemaContext, FormSchemaProvider, isRequiredField, useFormSchema, useIsRequiredField, useZodForm };
112
+ export { FormSchemaContext, FormSchemaProvider, isRequiredField, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
96
113
  //# sourceMappingURL=index.mjs.map
97
114
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
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"]}
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zod-utils/react-hook-form",
3
- "version": "0.12.0",
3
+ "version": "2.0.1",
4
4
  "description": "React Hook Form integration and utilities for Zod schemas",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",