Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged

Next #131

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
# [6.0.0-next.1](https://github.com/form-atoms/field/compare/v5.4.0...v6.0.0-next.1) (2025-10-24)


* Zod 4 ([#129](https://github.com/form-atoms/field/issues/129)) ([c5ccdc7](https://github.com/form-atoms/field/commit/c5ccdc78224fa25b641ac04f2cff316b04dc8acd))


### BREAKING CHANGES

* zod4 is now being used

* fix story

# [5.4.0](https://github.com/form-atoms/field/compare/v5.3.3...v5.4.0) (2025-10-22)


Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
"typescript-eslint": "^8.46.2",
"vite": "^7.1.11",
"vitest": "^4.0.0",
"zod": "3.25.76"
"zod": "4.1.12"
},
"publishConfig": {
"access": "public"
Expand Down Expand Up @@ -128,6 +128,6 @@
"jotai": "^2",
"jotai-effect": "^2",
"react": ">=16.8",
"zod": "^3"
"zod": "^4"
}
}
2 changes: 1 addition & 1 deletion src/components/radio-group/RadioGroup.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export const RequiredCustomAddress = radioGroupStory({
value: undefined,
schema: z.object(
{ street: z.string(), city: z.string() },
{ required_error: "Please choose shipping address." },
{ error: "Please choose shipping address." },
),
}),
options: addresses,
Expand Down
2 changes: 1 addition & 1 deletion src/components/select/Select.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export const RequiredAddress = selectStory({
city: z.string(),
zip: z.string(),
},
{ required_error: "Please select address." },
{ error: "Please select address." },
),
}),
options: addresses,
Expand Down
2 changes: 1 addition & 1 deletion src/components/select/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ describe("<Select />", () => {

const schema = z.object({ id: z.string(), username: z.string() });

type User = (typeof schema)["_output"];
type User = z.output<typeof schema>;

const props = {
field: zodField({
Expand Down
2 changes: 1 addition & 1 deletion src/fields/array-field/arrayField.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe("arrayField()", () => {

await act(async () => actions.current.validate());
expect(errors.current).toEqual([
"Array must contain at most 2 element(s)",
"Too big: expected array to have <=2 items",
]);
});
});
Expand Down
10 changes: 5 additions & 5 deletions src/fields/array-field/arrayField.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import type { ArrayCardinality, ZodAny, ZodArray, ZodSchema } from "zod";
import type { ZodAny, ZodArray, ZodSchema } from "zod";
import { z } from "zod";

import { prepareSchema } from "../../utils";
import { FieldConfig } from "../field";
import { type ZodField, zodField } from "../zod-field";

export type ZodArrayField<Element extends ZodSchema = ZodAny> = ZodField<
ZodArray<Element, ArrayCardinality>,
ZodArray<Element, ArrayCardinality>
ZodArray<Element>,
ZodArray<Element>
>;

export type ArrayFieldParams<ElementSchema extends z.Schema> = FieldConfig<
ZodArray<ElementSchema, "atleastone">,
ZodArray<ElementSchema, "many">
ZodArray<ElementSchema>,
ZodArray<ElementSchema>
>;

export const arrayField = <ElementSchema extends z.Schema>({
Expand Down
17 changes: 8 additions & 9 deletions src/fields/array-field/stringArrayField.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@ import { FormFieldSubmitValues } from "../../components/form";

import type { StringArrayField } from "./stringArrayField";

test("required stringArrayField has '[string, ...string[]]' submit value", () => {
expectTypeOf<
FormFieldSubmitValues<{ field: StringArrayField }>
>().toEqualTypeOf<{
field: [string, ...string[]];
}>();
});
// test.skip("required stringArrayField has '[string, ...string[]]' submit value", () => {
// expectTypeOf<
// FormFieldSubmitValues<{ field: StringArrayField }>
// >().toEqualTypeOf<{
// field: [string, ...string[]];
// }>();
// });

test("optional stringArrayField has 'string[]' submit value", () => {
expectTypeOf<
FormFieldSubmitValues<{ field: ReturnType<StringArrayField["optional"]> }>
>().toEqualTypeOf<{
// TODO: narrow?
field: [string, ...string[]] | string[];
field: string[];
}>();
});
2 changes: 1 addition & 1 deletion src/fields/boolean-field/booleanField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ export const booleanField = ({
}: Omit<FieldConfig<ZodBoolean>, "schema" | "optionalSchema"> = {}) =>
zodField({
value: undefined,
schema: z.boolean({ required_error }),
schema: z.boolean({ error: required_error }),
...config,
});
4 changes: 2 additions & 2 deletions src/fields/checkbox-field/checkboxField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ export const checkboxField = ({
true,
required_error
? {
errorMap: (issue) => {
return issue.code === "invalid_literal"
error: (issue) => {
return issue.code === "invalid_value"
? { message: required_error }
: { message: issue.message ?? "Invalid" };
},
Expand Down
2 changes: 1 addition & 1 deletion src/fields/date-field/dateField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const dateField = ({
value: undefined,
...prepareSchema({
initial: {
schema: z.date({ required_error }),
schema: z.date({ error: required_error }),
},
user: { schema, optionalSchema },
}),
Expand Down
5 changes: 2 additions & 3 deletions src/fields/files-field/filesField.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import type { FilesField } from "./filesField";

test("required filesField has '[File, ...File[]]' submit value", () => {
expectTypeOf<FormFieldSubmitValues<{ field: FilesField }>>().toEqualTypeOf<{
field: [File, ...File[]];
field: File[];
}>();
});

test("optional filesField has 'File[]' submit value", () => {
expectTypeOf<
FormFieldSubmitValues<{ field: ReturnType<FilesField["optional"]> }>
>().toEqualTypeOf<{
// TODO: narrow
field: [File, ...File[]] | File[];
field: File[];
}>();
});
2 changes: 1 addition & 1 deletion src/fields/files-field/filesField.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe("filesField()", () => {

await act(async () => actions.current.validate());
expect(errors.current).toEqual([
"Array must contain at most 2 element(s)",
"Too big: expected array to have <=2 items",
]);
});
});
Expand Down
3 changes: 2 additions & 1 deletion src/fields/files-field/filesField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ export type FilesFieldValue = ExtractAtomValue<

const isServer = typeof window === "undefined";

// TODO: test server with zod4
// the File constructor does not exist in node, so we must prevent getting reference error
const elementSchema = isServer ? z.never() : z.instanceof(File);
const elementSchema = isServer ? z.never() : z.file();

export const filesArrayField = arrayField({ elementSchema });

Expand Down
6 changes: 3 additions & 3 deletions src/fields/list-field/listField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
useFormSubmit,
} from "form-atoms";
import { describe, expect, it, vi } from "vitest";

import { z } from "zod";
import { listField } from "./listField";
import { useFieldError } from "../../hooks";
import { numberField } from "../number-field";
Expand Down Expand Up @@ -46,7 +46,7 @@ describe("listField()", () => {
const { result } = renderHook(() => useFieldError(list));

expect(result.current.error).toBe(
"Array must contain at least 1 element(s)",
"Too small: expected array to have >=1 items",
);
});
});
Expand Down Expand Up @@ -96,7 +96,7 @@ describe("listField()", () => {

await act(async () => actions.current.validate());
expect(errors.current).toEqual([
"Array must contain at most 2 element(s)",
"Too big: expected array to have <=2 items",
]);
});
});
Expand Down
14 changes: 7 additions & 7 deletions src/fields/list-field/listField.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ListAtom, ListAtomConfig, listAtom } from "@form-atoms/list-atom";
import { FormFieldValues, FormFields } from "form-atoms";
import { Atom } from "jotai";
import { ZodAny, ZodArray, z } from "zod";
import type { FormFieldValues, FormFields } from "form-atoms";
import type { Atom } from "jotai";
import { type ZodArray, ZodObject, z } from "zod";

import { extendAtom } from "../../atoms/extendAtom";
import { FormFieldSubmitValues } from "../../components";
import type { FormFieldSubmitValues } from "../../components";
import { UserValidateConfig, prepareSchema } from "../../utils";
import {
DefaultRequiredAtom,
Expand Down Expand Up @@ -50,7 +50,7 @@ export type OptionalListField<Fields extends FormFields> = ListField<

type ListFieldConfig<Fields extends FormFields> = ListAtomConfig<Fields> &
ZodParams &
UserValidateConfig<ZodArray<ZodAny, "atleastone">, ZodArray<ZodAny, "many">>;
UserValidateConfig<ZodArray<ZodObject>, ZodArray<ZodObject>>;

export const listField = <Fields extends FormFields>({
required_error,
Expand All @@ -61,8 +61,8 @@ export const listField = <Fields extends FormFields>({
const { validate, requiredAtom, makeOptional } = schemaValidate(
prepareSchema({
initial: {
schema: z.array(z.any()).nonempty(required_error),
optionalSchema: z.array(z.any()),
schema: z.array(z.object()).nonempty({ error: required_error }),
optionalSchema: z.array(z.object()),
},
user: { schema, optionalSchema },
}),
Expand Down
4 changes: 1 addition & 3 deletions src/fields/number-field/numberField.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ describe("numberField()", () => {
const { result: errors } = renderHook(() => useFieldErrors(field));

await act(async () => actions.current.validate());
expect(errors.current).toEqual([
"Number must be less than or equal to 6",
]);
expect(errors.current).toEqual(["Too big: expected number to be <=6"]);
});
});
});
2 changes: 1 addition & 1 deletion src/fields/number-field/numberField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const numberField = ({
value: undefined,
...prepareSchema({
initial: {
schema: z.number({ required_error }),
schema: z.number({ error: required_error }),
},
user: { schema, optionalSchema },
}),
Expand Down
2 changes: 1 addition & 1 deletion src/fields/string-field/stringField.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe("stringField()", () => {
const { result: errors } = renderHook(() => useFieldErrors(field));

await act(async () => actions.current.validate());
expect(errors.current).toEqual(["Invalid email"]);
expect(errors.current).toEqual(["Invalid email address"]);
});
});
});
2 changes: 1 addition & 1 deletion src/fields/string-field/stringField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const stringField = ({
value: undefined,
...prepareSchema({
initial: {
schema: z.string({ required_error }),
schema: z.string({ error: required_error }),
},
user: { schema, optionalSchema },
}),
Expand Down
2 changes: 1 addition & 1 deletion src/fields/text-field/textField.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe("textField()", () => {

await act(async () => actions.current.validate());
expect(errors.current).toEqual([
"String must contain at most 6 character(s)",
"Too big: expected string to have <=6 characters",
]);
});
});
Expand Down
2 changes: 1 addition & 1 deletion src/fields/zod-field/Docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Returns `fieldAtom` holding value constrained by `schema` & `optionalSchema` wit
type ZodField<
Schema extends z.Schema,
OptSchema extends z.Schema = ZodUndefined,
> = FieldAtom<Schema["_output"] | OptSchema["_output"]> & {
> = FieldAtom<z.output<Schema> | z.output<OptSchema>> & {
optional: () => OptionalZodField<Schema, OptSchema>;
} & Atom<
Config & {
Expand Down
8 changes: 4 additions & 4 deletions src/fields/zod-field/zodField.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
type ZodFieldConfig<
Schema extends z.Schema,
OptSchema extends z.Schema = ZodUndefined,
> = FieldAtomConfig<Schema["_output"] | OptSchema["_output"]> &
> = FieldAtomConfig<z.output<Schema> | z.output<OptSchema>> &
ValidateConfig<Schema, OptSchema>;

export type ZodFieldValue<Field> =
Expand All @@ -23,9 +23,9 @@ export type ZodFieldValue<Field> =
export type ZodFieldSubmitValue<Field> =
Field extends ZodField<infer Schema, infer OptSchema, infer Required>
? Required extends WritableRequiredAtom
? Schema["_output"] | OptSchema["_output"]
? z.output<Schema> | z.output<OptSchema>
: Required extends DefaultRequiredAtom
? Schema["_output"]
? z.output<Schema>
: never
: never;

Expand All @@ -47,7 +47,7 @@ export type ZodField<
OptSchema extends z.Schema = ZodUndefined,
RequiredAtom = DefaultRequiredAtom,
> = ExtendFieldAtom<
Schema["_output"] | OptSchema["_output"],
z.output<Schema> | z.output<OptSchema>,
{ required: RequiredAtom }
> & {
optional: (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { type ChangeEvent, useCallback, useMemo } from "react";
import type { UseFieldOptions } from "form-atoms";
import { useAtomValue } from "jotai";
import { ArrayCardinality, ZodAny, ZodArray } from "zod";
import type { ZodAny, ZodArray } from "zod";
import { z } from "zod";

import { UseOptionsProps, useFieldProps } from "..";
import { ZodArrayField, ZodField, ZodFieldValue } from "../../fields";
import type { ZodArrayField, ZodField, ZodFieldValue } from "../../fields";
import { useIndexValue } from "./useIndexValue";

export type UseMultiSelectFieldProps<Option, Field extends ZodArrayField> = {
Expand All @@ -13,11 +14,8 @@ export type UseMultiSelectFieldProps<Option, Field extends ZodArrayField> = {
} & Pick<UseOptionsProps<Option>, "options">;

export type ZodArrayFieldValue<Field> =
Field extends ZodField<
ZodArray<infer Value, ArrayCardinality>,
ZodArray<ZodAny, ArrayCardinality>
>
? Value["_output"]
Field extends ZodField<ZodArray<infer Value>, ZodArray<ZodAny>>
? z.infer<Value>
: never;

export const useMultiSelectFieldProps = <Option, Field extends ZodArrayField>(
Expand Down
13 changes: 3 additions & 10 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -937,14 +937,14 @@ __metadata:
typescript-eslint: "npm:^8.46.2"
vite: "npm:^7.1.11"
vitest: "npm:^4.0.0"
zod: "npm:3.25.76"
zod: "npm:4.1.12"
peerDependencies:
"@form-atoms/list-atom": ^2
form-atoms: ^3
jotai: ^2
jotai-effect: ^2
react: ">=16.8"
zod: ^3
zod: ^4
languageName: unknown
linkType: soft

Expand Down Expand Up @@ -11205,14 +11205,7 @@ __metadata:
languageName: node
linkType: hard

"zod@npm:3.25.76":
version: 3.25.76
resolution: "zod@npm:3.25.76"
checksum: 10/f0c963ec40cd96858451d1690404d603d36507c1fc9682f2dae59ab38b578687d542708a7fdbf645f77926f78c9ed558f57c3d3aa226c285f798df0c4da16995
languageName: node
linkType: hard

"zod@npm:^3.22.4 || ^4.0.0":
"zod@npm:4.1.12, zod@npm:^3.22.4 || ^4.0.0":
version: 4.1.12
resolution: "zod@npm:4.1.12"
checksum: 10/c5f04e6ac306515c4db6ef73cf7705f521c7a2107c8c8912416a0658d689f361db9bee829b0bf01ef4a22492f1065c5cbcdb523ce532606ac6792fd714f3c326
Expand Down