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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -451,7 +451,7 @@ import {
451
451
  useFieldChecks,
452
452
 
453
453
  // Form field utilities
454
- mergeFormFieldSelectorProps,
454
+ toFormFieldSelector,
455
455
  flattenFieldSelector,
456
456
 
457
457
  // Type utilities
@@ -627,21 +627,24 @@ type CreateNameSelector = FormFieldSelector<typeof formSchema, "name", "mode", "
627
627
 
628
628
  ---
629
629
 
630
- #### `mergeFormFieldSelectorProps(factoryProps, componentProps)`
630
+ #### `toFormFieldSelector(props)`
631
631
 
632
- Merges factory props with component props into a `FormFieldSelector`. Useful for creating form field component factories.
632
+ Extracts a `FormFieldSelector` from props containing schema, name, and optional discriminator. Encapsulates type assertion so callers don't need eslint-disable.
633
633
 
634
634
  ```typescript
635
- import { mergeFormFieldSelectorProps } from "@zod-utils/react-hook-form";
635
+ import { toFormFieldSelector } from "@zod-utils/react-hook-form";
636
+
637
+ // In a form field component
638
+ function InputFormField<TSchema extends z.ZodType>({ schema, name, discriminator, ...inputProps }) {
639
+ const selectorProps = toFormFieldSelector({ schema, name, discriminator });
640
+ // Use selectorProps for extractFieldFromSchema, useIsRequiredField, etc.
641
+ }
636
642
 
637
643
  // In a factory function
638
644
  function createInputFormField<TSchema extends z.ZodType>({ schema }: { schema: TSchema }) {
639
- return function InputFormField({ name, discriminator }) {
640
- const selectorProps = mergeFormFieldSelectorProps(
641
- { schema },
642
- { name, discriminator }
643
- );
644
- // Use selectorProps for extractFieldFromSchema, useIsRequiredField, etc.
645
+ return function BoundInputFormField({ name, discriminator, ...rest }) {
646
+ const selectorProps = toFormFieldSelector({ schema, name, discriminator });
647
+ return <InputFormField {...selectorProps} {...rest} />;
645
648
  };
646
649
  }
647
650
  ```
@@ -800,6 +803,29 @@ type EditNumberPaths = ValidFieldPaths<typeof schema, "mode", "edit", never, num
800
803
  - v2.x: `ValidFieldPathsOfType<TSchema, TValueConstraint, TDiscriminatorKey, TDiscriminatorValue, TFieldValues>`
801
804
  - v3.x: `ValidFieldPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>`
802
805
 
806
+ ### Migrating to v4.0.0
807
+
808
+ #### `mergeFormFieldSelectorProps` renamed → Use `toFormFieldSelector`
809
+
810
+ The function has been renamed and simplified to accept a single props object.
811
+
812
+ **Before (v3.x):**
813
+ ```typescript
814
+ import { mergeFormFieldSelectorProps } from "@zod-utils/react-hook-form";
815
+
816
+ const selectorProps = mergeFormFieldSelectorProps(
817
+ { schema },
818
+ { name, discriminator }
819
+ );
820
+ ```
821
+
822
+ **After (v4.x):**
823
+ ```typescript
824
+ import { toFormFieldSelector } from "@zod-utils/react-hook-form";
825
+
826
+ const selectorProps = toFormFieldSelector({ schema, name, discriminator });
827
+ ```
828
+
803
829
  ---
804
830
 
805
831
  ## License
package/dist/index.d.mts CHANGED
@@ -581,29 +581,24 @@ declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValu
581
581
  } & Omit<UseFormProps<TFormInput, unknown, TOutput>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<TFormInput, unknown, TOutput>;
582
582
 
583
583
  /**
584
- * Merges factory props with component props into a FormFieldSelector.
584
+ * Extracts a FormFieldSelector from props containing schema, name, and optional discriminator.
585
585
  * Encapsulates type assertion so callers don't need eslint-disable.
586
586
  *
587
- * This is the React Hook Form specific version that uses ValidFieldPaths
588
- * (which includes RHF's Path type constraint).
589
- *
590
- * @param factoryProps - Props from factory function (contains schema)
591
- * @param componentProps - Props from component call (contains name, discriminator)
587
+ * @param props - Object containing schema, name, and optional discriminator
592
588
  * @returns Properly typed FormFieldSelector
593
589
  *
594
590
  * @example
595
591
  * ```typescript
596
- * const selectorProps = mergeFormFieldSelectorProps(
597
- * { schema: mySchema },
598
- * { name: 'fieldName', discriminator: { key: 'mode', value: 'create' } }
599
- * );
592
+ * const selectorProps = toFormFieldSelector<TSchema, TPath, ...>(props);
600
593
  * ```
601
594
  */
602
- declare function mergeFormFieldSelectorProps<TSchema extends z.ZodType, TPath extends ValidFieldPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>, TFilterType = unknown, TStrict extends boolean = true>(factoryProps: {
603
- schema: TSchema;
604
- }, componentProps: {
605
- name: TPath;
606
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
595
+ declare function toFormFieldSelector<TSchema extends z.ZodType, TPath extends ValidFieldPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>, TFilterType = unknown, TStrict extends boolean = true>(props: {
596
+ schema: z.ZodType;
597
+ name: string;
598
+ discriminator?: {
599
+ key: string;
600
+ value: unknown;
601
+ };
607
602
  }): FormFieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>;
608
603
  /**
609
604
  * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.
@@ -627,4 +622,4 @@ declare function flattenFieldSelector<TSchema extends z.ZodType, TPath extends V
627
622
  discriminator?: undefined;
628
623
  }): any[];
629
624
 
630
- export { type FormFieldSelector, FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, flattenFieldSelector, isRequiredField, mergeFormFieldSelectorProps, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
625
+ export { type FormFieldSelector, FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, flattenFieldSelector, isRequiredField, toFormFieldSelector, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
package/dist/index.d.ts CHANGED
@@ -581,29 +581,24 @@ declare const useZodForm: <TInput extends FieldValues, TOutput extends FieldValu
581
581
  } & Omit<UseFormProps<TFormInput, unknown, TOutput>, "resolver" | "defaultValues">) => react_hook_form.UseFormReturn<TFormInput, unknown, TOutput>;
582
582
 
583
583
  /**
584
- * Merges factory props with component props into a FormFieldSelector.
584
+ * Extracts a FormFieldSelector from props containing schema, name, and optional discriminator.
585
585
  * Encapsulates type assertion so callers don't need eslint-disable.
586
586
  *
587
- * This is the React Hook Form specific version that uses ValidFieldPaths
588
- * (which includes RHF's Path type constraint).
589
- *
590
- * @param factoryProps - Props from factory function (contains schema)
591
- * @param componentProps - Props from component call (contains name, discriminator)
587
+ * @param props - Object containing schema, name, and optional discriminator
592
588
  * @returns Properly typed FormFieldSelector
593
589
  *
594
590
  * @example
595
591
  * ```typescript
596
- * const selectorProps = mergeFormFieldSelectorProps(
597
- * { schema: mySchema },
598
- * { name: 'fieldName', discriminator: { key: 'mode', value: 'create' } }
599
- * );
592
+ * const selectorProps = toFormFieldSelector<TSchema, TPath, ...>(props);
600
593
  * ```
601
594
  */
602
- declare function mergeFormFieldSelectorProps<TSchema extends z.ZodType, TPath extends ValidFieldPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>, TFilterType = unknown, TStrict extends boolean = true>(factoryProps: {
603
- schema: TSchema;
604
- }, componentProps: {
605
- name: TPath;
606
- discriminator?: Discriminator<TSchema, TDiscriminatorKey, TDiscriminatorValue>;
595
+ declare function toFormFieldSelector<TSchema extends z.ZodType, TPath extends ValidFieldPaths<TSchema, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>, TDiscriminatorKey extends DiscriminatorKey<TSchema>, TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>, TFieldValues extends InferredFieldValues<TSchema> = InferredFieldValues<TSchema>, TFilterType = unknown, TStrict extends boolean = true>(props: {
596
+ schema: z.ZodType;
597
+ name: string;
598
+ discriminator?: {
599
+ key: string;
600
+ value: unknown;
601
+ };
607
602
  }): FormFieldSelector<TSchema, TPath, TDiscriminatorKey, TDiscriminatorValue, TFieldValues, TFilterType, TStrict>;
608
603
  /**
609
604
  * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.
@@ -627,4 +622,4 @@ declare function flattenFieldSelector<TSchema extends z.ZodType, TPath extends V
627
622
  discriminator?: undefined;
628
623
  }): any[];
629
624
 
630
- export { type FormFieldSelector, FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, flattenFieldSelector, isRequiredField, mergeFormFieldSelectorProps, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
625
+ export { type FormFieldSelector, FormSchemaContext, type FormSchemaContextType, type FormSchemaContextValue, FormSchemaProvider, type InferredFieldValues, type PartialWithAllNullables, type PartialWithNullableObjects, type ValidFieldPaths, flattenFieldSelector, isRequiredField, toFormFieldSelector, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
package/dist/index.js CHANGED
@@ -36,8 +36,9 @@ var __objRest = (source, exclude) => {
36
36
  };
37
37
 
38
38
  // src/utils.ts
39
- function mergeFormFieldSelectorProps(factoryProps, componentProps) {
40
- return __spreadValues(__spreadValues({}, factoryProps), componentProps);
39
+ function toFormFieldSelector(props) {
40
+ const { schema, name, discriminator } = props;
41
+ return { schema, name, discriminator };
41
42
  }
42
43
  function flattenFieldSelector(params) {
43
44
  const _a = params, { discriminator } = _a, rest = __objRest(_a, ["discriminator"]);
@@ -106,7 +107,7 @@ exports.FormSchemaContext = FormSchemaContext;
106
107
  exports.FormSchemaProvider = FormSchemaProvider;
107
108
  exports.flattenFieldSelector = flattenFieldSelector;
108
109
  exports.isRequiredField = isRequiredField;
109
- exports.mergeFormFieldSelectorProps = mergeFormFieldSelectorProps;
110
+ exports.toFormFieldSelector = toFormFieldSelector;
110
111
  exports.useExtractFieldFromSchema = useExtractFieldFromSchema;
111
112
  exports.useFieldChecks = useFieldChecks;
112
113
  exports.useFormSchema = useFormSchema;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/context.tsx","../src/use-zod-form.ts"],"names":["createContext","useContext","jsx","useMemo","extractFieldFromSchema","requiresValidInput","getFieldChecks","zodResolver","useForm"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCO,SAAS,2BAAA,CAiBd,cACA,cAAA,EAgBA;AAEA,EAAA,OAAO,kCAAK,YAAA,CAAA,EAAiB,cAAA,CAAA;AAS/B;AAkBO,SAAS,qBAcd,MAAA,EAcA;AACA,EAAA,MAAmC,aAA3B,EAAA,aAAA,EA7HV,GA6HqC,EAAA,EAAT,IAAA,GAAA,SAAA,CAAS,IAAT,CAAlB,eAAA,CAAA,CAAA;AAER,EAAA,OAAO;AAAA,IACL,GAAG,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA;AAAA,IACrB,GAAI,aAAA,GAAgB,MAAA,CAAO,MAAA,CAAO,aAAa,IAAI;AAAC,GACtD;AACF;AC7CO,IAAM,iBAAA,GAAoBA,oBAMvB,IAAI;AA6BP,SAAS,cAYd,OAAA,EAiCA;AACA,EAAA,OAAOC,gBAAA;AAAA;AAAA,IAEL;AAAA,GAiBF;AACF;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EA4BG;AACD,EAAA,uBACEC,cAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,mBAcd,MAAA,EAcS;AAET,EAAA,OAAOC,cAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,CAAO,MAAA,IAAU,CAAC,OAAO,IAAA,EAAM;AAClC,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,gBAAgB,MAAM,CAAA;AAAA,EAC/B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AA2CO,SAAS,gBAcd,MAAA,EAQS;AACT,EAAA,MAAM,KAAA,GAAQC,4BAOZ,MAAM,CAAA;AAER,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAOC,wBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,0BAiBd,MAAA,EAQ4D;AAE5D,EAAA,OAAOF,aAAA;AAAA,IACL,MACEC,4BAOE,MAAM,CAAA;AAAA,IACV,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC;AAAA,GAClC;AACF;AAwBO,SAAS,eAcd,MAAA,EAQiB;AAEjB,EAAA,OAAOD,cAAQ,MAAM;AACnB,IAAA,MAAM,KAAA,GAAQC,4BAOZ,MAAM,CAAA;AACR,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAOE,oBAAe,KAAK,CAAA;AAAA,EAC7B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;ACnXO,IAAM,UAAA,GAAa,CAMxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAtLF,GAoLE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAUA,EAAA,MAAM,QAAA,GAAWC,eAAA,CAAY,MAAA,EAAQ,kBAAkB,CAAA;AAGvD,EAAA,OAAOC,qBAAA,CAAQ,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CACqD,CAAA;AAC5D","file":"index.js","sourcesContent":["import type {\n Discriminator,\n DiscriminatorKey,\n DiscriminatorValue,\n FieldSelector,\n ValidPaths,\n} from '@zod-utils/core';\nimport type { z } from 'zod';\nimport type {\n FormFieldSelector,\n InferredFieldValues,\n ValidFieldPaths,\n} from './types';\n\n/**\n * Merges factory props with component props into a FormFieldSelector.\n * Encapsulates type assertion so callers don't need eslint-disable.\n *\n * This is the React Hook Form specific version that uses ValidFieldPaths\n * (which includes RHF's Path type constraint).\n *\n * @param factoryProps - Props from factory function (contains schema)\n * @param componentProps - Props from component call (contains name, discriminator)\n * @returns Properly typed FormFieldSelector\n *\n * @example\n * ```typescript\n * const selectorProps = mergeFormFieldSelectorProps(\n * { schema: mySchema },\n * { name: 'fieldName', discriminator: { key: 'mode', value: 'create' } }\n * );\n * ```\n */\nexport function mergeFormFieldSelectorProps<\n TSchema extends z.ZodType,\n TPath extends ValidFieldPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFieldValues extends\n InferredFieldValues<TSchema> = InferredFieldValues<TSchema>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n factoryProps: { schema: TSchema },\n componentProps: {\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n },\n): FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n> {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return { ...factoryProps, ...componentProps } as FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n >;\n}\n\n/**\n * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.\n *\n * This is useful for `useMemo` and `useCallback` dependencies where you want to avoid\n * re-running when object references change but values stay the same.\n *\n * @param params - The FieldSelector containing schema, name, and optional discriminator\n * @returns An array of primitive values suitable for React dependency arrays\n *\n * @example\n * ```tsx\n * const memoizedValue = useMemo(() => {\n * return extractFieldFromSchema(params);\n * }, flattenFieldSelector(params));\n * ```\n */\nexport function flattenFieldSelector<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | {\n schema?: undefined;\n name?: undefined;\n discriminator?: undefined;\n },\n) {\n const { discriminator, ...rest } = params;\n\n return [\n ...Object.values(rest),\n ...(discriminator ? Object.values(discriminator) : []),\n ];\n}\n","'use client';\n\nimport {\n type Discriminator,\n type DiscriminatorKey,\n type DiscriminatorValue,\n type ExtractZodByPath,\n extractFieldFromSchema,\n type FieldSelector,\n getFieldChecks,\n requiresValidInput,\n type ValidPaths,\n type ZodUnionCheck,\n} from '@zod-utils/core';\nimport {\n type Context,\n createContext,\n type ReactNode,\n useContext,\n useMemo,\n} from 'react';\nimport type { z } from 'zod';\nimport type { SomeType } from 'zod/v4/core';\nimport { flattenFieldSelector } from './utils';\n\n/**\n * Type for the FormSchemaContext with full generic support.\n * @internal\n */\nexport type FormSchemaContextType<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n> = Context<Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n> | null>;\n\n/**\n * Context value type for FormSchemaContext.\n */\nexport type FormSchemaContextValue<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n> = Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n> | null;\n\n/**\n * React Context for providing Zod schema to form components.\n *\n * Use with {@link FormSchemaProvider} to provide schema context, and\n * {@link useFormSchema} to consume it in child components.\n */\nexport const FormSchemaContext = createContext<{\n schema: z.ZodType;\n discriminator?: {\n key: unknown;\n value: unknown;\n };\n} | null>(null);\n\n/**\n * Hook to access the form schema from context.\n *\n * The optional `_params` argument is used for TypeScript type inference only.\n * Pass your schema to get proper type narrowing of the context value.\n *\n * @param _params - Optional params for type inference (not used at runtime)\n * @returns The schema context value or null if not within a provider\n *\n * @example\n * ```tsx\n * // Without type params (returns generic context)\n * function MyFormField() {\n * const context = useFormSchema();\n * if (!context) return null;\n *\n * const { schema, discriminator } = context;\n * // Use schema for validation or field extraction\n * }\n *\n * // With type params (for type-safe schema access)\n * function TypedFormField() {\n * const context = useFormSchema({ schema: mySchema });\n * // context.schema is now typed as typeof mySchema\n * }\n * ```\n */\nexport function useFormSchema<\n TSchema extends z.ZodType = z.ZodType,\n TDiscriminatorKey extends\n DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n // Parameter used for type inference only, not at runtime\n _params?: TSchema extends z.ZodPipe<infer In, SomeType>\n ? In extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }\n : TSchema extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n },\n): FormSchemaContextValue<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n> {\n return useContext(\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n FormSchemaContext as Context<Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n > | null>,\n );\n}\n\n/**\n * Provider component that makes Zod schema available to all child components.\n *\n * Use this to wrap your form and provide schema context to nested components\n * like field labels and validation indicators.\n *\n * @example\n * Basic usage with ZodObject\n * ```tsx\n * const schema = z.object({\n * name: z.string(),\n * email: z.string().email().optional()\n * });\n *\n * <FormSchemaProvider schema={schema}>\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n *\n * @example\n * Usage with discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() })\n * ]);\n *\n * <FormSchemaProvider\n * schema={schema}\n * discriminator={{ key: 'mode', value: 'create' }}\n * >\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n */\nexport function FormSchemaProvider<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n discriminator,\n children,\n}: (TSchema extends z.ZodPipe<infer In, SomeType>\n ? In extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }\n : TSchema extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }) & {\n children: ReactNode;\n}) {\n return (\n <FormSchemaContext.Provider value={{ schema, discriminator }}>\n {children}\n </FormSchemaContext.Provider>\n );\n}\n\n/**\n * Hook to check if a field requires valid input based on the Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n *\n * @param params - Schema, name, and optional discriminator (schema and name are optional)\n * @returns true if the field requires valid input, false if it doesn't or if schema/name is not provided\n *\n * @example\n * ```tsx\n * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {\n * const isRequired = useIsRequiredField({ schema, name });\n *\n * return (\n * <label>\n * {name}\n * {isRequired && <span className=\"text-red-500\">*</span>}\n * </label>\n * );\n * }\n * ```\n */\nexport function useIsRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | {\n schema?: undefined;\n name?: undefined;\n discriminator?: undefined;\n },\n): boolean {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params.schema || !params.name) {\n return false;\n }\n\n return isRequiredField(params);\n }, [...flattenFieldSelector(params)]);\n}\n\n/**\n * Determines if a field requires valid input (will show validation errors on empty/invalid input).\n *\n * Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after\n * removing defaults. This tells you if the field will error when user submits empty input.\n *\n * Returns false if the underlying field accepts:\n * - `undefined` (via `.optional()`)\n * - `null` (via `.nullable()`)\n * - Empty strings (plain `z.string()` without `.min(1)`)\n * - Empty arrays (plain `z.array()` without `.min(1)`)\n *\n * @param options - Schema, field name, and optional discriminator\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * bio: z.string().optional(),\n * });\n *\n * isRequiredField({ schema, name: 'name' }); // true\n * isRequiredField({ schema, name: 'bio' }); // false\n * ```\n *\n * @example\n * With discriminated union\n * ```typescript\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string().min(1) }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * isRequiredField({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * }); // true\n * ```\n */\nexport function isRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): boolean {\n const field = extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params);\n\n if (!field) {\n return false;\n }\n\n return requiresValidInput(field);\n}\n\n/**\n * React hook to extract a field's Zod schema from a parent schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Supports nested paths and discriminated unions.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns The Zod schema for the field, or undefined if not found\n *\n * @example\n * ```tsx\n * function MyFieldInfo({ name, schema }: { name: string; schema: z.ZodType }) {\n * const fieldSchema = useExtractFieldFromSchema({ schema, name });\n *\n * if (!fieldSchema) return null;\n *\n * // Use fieldSchema for custom validation or field info\n * return <span>{fieldSchema._zod.typeName}</span>;\n * }\n * ```\n *\n * @example\n * With discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * const fieldSchema = useExtractFieldFromSchema({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * });\n * // Returns z.string() schema\n * ```\n */\nexport function useExtractFieldFromSchema<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(\n () =>\n extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params),\n [...flattenFieldSelector(params)],\n );\n}\n\n/**\n * Hook to get validation checks from a field's Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Combines field extraction and check retrieval in one cached operation.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns Array of validation checks (min, max, pattern, etc.) or empty array\n *\n * @example\n * ```tsx\n * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {\n * const checks = useFieldChecks({ schema, name });\n *\n * const maxLength = checks.find(c => c.check === 'max_length');\n * if (maxLength) {\n * return <span>Max {maxLength.maximum} characters</span>;\n * }\n * return null;\n * }\n * ```\n */\nexport function useFieldChecks<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): ZodUnionCheck[] {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n const field = extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params);\n if (!field) return [];\n return getFieldChecks(field);\n }, [...flattenFieldSelector(params)]);\n}\n","import { zodResolver } from '@hookform/resolvers/zod';\nimport { type FieldValues, type UseFormProps, useForm } from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TFormInput - The form input type (defaults to PartialWithNullableObjects<TInput>)\n * @template TDefaultValues - The type of default values (inferred from usage for better type safety)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`\n * @param options.defaultValues - Default form values (shallow partial - nested objects must be complete if provided)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * Without default values (all fields are optional during editing)\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * age: z.number(),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * // ✅ No defaultValues needed - fields are optional during editing\n * const form = useZodForm({ schema });\n *\n * // Form fields can be set individually as user types\n * form.setValue('name', 'John');\n * form.setValue('email', 'john@example.com');\n * form.setValue('age', 25);\n *\n * // All fields must be valid on submit (per schema validation)\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n TInput extends FieldValues,\n TOutput extends FieldValues,\n TFormInput extends\n PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>,\n TDefaultValues extends Partial<TFormInput> | undefined = undefined,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TDefaultValues;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<\n UseFormProps<TFormInput, unknown, TOutput>,\n 'resolver' | 'defaultValues'\n>) => {\n const resolver = zodResolver(schema, zodResolverOptions);\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useForm({\n resolver,\n ...formOptions,\n } as unknown as UseFormProps<TFormInput, unknown, TOutput>);\n};\n"]}
1
+ {"version":3,"sources":["../src/utils.ts","../src/context.tsx","../src/use-zod-form.ts"],"names":["createContext","useContext","jsx","useMemo","extractFieldFromSchema","requiresValidInput","getFieldChecks","zodResolver","useForm"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBO,SAAS,oBAgBd,KAAA,EAYA;AACA,EAAA,MAAM,EAAE,MAAA,EAAQ,IAAA,EAAM,aAAA,EAAc,GAAI,KAAA;AAExC,EAAA,OAAO,EAAE,MAAA,EAAQ,IAAA,EAAM,aAAA,EAAc;AASvC;AAkBO,SAAS,qBAcd,MAAA,EAcA;AACA,EAAA,MAAmC,aAA3B,EAAA,aAAA,EAhHV,GAgHqC,EAAA,EAAT,IAAA,GAAA,SAAA,CAAS,IAAT,CAAlB,eAAA,CAAA,CAAA;AAER,EAAA,OAAO;AAAA,IACL,GAAG,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA;AAAA,IACrB,GAAI,aAAA,GAAgB,MAAA,CAAO,MAAA,CAAO,aAAa,IAAI;AAAC,GACtD;AACF;AChCO,IAAM,iBAAA,GAAoBA,oBAMvB,IAAI;AA6BP,SAAS,cAYd,OAAA,EAiCA;AACA,EAAA,OAAOC,gBAAA;AAAA;AAAA,IAEL;AAAA,GAiBF;AACF;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EA4BG;AACD,EAAA,uBACEC,cAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,mBAcd,MAAA,EAcS;AAET,EAAA,OAAOC,cAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,CAAO,MAAA,IAAU,CAAC,OAAO,IAAA,EAAM;AAClC,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,gBAAgB,MAAM,CAAA;AAAA,EAC/B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AA2CO,SAAS,gBAcd,MAAA,EAQS;AACT,EAAA,MAAM,KAAA,GAAQC,4BAOZ,MAAM,CAAA;AAER,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAOC,wBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,0BAiBd,MAAA,EAQ4D;AAE5D,EAAA,OAAOF,aAAA;AAAA,IACL,MACEC,4BAOE,MAAM,CAAA;AAAA,IACV,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC;AAAA,GAClC;AACF;AAwBO,SAAS,eAcd,MAAA,EAQiB;AAEjB,EAAA,OAAOD,cAAQ,MAAM;AACnB,IAAA,MAAM,KAAA,GAAQC,4BAOZ,MAAM,CAAA;AACR,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAOE,oBAAe,KAAK,CAAA;AAAA,EAC7B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;ACnXO,IAAM,UAAA,GAAa,CAMxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAtLF,GAoLE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAUA,EAAA,MAAM,QAAA,GAAWC,eAAA,CAAY,MAAA,EAAQ,kBAAkB,CAAA;AAGvD,EAAA,OAAOC,qBAAA,CAAQ,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CACqD,CAAA;AAC5D","file":"index.js","sourcesContent":["import type {\n DiscriminatorKey,\n DiscriminatorValue,\n FieldSelector,\n ValidPaths,\n} from '@zod-utils/core';\nimport type { z } from 'zod';\nimport type {\n FormFieldSelector,\n InferredFieldValues,\n ValidFieldPaths,\n} from './types';\n\n/**\n * Extracts a FormFieldSelector from props containing schema, name, and optional discriminator.\n * Encapsulates type assertion so callers don't need eslint-disable.\n *\n * @param props - Object containing schema, name, and optional discriminator\n * @returns Properly typed FormFieldSelector\n *\n * @example\n * ```typescript\n * const selectorProps = toFormFieldSelector<TSchema, TPath, ...>(props);\n * ```\n */\nexport function toFormFieldSelector<\n TSchema extends z.ZodType,\n TPath extends ValidFieldPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFieldValues extends\n InferredFieldValues<TSchema> = InferredFieldValues<TSchema>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(props: {\n schema: z.ZodType;\n name: string;\n discriminator?: { key: string; value: unknown };\n}): FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n> {\n const { schema, name, discriminator } = props;\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return { schema, name, discriminator } as FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n >;\n}\n\n/**\n * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.\n *\n * This is useful for `useMemo` and `useCallback` dependencies where you want to avoid\n * re-running when object references change but values stay the same.\n *\n * @param params - The FieldSelector containing schema, name, and optional discriminator\n * @returns An array of primitive values suitable for React dependency arrays\n *\n * @example\n * ```tsx\n * const memoizedValue = useMemo(() => {\n * return extractFieldFromSchema(params);\n * }, flattenFieldSelector(params));\n * ```\n */\nexport function flattenFieldSelector<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | {\n schema?: undefined;\n name?: undefined;\n discriminator?: undefined;\n },\n) {\n const { discriminator, ...rest } = params;\n\n return [\n ...Object.values(rest),\n ...(discriminator ? Object.values(discriminator) : []),\n ];\n}\n","'use client';\n\nimport {\n type Discriminator,\n type DiscriminatorKey,\n type DiscriminatorValue,\n type ExtractZodByPath,\n extractFieldFromSchema,\n type FieldSelector,\n getFieldChecks,\n requiresValidInput,\n type ValidPaths,\n type ZodUnionCheck,\n} from '@zod-utils/core';\nimport {\n type Context,\n createContext,\n type ReactNode,\n useContext,\n useMemo,\n} from 'react';\nimport type { z } from 'zod';\nimport type { SomeType } from 'zod/v4/core';\nimport { flattenFieldSelector } from './utils';\n\n/**\n * Type for the FormSchemaContext with full generic support.\n * @internal\n */\nexport type FormSchemaContextType<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n> = Context<Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n> | null>;\n\n/**\n * Context value type for FormSchemaContext.\n */\nexport type FormSchemaContextValue<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n> = Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n> | null;\n\n/**\n * React Context for providing Zod schema to form components.\n *\n * Use with {@link FormSchemaProvider} to provide schema context, and\n * {@link useFormSchema} to consume it in child components.\n */\nexport const FormSchemaContext = createContext<{\n schema: z.ZodType;\n discriminator?: {\n key: unknown;\n value: unknown;\n };\n} | null>(null);\n\n/**\n * Hook to access the form schema from context.\n *\n * The optional `_params` argument is used for TypeScript type inference only.\n * Pass your schema to get proper type narrowing of the context value.\n *\n * @param _params - Optional params for type inference (not used at runtime)\n * @returns The schema context value or null if not within a provider\n *\n * @example\n * ```tsx\n * // Without type params (returns generic context)\n * function MyFormField() {\n * const context = useFormSchema();\n * if (!context) return null;\n *\n * const { schema, discriminator } = context;\n * // Use schema for validation or field extraction\n * }\n *\n * // With type params (for type-safe schema access)\n * function TypedFormField() {\n * const context = useFormSchema({ schema: mySchema });\n * // context.schema is now typed as typeof mySchema\n * }\n * ```\n */\nexport function useFormSchema<\n TSchema extends z.ZodType = z.ZodType,\n TDiscriminatorKey extends\n DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n // Parameter used for type inference only, not at runtime\n _params?: TSchema extends z.ZodPipe<infer In, SomeType>\n ? In extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }\n : TSchema extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n },\n): FormSchemaContextValue<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n> {\n return useContext(\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n FormSchemaContext as Context<Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n > | null>,\n );\n}\n\n/**\n * Provider component that makes Zod schema available to all child components.\n *\n * Use this to wrap your form and provide schema context to nested components\n * like field labels and validation indicators.\n *\n * @example\n * Basic usage with ZodObject\n * ```tsx\n * const schema = z.object({\n * name: z.string(),\n * email: z.string().email().optional()\n * });\n *\n * <FormSchemaProvider schema={schema}>\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n *\n * @example\n * Usage with discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() })\n * ]);\n *\n * <FormSchemaProvider\n * schema={schema}\n * discriminator={{ key: 'mode', value: 'create' }}\n * >\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n */\nexport function FormSchemaProvider<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n discriminator,\n children,\n}: (TSchema extends z.ZodPipe<infer In, SomeType>\n ? In extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }\n : TSchema extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }) & {\n children: ReactNode;\n}) {\n return (\n <FormSchemaContext.Provider value={{ schema, discriminator }}>\n {children}\n </FormSchemaContext.Provider>\n );\n}\n\n/**\n * Hook to check if a field requires valid input based on the Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n *\n * @param params - Schema, name, and optional discriminator (schema and name are optional)\n * @returns true if the field requires valid input, false if it doesn't or if schema/name is not provided\n *\n * @example\n * ```tsx\n * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {\n * const isRequired = useIsRequiredField({ schema, name });\n *\n * return (\n * <label>\n * {name}\n * {isRequired && <span className=\"text-red-500\">*</span>}\n * </label>\n * );\n * }\n * ```\n */\nexport function useIsRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | {\n schema?: undefined;\n name?: undefined;\n discriminator?: undefined;\n },\n): boolean {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params.schema || !params.name) {\n return false;\n }\n\n return isRequiredField(params);\n }, [...flattenFieldSelector(params)]);\n}\n\n/**\n * Determines if a field requires valid input (will show validation errors on empty/invalid input).\n *\n * Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after\n * removing defaults. This tells you if the field will error when user submits empty input.\n *\n * Returns false if the underlying field accepts:\n * - `undefined` (via `.optional()`)\n * - `null` (via `.nullable()`)\n * - Empty strings (plain `z.string()` without `.min(1)`)\n * - Empty arrays (plain `z.array()` without `.min(1)`)\n *\n * @param options - Schema, field name, and optional discriminator\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * bio: z.string().optional(),\n * });\n *\n * isRequiredField({ schema, name: 'name' }); // true\n * isRequiredField({ schema, name: 'bio' }); // false\n * ```\n *\n * @example\n * With discriminated union\n * ```typescript\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string().min(1) }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * isRequiredField({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * }); // true\n * ```\n */\nexport function isRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): boolean {\n const field = extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params);\n\n if (!field) {\n return false;\n }\n\n return requiresValidInput(field);\n}\n\n/**\n * React hook to extract a field's Zod schema from a parent schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Supports nested paths and discriminated unions.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns The Zod schema for the field, or undefined if not found\n *\n * @example\n * ```tsx\n * function MyFieldInfo({ name, schema }: { name: string; schema: z.ZodType }) {\n * const fieldSchema = useExtractFieldFromSchema({ schema, name });\n *\n * if (!fieldSchema) return null;\n *\n * // Use fieldSchema for custom validation or field info\n * return <span>{fieldSchema._zod.typeName}</span>;\n * }\n * ```\n *\n * @example\n * With discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * const fieldSchema = useExtractFieldFromSchema({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * });\n * // Returns z.string() schema\n * ```\n */\nexport function useExtractFieldFromSchema<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(\n () =>\n extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params),\n [...flattenFieldSelector(params)],\n );\n}\n\n/**\n * Hook to get validation checks from a field's Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Combines field extraction and check retrieval in one cached operation.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns Array of validation checks (min, max, pattern, etc.) or empty array\n *\n * @example\n * ```tsx\n * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {\n * const checks = useFieldChecks({ schema, name });\n *\n * const maxLength = checks.find(c => c.check === 'max_length');\n * if (maxLength) {\n * return <span>Max {maxLength.maximum} characters</span>;\n * }\n * return null;\n * }\n * ```\n */\nexport function useFieldChecks<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): ZodUnionCheck[] {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n const field = extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params);\n if (!field) return [];\n return getFieldChecks(field);\n }, [...flattenFieldSelector(params)]);\n}\n","import { zodResolver } from '@hookform/resolvers/zod';\nimport { type FieldValues, type UseFormProps, useForm } from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TFormInput - The form input type (defaults to PartialWithNullableObjects<TInput>)\n * @template TDefaultValues - The type of default values (inferred from usage for better type safety)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`\n * @param options.defaultValues - Default form values (shallow partial - nested objects must be complete if provided)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * Without default values (all fields are optional during editing)\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * age: z.number(),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * // ✅ No defaultValues needed - fields are optional during editing\n * const form = useZodForm({ schema });\n *\n * // Form fields can be set individually as user types\n * form.setValue('name', 'John');\n * form.setValue('email', 'john@example.com');\n * form.setValue('age', 25);\n *\n * // All fields must be valid on submit (per schema validation)\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n TInput extends FieldValues,\n TOutput extends FieldValues,\n TFormInput extends\n PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>,\n TDefaultValues extends Partial<TFormInput> | undefined = undefined,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TDefaultValues;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<\n UseFormProps<TFormInput, unknown, TOutput>,\n 'resolver' | 'defaultValues'\n>) => {\n const resolver = zodResolver(schema, zodResolverOptions);\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useForm({\n resolver,\n ...formOptions,\n } as unknown as UseFormProps<TFormInput, unknown, TOutput>);\n};\n"]}
package/dist/index.mjs CHANGED
@@ -35,8 +35,9 @@ var __objRest = (source, exclude) => {
35
35
  };
36
36
 
37
37
  // src/utils.ts
38
- function mergeFormFieldSelectorProps(factoryProps, componentProps) {
39
- return __spreadValues(__spreadValues({}, factoryProps), componentProps);
38
+ function toFormFieldSelector(props) {
39
+ const { schema, name, discriminator } = props;
40
+ return { schema, name, discriminator };
40
41
  }
41
42
  function flattenFieldSelector(params) {
42
43
  const _a = params, { discriminator } = _a, rest = __objRest(_a, ["discriminator"]);
@@ -101,6 +102,6 @@ var useZodForm = (_a) => {
101
102
  }, formOptions));
102
103
  };
103
104
 
104
- export { FormSchemaContext, FormSchemaProvider, flattenFieldSelector, isRequiredField, mergeFormFieldSelectorProps, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
105
+ export { FormSchemaContext, FormSchemaProvider, flattenFieldSelector, isRequiredField, toFormFieldSelector, useExtractFieldFromSchema, useFieldChecks, useFormSchema, useIsRequiredField, useZodForm };
105
106
  //# sourceMappingURL=index.mjs.map
106
107
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils.ts","../src/context.tsx","../src/use-zod-form.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCO,SAAS,2BAAA,CAiBd,cACA,cAAA,EAgBA;AAEA,EAAA,OAAO,kCAAK,YAAA,CAAA,EAAiB,cAAA,CAAA;AAS/B;AAkBO,SAAS,qBAcd,MAAA,EAcA;AACA,EAAA,MAAmC,aAA3B,EAAA,aAAA,EA7HV,GA6HqC,EAAA,EAAT,IAAA,GAAA,SAAA,CAAS,IAAT,CAAlB,eAAA,CAAA,CAAA;AAER,EAAA,OAAO;AAAA,IACL,GAAG,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA;AAAA,IACrB,GAAI,aAAA,GAAgB,MAAA,CAAO,MAAA,CAAO,aAAa,IAAI;AAAC,GACtD;AACF;AC7CO,IAAM,iBAAA,GAAoB,cAMvB,IAAI;AA6BP,SAAS,cAYd,OAAA,EAiCA;AACA,EAAA,OAAO,UAAA;AAAA;AAAA,IAEL;AAAA,GAiBF;AACF;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EA4BG;AACD,EAAA,uBACE,GAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,mBAcd,MAAA,EAcS;AAET,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,CAAO,MAAA,IAAU,CAAC,OAAO,IAAA,EAAM;AAClC,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,gBAAgB,MAAM,CAAA;AAAA,EAC/B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AA2CO,SAAS,gBAcd,MAAA,EAQS;AACT,EAAA,MAAM,KAAA,GAAQ,uBAOZ,MAAM,CAAA;AAER,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,mBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,0BAiBd,MAAA,EAQ4D;AAE5D,EAAA,OAAO,OAAA;AAAA,IACL,MACE,uBAOE,MAAM,CAAA;AAAA,IACV,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC;AAAA,GAClC;AACF;AAwBO,SAAS,eAcd,MAAA,EAQiB;AAEjB,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,MAAM,KAAA,GAAQ,uBAOZ,MAAM,CAAA;AACR,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAO,eAAe,KAAK,CAAA;AAAA,EAC7B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;ACnXO,IAAM,UAAA,GAAa,CAMxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAtLF,GAoLE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAUA,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,MAAA,EAAQ,kBAAkB,CAAA;AAGvD,EAAA,OAAO,OAAA,CAAQ,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CACqD,CAAA;AAC5D","file":"index.mjs","sourcesContent":["import type {\n Discriminator,\n DiscriminatorKey,\n DiscriminatorValue,\n FieldSelector,\n ValidPaths,\n} from '@zod-utils/core';\nimport type { z } from 'zod';\nimport type {\n FormFieldSelector,\n InferredFieldValues,\n ValidFieldPaths,\n} from './types';\n\n/**\n * Merges factory props with component props into a FormFieldSelector.\n * Encapsulates type assertion so callers don't need eslint-disable.\n *\n * This is the React Hook Form specific version that uses ValidFieldPaths\n * (which includes RHF's Path type constraint).\n *\n * @param factoryProps - Props from factory function (contains schema)\n * @param componentProps - Props from component call (contains name, discriminator)\n * @returns Properly typed FormFieldSelector\n *\n * @example\n * ```typescript\n * const selectorProps = mergeFormFieldSelectorProps(\n * { schema: mySchema },\n * { name: 'fieldName', discriminator: { key: 'mode', value: 'create' } }\n * );\n * ```\n */\nexport function mergeFormFieldSelectorProps<\n TSchema extends z.ZodType,\n TPath extends ValidFieldPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFieldValues extends\n InferredFieldValues<TSchema> = InferredFieldValues<TSchema>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n factoryProps: { schema: TSchema },\n componentProps: {\n name: TPath;\n discriminator?: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n },\n): FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n> {\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return { ...factoryProps, ...componentProps } as FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n >;\n}\n\n/**\n * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.\n *\n * This is useful for `useMemo` and `useCallback` dependencies where you want to avoid\n * re-running when object references change but values stay the same.\n *\n * @param params - The FieldSelector containing schema, name, and optional discriminator\n * @returns An array of primitive values suitable for React dependency arrays\n *\n * @example\n * ```tsx\n * const memoizedValue = useMemo(() => {\n * return extractFieldFromSchema(params);\n * }, flattenFieldSelector(params));\n * ```\n */\nexport function flattenFieldSelector<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | {\n schema?: undefined;\n name?: undefined;\n discriminator?: undefined;\n },\n) {\n const { discriminator, ...rest } = params;\n\n return [\n ...Object.values(rest),\n ...(discriminator ? Object.values(discriminator) : []),\n ];\n}\n","'use client';\n\nimport {\n type Discriminator,\n type DiscriminatorKey,\n type DiscriminatorValue,\n type ExtractZodByPath,\n extractFieldFromSchema,\n type FieldSelector,\n getFieldChecks,\n requiresValidInput,\n type ValidPaths,\n type ZodUnionCheck,\n} from '@zod-utils/core';\nimport {\n type Context,\n createContext,\n type ReactNode,\n useContext,\n useMemo,\n} from 'react';\nimport type { z } from 'zod';\nimport type { SomeType } from 'zod/v4/core';\nimport { flattenFieldSelector } from './utils';\n\n/**\n * Type for the FormSchemaContext with full generic support.\n * @internal\n */\nexport type FormSchemaContextType<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n> = Context<Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n> | null>;\n\n/**\n * Context value type for FormSchemaContext.\n */\nexport type FormSchemaContextValue<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n> = Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n> | null;\n\n/**\n * React Context for providing Zod schema to form components.\n *\n * Use with {@link FormSchemaProvider} to provide schema context, and\n * {@link useFormSchema} to consume it in child components.\n */\nexport const FormSchemaContext = createContext<{\n schema: z.ZodType;\n discriminator?: {\n key: unknown;\n value: unknown;\n };\n} | null>(null);\n\n/**\n * Hook to access the form schema from context.\n *\n * The optional `_params` argument is used for TypeScript type inference only.\n * Pass your schema to get proper type narrowing of the context value.\n *\n * @param _params - Optional params for type inference (not used at runtime)\n * @returns The schema context value or null if not within a provider\n *\n * @example\n * ```tsx\n * // Without type params (returns generic context)\n * function MyFormField() {\n * const context = useFormSchema();\n * if (!context) return null;\n *\n * const { schema, discriminator } = context;\n * // Use schema for validation or field extraction\n * }\n *\n * // With type params (for type-safe schema access)\n * function TypedFormField() {\n * const context = useFormSchema({ schema: mySchema });\n * // context.schema is now typed as typeof mySchema\n * }\n * ```\n */\nexport function useFormSchema<\n TSchema extends z.ZodType = z.ZodType,\n TDiscriminatorKey extends\n DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n // Parameter used for type inference only, not at runtime\n _params?: TSchema extends z.ZodPipe<infer In, SomeType>\n ? In extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }\n : TSchema extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n },\n): FormSchemaContextValue<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n> {\n return useContext(\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n FormSchemaContext as Context<Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n > | null>,\n );\n}\n\n/**\n * Provider component that makes Zod schema available to all child components.\n *\n * Use this to wrap your form and provide schema context to nested components\n * like field labels and validation indicators.\n *\n * @example\n * Basic usage with ZodObject\n * ```tsx\n * const schema = z.object({\n * name: z.string(),\n * email: z.string().email().optional()\n * });\n *\n * <FormSchemaProvider schema={schema}>\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n *\n * @example\n * Usage with discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() })\n * ]);\n *\n * <FormSchemaProvider\n * schema={schema}\n * discriminator={{ key: 'mode', value: 'create' }}\n * >\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n */\nexport function FormSchemaProvider<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n discriminator,\n children,\n}: (TSchema extends z.ZodPipe<infer In, SomeType>\n ? In extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }\n : TSchema extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }) & {\n children: ReactNode;\n}) {\n return (\n <FormSchemaContext.Provider value={{ schema, discriminator }}>\n {children}\n </FormSchemaContext.Provider>\n );\n}\n\n/**\n * Hook to check if a field requires valid input based on the Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n *\n * @param params - Schema, name, and optional discriminator (schema and name are optional)\n * @returns true if the field requires valid input, false if it doesn't or if schema/name is not provided\n *\n * @example\n * ```tsx\n * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {\n * const isRequired = useIsRequiredField({ schema, name });\n *\n * return (\n * <label>\n * {name}\n * {isRequired && <span className=\"text-red-500\">*</span>}\n * </label>\n * );\n * }\n * ```\n */\nexport function useIsRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | {\n schema?: undefined;\n name?: undefined;\n discriminator?: undefined;\n },\n): boolean {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params.schema || !params.name) {\n return false;\n }\n\n return isRequiredField(params);\n }, [...flattenFieldSelector(params)]);\n}\n\n/**\n * Determines if a field requires valid input (will show validation errors on empty/invalid input).\n *\n * Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after\n * removing defaults. This tells you if the field will error when user submits empty input.\n *\n * Returns false if the underlying field accepts:\n * - `undefined` (via `.optional()`)\n * - `null` (via `.nullable()`)\n * - Empty strings (plain `z.string()` without `.min(1)`)\n * - Empty arrays (plain `z.array()` without `.min(1)`)\n *\n * @param options - Schema, field name, and optional discriminator\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * bio: z.string().optional(),\n * });\n *\n * isRequiredField({ schema, name: 'name' }); // true\n * isRequiredField({ schema, name: 'bio' }); // false\n * ```\n *\n * @example\n * With discriminated union\n * ```typescript\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string().min(1) }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * isRequiredField({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * }); // true\n * ```\n */\nexport function isRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): boolean {\n const field = extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params);\n\n if (!field) {\n return false;\n }\n\n return requiresValidInput(field);\n}\n\n/**\n * React hook to extract a field's Zod schema from a parent schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Supports nested paths and discriminated unions.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns The Zod schema for the field, or undefined if not found\n *\n * @example\n * ```tsx\n * function MyFieldInfo({ name, schema }: { name: string; schema: z.ZodType }) {\n * const fieldSchema = useExtractFieldFromSchema({ schema, name });\n *\n * if (!fieldSchema) return null;\n *\n * // Use fieldSchema for custom validation or field info\n * return <span>{fieldSchema._zod.typeName}</span>;\n * }\n * ```\n *\n * @example\n * With discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * const fieldSchema = useExtractFieldFromSchema({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * });\n * // Returns z.string() schema\n * ```\n */\nexport function useExtractFieldFromSchema<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(\n () =>\n extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params),\n [...flattenFieldSelector(params)],\n );\n}\n\n/**\n * Hook to get validation checks from a field's Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Combines field extraction and check retrieval in one cached operation.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns Array of validation checks (min, max, pattern, etc.) or empty array\n *\n * @example\n * ```tsx\n * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {\n * const checks = useFieldChecks({ schema, name });\n *\n * const maxLength = checks.find(c => c.check === 'max_length');\n * if (maxLength) {\n * return <span>Max {maxLength.maximum} characters</span>;\n * }\n * return null;\n * }\n * ```\n */\nexport function useFieldChecks<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): ZodUnionCheck[] {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n const field = extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params);\n if (!field) return [];\n return getFieldChecks(field);\n }, [...flattenFieldSelector(params)]);\n}\n","import { zodResolver } from '@hookform/resolvers/zod';\nimport { type FieldValues, type UseFormProps, useForm } from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TFormInput - The form input type (defaults to PartialWithNullableObjects<TInput>)\n * @template TDefaultValues - The type of default values (inferred from usage for better type safety)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`\n * @param options.defaultValues - Default form values (shallow partial - nested objects must be complete if provided)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * Without default values (all fields are optional during editing)\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * age: z.number(),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * // ✅ No defaultValues needed - fields are optional during editing\n * const form = useZodForm({ schema });\n *\n * // Form fields can be set individually as user types\n * form.setValue('name', 'John');\n * form.setValue('email', 'john@example.com');\n * form.setValue('age', 25);\n *\n * // All fields must be valid on submit (per schema validation)\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n TInput extends FieldValues,\n TOutput extends FieldValues,\n TFormInput extends\n PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>,\n TDefaultValues extends Partial<TFormInput> | undefined = undefined,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TDefaultValues;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<\n UseFormProps<TFormInput, unknown, TOutput>,\n 'resolver' | 'defaultValues'\n>) => {\n const resolver = zodResolver(schema, zodResolverOptions);\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useForm({\n resolver,\n ...formOptions,\n } as unknown as UseFormProps<TFormInput, unknown, TOutput>);\n};\n"]}
1
+ {"version":3,"sources":["../src/utils.ts","../src/context.tsx","../src/use-zod-form.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBO,SAAS,oBAgBd,KAAA,EAYA;AACA,EAAA,MAAM,EAAE,MAAA,EAAQ,IAAA,EAAM,aAAA,EAAc,GAAI,KAAA;AAExC,EAAA,OAAO,EAAE,MAAA,EAAQ,IAAA,EAAM,aAAA,EAAc;AASvC;AAkBO,SAAS,qBAcd,MAAA,EAcA;AACA,EAAA,MAAmC,aAA3B,EAAA,aAAA,EAhHV,GAgHqC,EAAA,EAAT,IAAA,GAAA,SAAA,CAAS,IAAT,CAAlB,eAAA,CAAA,CAAA;AAER,EAAA,OAAO;AAAA,IACL,GAAG,MAAA,CAAO,MAAA,CAAO,IAAI,CAAA;AAAA,IACrB,GAAI,aAAA,GAAgB,MAAA,CAAO,MAAA,CAAO,aAAa,IAAI;AAAC,GACtD;AACF;AChCO,IAAM,iBAAA,GAAoB,cAMvB,IAAI;AA6BP,SAAS,cAYd,OAAA,EAiCA;AACA,EAAA,OAAO,UAAA;AAAA;AAAA,IAEL;AAAA,GAiBF;AACF;AAqCO,SAAS,kBAAA,CAId;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA;AACF,CAAA,EA4BG;AACD,EAAA,uBACE,GAAA,CAAC,kBAAkB,QAAA,EAAlB,EAA2B,OAAO,EAAE,MAAA,EAAQ,aAAA,EAAc,EACxD,QAAA,EACH,CAAA;AAEJ;AAwBO,SAAS,mBAcd,MAAA,EAcS;AAET,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,IAAI,CAAC,MAAA,CAAO,MAAA,IAAU,CAAC,OAAO,IAAA,EAAM;AAClC,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,gBAAgB,MAAM,CAAA;AAAA,EAC/B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;AA2CO,SAAS,gBAcd,MAAA,EAQS;AACT,EAAA,MAAM,KAAA,GAAQ,uBAOZ,MAAM,CAAA;AAER,EAAA,IAAI,CAAC,KAAA,EAAO;AACV,IAAA,OAAO,KAAA;AAAA,EACT;AAEA,EAAA,OAAO,mBAAmB,KAAK,CAAA;AACjC;AAuCO,SAAS,0BAiBd,MAAA,EAQ4D;AAE5D,EAAA,OAAO,OAAA;AAAA,IACL,MACE,uBAOE,MAAM,CAAA;AAAA,IACV,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC;AAAA,GAClC;AACF;AAwBO,SAAS,eAcd,MAAA,EAQiB;AAEjB,EAAA,OAAO,QAAQ,MAAM;AACnB,IAAA,MAAM,KAAA,GAAQ,uBAOZ,MAAM,CAAA;AACR,IAAA,IAAI,CAAC,KAAA,EAAO,OAAO,EAAC;AACpB,IAAA,OAAO,eAAe,KAAK,CAAA;AAAA,EAC7B,GAAG,CAAC,GAAG,oBAAA,CAAqB,MAAM,CAAC,CAAC,CAAA;AACtC;ACnXO,IAAM,UAAA,GAAa,CAMxB,EAAA,KAWI;AAXJ,EAAA,IAAA,EAAA,GAAA,EAAA,EACA;AAAA,IAAA,MAAA;AAAA,IACA;AAAA,GAtLF,GAoLE,EAAA,EAGG,WAAA,GAAA,SAAA,CAHH,EAAA,EAGG;AAAA,IAFH,QAAA;AAAA,IACA;AAAA,GAAA,CAAA;AAUA,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,MAAA,EAAQ,kBAAkB,CAAA;AAGvD,EAAA,OAAO,OAAA,CAAQ,cAAA,CAAA;AAAA,IACb;AAAA,GAAA,EACG,WAAA,CACqD,CAAA;AAC5D","file":"index.mjs","sourcesContent":["import type {\n DiscriminatorKey,\n DiscriminatorValue,\n FieldSelector,\n ValidPaths,\n} from '@zod-utils/core';\nimport type { z } from 'zod';\nimport type {\n FormFieldSelector,\n InferredFieldValues,\n ValidFieldPaths,\n} from './types';\n\n/**\n * Extracts a FormFieldSelector from props containing schema, name, and optional discriminator.\n * Encapsulates type assertion so callers don't need eslint-disable.\n *\n * @param props - Object containing schema, name, and optional discriminator\n * @returns Properly typed FormFieldSelector\n *\n * @example\n * ```typescript\n * const selectorProps = toFormFieldSelector<TSchema, TPath, ...>(props);\n * ```\n */\nexport function toFormFieldSelector<\n TSchema extends z.ZodType,\n TPath extends ValidFieldPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFieldValues extends\n InferredFieldValues<TSchema> = InferredFieldValues<TSchema>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(props: {\n schema: z.ZodType;\n name: string;\n discriminator?: { key: string; value: unknown };\n}): FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n> {\n const { schema, name, discriminator } = props;\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return { schema, name, discriminator } as FormFieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFieldValues,\n TFilterType,\n TStrict\n >;\n}\n\n/**\n * Flattens a FieldSelector into an array of primitive values for use in React dependency arrays.\n *\n * This is useful for `useMemo` and `useCallback` dependencies where you want to avoid\n * re-running when object references change but values stay the same.\n *\n * @param params - The FieldSelector containing schema, name, and optional discriminator\n * @returns An array of primitive values suitable for React dependency arrays\n *\n * @example\n * ```tsx\n * const memoizedValue = useMemo(() => {\n * return extractFieldFromSchema(params);\n * }, flattenFieldSelector(params));\n * ```\n */\nexport function flattenFieldSelector<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | {\n schema?: undefined;\n name?: undefined;\n discriminator?: undefined;\n },\n) {\n const { discriminator, ...rest } = params;\n\n return [\n ...Object.values(rest),\n ...(discriminator ? Object.values(discriminator) : []),\n ];\n}\n","'use client';\n\nimport {\n type Discriminator,\n type DiscriminatorKey,\n type DiscriminatorValue,\n type ExtractZodByPath,\n extractFieldFromSchema,\n type FieldSelector,\n getFieldChecks,\n requiresValidInput,\n type ValidPaths,\n type ZodUnionCheck,\n} from '@zod-utils/core';\nimport {\n type Context,\n createContext,\n type ReactNode,\n useContext,\n useMemo,\n} from 'react';\nimport type { z } from 'zod';\nimport type { SomeType } from 'zod/v4/core';\nimport { flattenFieldSelector } from './utils';\n\n/**\n * Type for the FormSchemaContext with full generic support.\n * @internal\n */\nexport type FormSchemaContextType<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n> = Context<Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n> | null>;\n\n/**\n * Context value type for FormSchemaContext.\n */\nexport type FormSchemaContextValue<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n> = Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n> | null;\n\n/**\n * React Context for providing Zod schema to form components.\n *\n * Use with {@link FormSchemaProvider} to provide schema context, and\n * {@link useFormSchema} to consume it in child components.\n */\nexport const FormSchemaContext = createContext<{\n schema: z.ZodType;\n discriminator?: {\n key: unknown;\n value: unknown;\n };\n} | null>(null);\n\n/**\n * Hook to access the form schema from context.\n *\n * The optional `_params` argument is used for TypeScript type inference only.\n * Pass your schema to get proper type narrowing of the context value.\n *\n * @param _params - Optional params for type inference (not used at runtime)\n * @returns The schema context value or null if not within a provider\n *\n * @example\n * ```tsx\n * // Without type params (returns generic context)\n * function MyFormField() {\n * const context = useFormSchema();\n * if (!context) return null;\n *\n * const { schema, discriminator } = context;\n * // Use schema for validation or field extraction\n * }\n *\n * // With type params (for type-safe schema access)\n * function TypedFormField() {\n * const context = useFormSchema({ schema: mySchema });\n * // context.schema is now typed as typeof mySchema\n * }\n * ```\n */\nexport function useFormSchema<\n TSchema extends z.ZodType = z.ZodType,\n TDiscriminatorKey extends\n DiscriminatorKey<TSchema> = DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n // Parameter used for type inference only, not at runtime\n _params?: TSchema extends z.ZodPipe<infer In, SomeType>\n ? In extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }\n : TSchema extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n },\n): FormSchemaContextValue<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n> {\n return useContext(\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n FormSchemaContext as Context<Omit<\n FieldSelector<\n TSchema,\n ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n 'name'\n > | null>,\n );\n}\n\n/**\n * Provider component that makes Zod schema available to all child components.\n *\n * Use this to wrap your form and provide schema context to nested components\n * like field labels and validation indicators.\n *\n * @example\n * Basic usage with ZodObject\n * ```tsx\n * const schema = z.object({\n * name: z.string(),\n * email: z.string().email().optional()\n * });\n *\n * <FormSchemaProvider schema={schema}>\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n *\n * @example\n * Usage with discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() })\n * ]);\n *\n * <FormSchemaProvider\n * schema={schema}\n * discriminator={{ key: 'mode', value: 'create' }}\n * >\n * <YourFormComponents />\n * </FormSchemaProvider>\n * ```\n */\nexport function FormSchemaProvider<\n TSchema extends z.ZodType,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n>({\n schema,\n discriminator,\n children,\n}: (TSchema extends z.ZodPipe<infer In, SomeType>\n ? In extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }\n : TSchema extends z.ZodDiscriminatedUnion\n ? {\n schema: TSchema;\n discriminator: Discriminator<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue\n >;\n }\n : {\n schema: TSchema;\n discriminator?: undefined;\n }) & {\n children: ReactNode;\n}) {\n return (\n <FormSchemaContext.Provider value={{ schema, discriminator }}>\n {children}\n </FormSchemaContext.Provider>\n );\n}\n\n/**\n * Hook to check if a field requires valid input based on the Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n *\n * @param params - Schema, name, and optional discriminator (schema and name are optional)\n * @returns true if the field requires valid input, false if it doesn't or if schema/name is not provided\n *\n * @example\n * ```tsx\n * function MyFieldLabel({ name, schema }: { name: string; schema: z.ZodType }) {\n * const isRequired = useIsRequiredField({ schema, name });\n *\n * return (\n * <label>\n * {name}\n * {isRequired && <span className=\"text-red-500\">*</span>}\n * </label>\n * );\n * }\n * ```\n */\nexport function useIsRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params:\n | FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >\n | {\n schema?: undefined;\n name?: undefined;\n discriminator?: undefined;\n },\n): boolean {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n if (!params.schema || !params.name) {\n return false;\n }\n\n return isRequiredField(params);\n }, [...flattenFieldSelector(params)]);\n}\n\n/**\n * Determines if a field requires valid input (will show validation errors on empty/invalid input).\n *\n * Uses `requiresValidInput` from `@zod-utils/core` which checks the underlying field after\n * removing defaults. This tells you if the field will error when user submits empty input.\n *\n * Returns false if the underlying field accepts:\n * - `undefined` (via `.optional()`)\n * - `null` (via `.nullable()`)\n * - Empty strings (plain `z.string()` without `.min(1)`)\n * - Empty arrays (plain `z.array()` without `.min(1)`)\n *\n * @param options - Schema, field name, and optional discriminator\n * @returns true if the field requires valid input, false otherwise\n *\n * @example\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * bio: z.string().optional(),\n * });\n *\n * isRequiredField({ schema, name: 'name' }); // true\n * isRequiredField({ schema, name: 'bio' }); // false\n * ```\n *\n * @example\n * With discriminated union\n * ```typescript\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string().min(1) }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * isRequiredField({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * }); // true\n * ```\n */\nexport function isRequiredField<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): boolean {\n const field = extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params);\n\n if (!field) {\n return false;\n }\n\n return requiresValidInput(field);\n}\n\n/**\n * React hook to extract a field's Zod schema from a parent schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Supports nested paths and discriminated unions.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns The Zod schema for the field, or undefined if not found\n *\n * @example\n * ```tsx\n * function MyFieldInfo({ name, schema }: { name: string; schema: z.ZodType }) {\n * const fieldSchema = useExtractFieldFromSchema({ schema, name });\n *\n * if (!fieldSchema) return null;\n *\n * // Use fieldSchema for custom validation or field info\n * return <span>{fieldSchema._zod.typeName}</span>;\n * }\n * ```\n *\n * @example\n * With discriminated union\n * ```tsx\n * const schema = z.discriminatedUnion('mode', [\n * z.object({ mode: z.literal('create'), name: z.string() }),\n * z.object({ mode: z.literal('edit'), id: z.number() }),\n * ]);\n *\n * const fieldSchema = useExtractFieldFromSchema({\n * schema,\n * name: 'name',\n * discriminator: { key: 'mode', value: 'create' },\n * });\n * // Returns z.string() schema\n * ```\n */\nexport function useExtractFieldFromSchema<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema> = never,\n TDiscriminatorValue extends DiscriminatorValue<\n TSchema,\n TDiscriminatorKey\n > = never,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): (ExtractZodByPath<TSchema, TPath> & z.ZodType) | undefined {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(\n () =>\n extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params),\n [...flattenFieldSelector(params)],\n );\n}\n\n/**\n * Hook to get validation checks from a field's Zod schema.\n *\n * Memoized - only recalculates when schema, name, or discriminator changes.\n * Combines field extraction and check retrieval in one cached operation.\n *\n * @param params - Schema, name, and optional discriminator\n * @returns Array of validation checks (min, max, pattern, etc.) or empty array\n *\n * @example\n * ```tsx\n * function MyFieldHint({ schema, name }: { schema: z.ZodType; name: string }) {\n * const checks = useFieldChecks({ schema, name });\n *\n * const maxLength = checks.find(c => c.check === 'max_length');\n * if (maxLength) {\n * return <span>Max {maxLength.maximum} characters</span>;\n * }\n * return null;\n * }\n * ```\n */\nexport function useFieldChecks<\n TSchema extends z.ZodType,\n TPath extends ValidPaths<\n TSchema,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n TDiscriminatorKey extends DiscriminatorKey<TSchema>,\n TDiscriminatorValue extends DiscriminatorValue<TSchema, TDiscriminatorKey>,\n TFilterType = unknown,\n TStrict extends boolean = true,\n>(\n params: FieldSelector<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >,\n): ZodUnionCheck[] {\n // biome-ignore lint/correctness/useExhaustiveDependencies: using flattenFieldSelector for stable deps\n return useMemo(() => {\n const field = extractFieldFromSchema<\n TSchema,\n TPath,\n TDiscriminatorKey,\n TDiscriminatorValue,\n TFilterType,\n TStrict\n >(params);\n if (!field) return [];\n return getFieldChecks(field);\n }, [...flattenFieldSelector(params)]);\n}\n","import { zodResolver } from '@hookform/resolvers/zod';\nimport { type FieldValues, type UseFormProps, useForm } from 'react-hook-form';\nimport type { z } from 'zod';\nimport type {\n PartialWithAllNullables,\n PartialWithNullableObjects,\n} from './types';\n\n/**\n * Type-safe React Hook Form wrapper with automatic Zod v4 schema validation and type transformation.\n *\n * This hook eliminates the TypeScript friction between React Hook Form's nullable field values\n * and Zod's strict output types. It uses a two-type schema pattern where:\n * - **Input type** (`PartialWithNullableObjects<TOutput>`): Form fields accept `null | undefined` during editing\n * - **Output type** (`TOutput`): Validated data matches exact schema type (no `null | undefined`)\n *\n * **Key Benefits:**\n * - ✅ No more \"Type 'null' is not assignable to...\" TypeScript errors\n * - ✅ Use `form.setValue()` and `form.reset()` with `null` values freely\n * - ✅ Validated output is still type-safe with exact Zod schema types\n * - ✅ Automatic zodResolver setup - no manual configuration needed\n *\n * @template TInput - The Zod schema input type (accepts nullable/undefined values during form editing)\n * @template TOutput - The Zod schema output type (extends FieldValues)\n * @template TFormInput - The form input type (defaults to PartialWithNullableObjects<TInput>)\n * @template TDefaultValues - The type of default values (inferred from usage for better type safety)\n *\n * @param options - Configuration object\n * @param options.schema - Zod schema with two-type signature `z.ZodType<TOutput, TInput>`\n * @param options.defaultValues - Default form values (shallow partial - nested objects must be complete if provided)\n * @param options.zodResolverOptions - Optional zodResolver configuration\n * @param options....formOptions - All other react-hook-form useForm options\n *\n * @returns React Hook Form instance with type-safe methods\n *\n * @example\n * Basic usage with required fields\n * ```typescript\n * import { useZodForm } from '@zod-utils/react-hook-form';\n * import { z } from 'zod';\n *\n * const schema = z.object({\n * name: z.string().min(1), // Required field\n * age: z.number().min(0),\n * }) satisfies z.ZodType<{ name: string; age: number }, any>;\n *\n * function MyForm() {\n * const form = useZodForm({ schema });\n *\n * // ✅ These work without type errors:\n * form.setValue('name', null); // Accepts null during editing\n * form.reset({ name: null, age: null }); // Reset with null\n *\n * const onSubmit = (data: { name: string; age: number }) => {\n * // ✅ data is exact type - no null | undefined\n * console.log(data.name.toUpperCase()); // Safe to use string methods\n * };\n *\n * return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>;\n * }\n * ```\n *\n * @example\n * With default values\n * ```typescript\n * const schema = z.object({\n * username: z.string(),\n * email: z.string().email(),\n * notifications: z.boolean().default(true),\n * }) satisfies z.ZodType<{\n * username: string;\n * email: string;\n * notifications: boolean;\n * }, any>;\n *\n * const form = useZodForm({\n * schema,\n * defaultValues: {\n * username: '',\n * email: '',\n * // notifications gets default from schema\n * },\n * });\n * ```\n *\n * @example\n * Without default values (all fields are optional during editing)\n * ```typescript\n * const schema = z.object({\n * name: z.string().min(1),\n * email: z.string().email(),\n * age: z.number(),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * // ✅ No defaultValues needed - fields are optional during editing\n * const form = useZodForm({ schema });\n *\n * // Form fields can be set individually as user types\n * form.setValue('name', 'John');\n * form.setValue('email', 'john@example.com');\n * form.setValue('age', 25);\n *\n * // All fields must be valid on submit (per schema validation)\n * ```\n *\n * @example\n * With optional and nullable fields\n * ```typescript\n * const schema = z.object({\n * title: z.string(),\n * description: z.string().optional(), // Optional in output\n * tags: z.array(z.string()).nullable(), // Nullable in output\n * }) satisfies z.ZodType<{\n * title: string;\n * description?: string;\n * tags: string[] | null;\n * }, any>;\n *\n * const form = useZodForm({ schema });\n *\n * // All fields accept null/undefined during editing\n * form.setValue('title', null);\n * form.setValue('description', undefined);\n * form.setValue('tags', null);\n * ```\n *\n * @example\n * With zodResolver options\n * ```typescript\n * const form = useZodForm({\n * schema,\n * zodResolverOptions: {\n * async: true, // Enable async validation\n * errorMap: customErrorMap, // Custom error messages\n * },\n * });\n * ```\n *\n * @example\n * Complete form example\n * ```typescript\n * const userSchema = z.object({\n * name: z.string().min(1, 'Name is required'),\n * email: z.string().email('Invalid email'),\n * age: z.number().min(18, 'Must be 18+'),\n * }) satisfies z.ZodType<{ name: string; email: string; age: number }, any>;\n *\n * function UserForm() {\n * const form = useZodForm({\n * schema: userSchema,\n * defaultValues: { name: '', email: '', age: null },\n * });\n *\n * const onSubmit = (data: { name: string; email: string; age: number }) => {\n * // Type-safe: data has exact types, no null/undefined\n * console.log(`${data.name} is ${data.age} years old`);\n * };\n *\n * return (\n * <form onSubmit={form.handleSubmit(onSubmit)}>\n * <input {...form.register('name')} />\n * <input {...form.register('email')} type=\"email\" />\n * <input {...form.register('age', { valueAsNumber: true })} type=\"number\" />\n * <button type=\"submit\">Submit</button>\n * </form>\n * );\n * }\n * ```\n *\n * @see {@link PartialWithNullableObjects} for the type transformation utility\n * @see https://react-hook-form.com/docs/useform for React Hook Form documentation\n * @see https://zod.dev for Zod schema documentation\n * @since 0.1.0\n */\nexport const useZodForm = <\n TInput extends FieldValues,\n TOutput extends FieldValues,\n TFormInput extends\n PartialWithAllNullables<TInput> = PartialWithNullableObjects<TInput>,\n TDefaultValues extends Partial<TFormInput> | undefined = undefined,\n>({\n schema,\n zodResolverOptions,\n ...formOptions\n}: {\n schema: z.ZodType<TOutput, TInput>;\n defaultValues?: TDefaultValues;\n zodResolverOptions?: Parameters<typeof zodResolver>[1];\n} & Omit<\n UseFormProps<TFormInput, unknown, TOutput>,\n 'resolver' | 'defaultValues'\n>) => {\n const resolver = zodResolver(schema, zodResolverOptions);\n\n // eslint-disable-next-line @typescript-eslint/consistent-type-assertions\n return useForm({\n resolver,\n ...formOptions,\n } as unknown as UseFormProps<TFormInput, unknown, TOutput>);\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zod-utils/react-hook-form",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "React Hook Form integration and utilities for Zod schemas",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",