diff --git a/package.json b/package.json index 5deb641f..8defa442 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "react": "^19.1.0", "react-dom": "^19.1.0", "tailwindcss": "^4.1.0", - "zod": "^3.23.6" + "zod": "^3.25.x" }, "dependencies": { "@douglasneuroinformatics/libjs": "^2.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82be62d9..dff77333 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,7 +9,7 @@ importers: dependencies: '@douglasneuroinformatics/libjs': specifier: ^2.8.0 - version: 2.8.0(neverthrow@8.2.0)(zod@3.24.2) + version: 2.8.0(neverthrow@8.2.0)(zod@3.25.32) '@douglasneuroinformatics/libui-form-types': specifier: ^0.11.0 version: 0.11.0 @@ -137,8 +137,8 @@ importers: specifier: ^1.1.2 version: 1.1.2(@types/react-dom@19.0.4(@types/react@19.0.12))(@types/react@19.0.12)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) zod: - specifier: ^3.23.6 - version: 3.24.2 + specifier: ^3.25.x + version: 3.25.32 zustand: specifier: ^5.0.3 version: 5.0.3(@types/react@19.0.12)(react@19.1.0) @@ -6327,9 +6327,9 @@ packages: { integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ== } engines: { node: '>=18' } - zod@3.24.2: + zod@3.25.32: resolution: - { integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ== } + { integrity: sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g== } zustand@5.0.3: resolution: @@ -6603,7 +6603,7 @@ snapshots: - svelte - ts-node - '@douglasneuroinformatics/libjs@2.8.0(neverthrow@8.2.0)(zod@3.24.2)': + '@douglasneuroinformatics/libjs@2.8.0(neverthrow@8.2.0)(zod@3.25.32)': dependencies: clean-stack: 5.2.0 extract-stack: 3.0.0 @@ -6611,7 +6611,7 @@ snapshots: serialize-error: 12.0.0 stringify-object: 5.0.0 type-fest: 4.39.0 - zod: 3.24.2 + zod: 3.25.32 '@douglasneuroinformatics/libui-form-types@0.11.0': dependencies: @@ -11607,7 +11607,7 @@ snapshots: yoctocolors@2.1.1: {} - zod@3.24.2: {} + zod@3.25.32: {} zustand@5.0.3(@types/react@19.0.12)(react@19.1.0): optionalDependencies: diff --git a/src/components/Form/Form.stories.tsx b/src/components/Form/Form.stories.tsx index beeca4ab..0e29528e 100644 --- a/src/components/Form/Form.stories.tsx +++ b/src/components/Form/Form.stories.tsx @@ -7,11 +7,13 @@ import type { FormFields } from '@douglasneuroinformatics/libui-form-types'; import type FormTypes from '@douglasneuroinformatics/libui-form-types'; import type { Meta, StoryObj } from '@storybook/react'; import type { IntRange } from 'type-fest'; -import { z } from 'zod'; +import { z } from 'zod/v4'; import { Heading } from '../Heading'; import { Form } from './Form'; +import type { ZodTypeLike } from './types'; + const DISABLED = false; const $ExampleFormData = z.object({ @@ -49,7 +51,7 @@ const $ExampleFormData = z.object({ stringRadio: z.enum(['a', 'b', 'c']).optional() }); type ExampleFormSchemaType = typeof $ExampleFormData; -type ExampleFormData = z.TypeOf; +type ExampleFormData = z.infer; const $SimpleExampleFormData = z.object({ name: z.string() @@ -513,7 +515,7 @@ export const WithPreventReset: StoryObj } }; -export const WithSuspend: StoryObj, { delay?: number }>> = { +export const WithSuspend: StoryObj, { delay?: number }>> = { args: { content: { delay: { diff --git a/src/components/Form/Form.test.tsx b/src/components/Form/Form.test.tsx index dee5f734..61071283 100644 --- a/src/components/Form/Form.test.tsx +++ b/src/components/Form/Form.test.tsx @@ -2,7 +2,7 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { userEvent } from '@testing-library/user-event'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import type { Mock } from 'vitest'; -import { z } from 'zod'; +import { z } from 'zod/v4'; import { Form } from './Form'; diff --git a/src/components/Form/Form.tsx b/src/components/Form/Form.tsx index 2a6817b5..3b93183c 100644 --- a/src/components/Form/Form.tsx +++ b/src/components/Form/Form.tsx @@ -10,7 +10,6 @@ import type { import { get, set } from 'lodash-es'; import { twMerge } from 'tailwind-merge'; import type { Promisable } from 'type-fest'; -import { z } from 'zod'; import { useTranslation } from '@/hooks'; import { cn } from '@/utils'; @@ -22,9 +21,9 @@ import { ErrorMessage } from './ErrorMessage'; import { FieldsComponent } from './FieldsComponent'; import { getInitialValues } from './utils'; -import type { FormErrors } from './types'; +import type { FormErrors, ZodErrorLike, ZodTypeLike } from './types'; -type FormProps, TData extends z.TypeOf = z.TypeOf> = { +type FormProps, TData extends TSchema['_input'] = TSchema['_input']> = { [key: `data-${string}`]: unknown; additionalButtons?: { left?: React.ReactNode; @@ -42,7 +41,7 @@ type FormProps, TData extends z.TypeOf) => Promisable<{ errorMessage: string; success: false } | { success: true }>) | null; - onError?: (error: z.ZodError>) => void; + onError?: (error: ZodErrorLike) => void; onSubmit: (data: NoInfer) => Promisable; preventResetValuesOnReset?: boolean; readOnly?: boolean; @@ -50,10 +49,10 @@ type FormProps, TData extends z.TypeOf; + validationSchema: ZodTypeLike; }; -const Form = , TData extends z.TypeOf = z.TypeOf>({ +const Form = , TData extends TSchema['_input'] = TSchema['_input']>({ additionalButtons, className, content, @@ -81,7 +80,7 @@ const Form = , TData extends z.TypeOf) => { + const handleError = (error: ZodErrorLike) => { const fieldErrors: FormErrors = {}; const rootErrors: string[] = []; for (const issue of error.issues) { diff --git a/src/components/Form/types.ts b/src/components/Form/types.ts index e9cc675d..4684d019 100644 --- a/src/components/Form/types.ts +++ b/src/components/Form/types.ts @@ -25,3 +25,36 @@ export type BaseFieldComponentProps = { [K in keyof TData]?: FieldError; }; + +export type ZodIssueLike = { + [key: string]: any; + readonly code: string; + readonly message: string; + readonly path: PropertyKey[]; +}; + +export type ZodErrorLike = { + cause?: unknown; + issues: ZodIssueLike[]; + name: string; +}; + +export type ZodSafeParseResultLike = ZodSafeParseErrorLike | ZodSafeParseSuccessLike; + +export type ZodSafeParseSuccessLike = { + data: TOutput; + error?: never; + success: true; +}; + +export type ZodSafeParseErrorLike = { + data?: never; + error: ZodErrorLike; + success: false; +}; + +export type ZodTypeLike = { + readonly _input: TInput; + readonly _output: TOutput; + safeParseAsync: (data: unknown) => Promise>; +};