formstand 0.13.0 → 0.15.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.
@@ -0,0 +1,144 @@
1
+ import { useStore } from 'zustand/react';
2
+ import { useShallow } from 'zustand/react/shallow';
3
+
4
+ // src/core/equality.ts
5
+ var isPlainObject = (v) => {
6
+ if (typeof v !== "object" || v === null) return false;
7
+ const proto = Object.getPrototypeOf(v);
8
+ return proto === Object.prototype || proto === null;
9
+ };
10
+ var valuesEqual = (a, b) => {
11
+ if (Object.is(a, b)) return true;
12
+ if (a instanceof Date && b instanceof Date) {
13
+ return a.getTime() === b.getTime();
14
+ }
15
+ if (Array.isArray(a) && Array.isArray(b)) {
16
+ return a.length === b.length && a.every((v, i) => valuesEqual(v, b[i]));
17
+ }
18
+ if (isPlainObject(a) && isPlainObject(b)) {
19
+ const aKeys = Object.keys(a);
20
+ const bKeys = Object.keys(b);
21
+ return aKeys.length === bKeys.length && aKeys.every((k) => Object.hasOwn(b, k) && valuesEqual(a[k], b[k]));
22
+ }
23
+ return false;
24
+ };
25
+ var dirtyCache = /* @__PURE__ */ new WeakMap();
26
+ var MAX_VERDICTS = 4;
27
+ var isFieldDirty = (value, initialValue) => {
28
+ if (Object.is(value, initialValue)) return false;
29
+ if (typeof value !== "object" || value === null) {
30
+ return !valuesEqual(value, initialValue);
31
+ }
32
+ const cached = dirtyCache.get(value) ?? [];
33
+ const hit = cached.find((v) => Object.is(v.initial, initialValue));
34
+ if (hit !== void 0) return hit.dirty;
35
+ const dirty = !valuesEqual(value, initialValue);
36
+ dirtyCache.set(
37
+ value,
38
+ [{ initial: initialValue, dirty }, ...cached].slice(0, MAX_VERDICTS)
39
+ );
40
+ return dirty;
41
+ };
42
+
43
+ // src/core/path.ts
44
+ var MAX_ARRAY_INDEX = 1e5;
45
+ var isIndexSegment = (s) => {
46
+ const n = Number(s);
47
+ return Number.isInteger(n) && n >= 0 && String(n) === s;
48
+ };
49
+ var PARSE_CACHE_MAX = 4096;
50
+ var parseCache = /* @__PURE__ */ new Map();
51
+ var parsePath = (path) => {
52
+ const cached = parseCache.get(path);
53
+ if (cached !== void 0) return cached;
54
+ const segments = Object.freeze(
55
+ path === "" ? [] : path.split(".").map((s) => isIndexSegment(s) ? Number(s) : s)
56
+ );
57
+ if (parseCache.size >= PARSE_CACHE_MAX) parseCache.clear();
58
+ parseCache.set(path, segments);
59
+ return segments;
60
+ };
61
+ var isPlainRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
62
+ var readSegment = (obj, segment) => {
63
+ if (obj == null) return void 0;
64
+ if (Array.isArray(obj)) {
65
+ return typeof segment === "number" ? obj[segment] : void 0;
66
+ }
67
+ return isPlainRecord(obj) && Object.hasOwn(obj, String(segment)) ? obj[String(segment)] : void 0;
68
+ };
69
+ var getAtPath = (obj, path) => parsePath(path).reduce(readSegment, obj);
70
+ var slotAtPath = (obj, path) => {
71
+ const walked = parsePath(path).reduce(
72
+ (acc, segment) => {
73
+ const container = acc.current;
74
+ const addressable = typeof segment === "number" ? Array.isArray(container) ? segment < container.length : isPlainRecord(container) : isPlainRecord(container);
75
+ return !acc.ok || !addressable ? { ok: false, current: void 0 } : { ok: true, current: readSegment(container, segment) };
76
+ },
77
+ { ok: true, current: obj }
78
+ );
79
+ return walked.ok ? { exists: true, value: walked.current } : { exists: false };
80
+ };
81
+ var hasOwnSlot = (obj, path) => parsePath(path).reduce(
82
+ (acc, segment) => {
83
+ const container = acc.current;
84
+ const owns = Array.isArray(container) ? typeof segment === "number" && segment < container.length : isPlainRecord(container) && Object.hasOwn(container, String(segment));
85
+ return !acc.ok || !owns ? { ok: false, current: void 0 } : { ok: true, current: readSegment(container, segment) };
86
+ },
87
+ { ok: true, current: obj }
88
+ ).ok;
89
+ var writeIntoArray = (arr, index, rest, value) => {
90
+ const length = Math.max(arr.length, index + 1);
91
+ return Array.from(
92
+ { length },
93
+ (_, i) => i === index ? writeSegments(arr[i], rest, value) : arr[i]
94
+ );
95
+ };
96
+ var refusesHugeIndex = (index) => {
97
+ if (index > MAX_ARRAY_INDEX) {
98
+ console.warn(
99
+ `[formstand] refusing to write array index ${index} (max ${MAX_ARRAY_INDEX}); value left unchanged.`
100
+ );
101
+ return true;
102
+ }
103
+ return false;
104
+ };
105
+ var writeSegments = (obj, segments, value) => {
106
+ const [head, ...rest] = segments;
107
+ if (head === void 0) return value;
108
+ if (Array.isArray(obj)) {
109
+ if (typeof head !== "number") {
110
+ console.warn(
111
+ `[formstand] cannot write string key "${head}" into an array; value left unchanged.`
112
+ );
113
+ return obj;
114
+ }
115
+ if (refusesHugeIndex(head)) return obj;
116
+ return writeIntoArray(obj, head, rest, value);
117
+ }
118
+ if (isPlainRecord(obj)) {
119
+ if (!isPlainObject(obj)) {
120
+ console.warn(
121
+ `[formstand] cannot write key "${String(head)}" through a non-plain object (Date/Map/Set/class instance); value left unchanged.`
122
+ );
123
+ return obj;
124
+ }
125
+ const key = String(head);
126
+ return { ...obj, [key]: writeSegments(obj[key], rest, value) };
127
+ }
128
+ if (typeof head === "number") {
129
+ if (refusesHugeIndex(head)) return obj;
130
+ return writeIntoArray([], head, rest, value);
131
+ }
132
+ return { [head]: writeSegments(void 0, rest, value) };
133
+ };
134
+ var setAtPath = (obj, path, value) => writeSegments(obj, parsePath(path), value);
135
+ function useFormSelector(form, selector) {
136
+ return useStore(form.store, selector);
137
+ }
138
+ function useFormSelectorShallow(form, selector) {
139
+ return useStore(form.store, useShallow(selector));
140
+ }
141
+
142
+ export { getAtPath, hasOwnSlot, isFieldDirty, isPlainObject, parsePath, setAtPath, slotAtPath, useFormSelector, useFormSelectorShallow, valuesEqual };
143
+ //# sourceMappingURL=chunk-N7WPXYCU.js.map
144
+ //# sourceMappingURL=chunk-N7WPXYCU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/equality.ts","../src/core/path.ts","../src/react/useFormSelector.ts"],"names":[],"mappings":"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","file":"chunk-N7WPXYCU.js","sourcesContent":["export const isPlainObject = (v: unknown): v is Record<string, unknown> => {\n if (typeof v !== \"object\" || v === null) return false;\n const proto = Object.getPrototypeOf(v);\n return proto === Object.prototype || proto === null;\n};\n\n// Structural equality used to decide whether a field still matches its initial\n// value. Recurses into arrays and plain objects; Dates compare by timestamp\n// (re-picking the same date must not leave the field permanently dirty);\n// other exotic objects fall back to Object.is so a real change is never\n// mistaken for \"unchanged\".\nexport const valuesEqual = (a: unknown, b: unknown): boolean => {\n if (Object.is(a, b)) return true;\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime();\n }\n if (Array.isArray(a) && Array.isArray(b)) {\n return a.length === b.length && a.every((v, i) => valuesEqual(v, b[i]));\n }\n if (isPlainObject(a) && isPlainObject(b)) {\n const aKeys = Object.keys(a);\n const bKeys = Object.keys(b);\n // hasOwn matters: equal counts don't imply equal key SETS. Without it,\n // { realname: undefined } vs { nickname: \"Ann\" } passes (1 === 1, and\n // a.realname === missing b.realname === undefined) without b's real key\n // ever being inspected — dirty tracking would read a real change as\n // clean. With hasOwn plus the length check the key sets must match, so\n // the compare is also symmetric.\n return (\n aKeys.length === bKeys.length &&\n aKeys.every((k) => Object.hasOwn(b, k) && valuesEqual(a[k], b[k]))\n );\n }\n return false;\n};\n\n// The deep compare for a container-valued field runs inside store selectors\n// and watch subscriptions, i.e. on every store change — cache verdicts per\n// value object, keyed by the initial-slice reference each was computed for.\n// A small list (not a single slot) so distinct fields/forms sharing one value\n// object (a module-level default, a shared sentinel array) coexist instead of\n// evicting each other; bounded so no entry grows without limit. Form state is\n// immutable, so identical references always mean an identical verdict, and\n// the WeakMap releases everything when the value object dies.\ntype DirtyVerdict = Readonly<{ initial: unknown; dirty: boolean }>;\nconst dirtyCache = new WeakMap<object, readonly DirtyVerdict[]>();\nconst MAX_VERDICTS = 4;\n\n// The single definition of dirtiness: the current value slice differs\n// structurally from its initial-values slice. Derived (rather than tracked by\n// writers) so reads are accurate at any depth.\nexport const isFieldDirty = (value: unknown, initialValue: unknown): boolean => {\n if (Object.is(value, initialValue)) return false;\n if (typeof value !== \"object\" || value === null) {\n return !valuesEqual(value, initialValue);\n }\n const cached = dirtyCache.get(value) ?? [];\n const hit = cached.find((v) => Object.is(v.initial, initialValue));\n if (hit !== undefined) return hit.dirty;\n const dirty = !valuesEqual(value, initialValue);\n dirtyCache.set(\n value,\n [{ initial: initialValue, dirty }, ...cached].slice(0, MAX_VERDICTS),\n );\n return dirty;\n};\n","import { isPlainObject } from \"./equality\";\n\nexport type PathSegment = string | number;\n\n// Segment keys containing \".\" are not addressable (paths are split on dots);\n// use nested objects or index-free keys instead.\n\n// Writes above this index are refused — a typo'd path like \"items.4294967296\"\n// must not allocate a multi-gigabyte array.\nconst MAX_ARRAY_INDEX = 100_000;\n\nconst isIndexSegment = (s: string): boolean => {\n const n = Number(s);\n return Number.isInteger(n) && n >= 0 && String(n) === s;\n};\n\n// Hot path: every field subscription re-resolves its path on every store\n// notification (getAtPath in selectors), so parsing is memoized — a form's\n// paths are a small fixed set. Bounded like equality's dirtyCache: a\n// pathological unbounded key set resets the cache instead of growing it\n// forever (the reset only costs re-parsing, never correctness).\nconst PARSE_CACHE_MAX = 4096;\nconst parseCache = new Map<string, readonly PathSegment[]>();\n\nexport const parsePath = (path: string): readonly PathSegment[] => {\n const cached = parseCache.get(path);\n if (cached !== undefined) return cached;\n // Frozen because the array is now SHARED across every caller (and cached):\n // parsePath is a public export, and pre-cache each call returned a private\n // array that was safe to mutate. Freezing enforces the `readonly` type at\n // runtime so an external `.sort()`/`.reverse()`/`.push()` throws instead of\n // silently corrupting the cache for every later lookup of this path.\n const segments = Object.freeze(\n path === \"\"\n ? []\n : path.split(\".\").map((s) => (isIndexSegment(s) ? Number(s) : s)),\n );\n if (parseCache.size >= PARSE_CACHE_MAX) parseCache.clear();\n parseCache.set(path, segments);\n return segments;\n};\n\nconst isPlainRecord = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === \"object\" && !Array.isArray(value);\n\n// The existing container decides how a segment is interpreted: arrays take\n// numeric segments as indices, records take any segment as a string key (so a\n// z.record keyed \"0\" reads/writes the record key, not an array index). Only\n// when the container is absent does the segment type pick what gets created.\nconst readSegment = (obj: unknown, segment: PathSegment): unknown => {\n if (obj == null) return undefined;\n if (Array.isArray(obj)) {\n return typeof segment === \"number\" ? obj[segment] : undefined;\n }\n // Own properties only: a runtime-built path like \"lookup.constructor\" on a\n // z.record must read as absent, not leak Object.prototype members as field\n // values.\n return isPlainRecord(obj) && Object.hasOwn(obj, String(segment))\n ? obj[String(segment)]\n : undefined;\n};\n\nexport const getAtPath = (obj: unknown, path: string): unknown =>\n parsePath(path).reduce<unknown>(readSegment, obj);\n\nexport type SlotAtPath =\n | Readonly<{ exists: true; value: unknown }>\n | Readonly<{ exists: false }>;\n\n// Resolve `path` while checking that every segment is read from a real\n// container: an in-range array index, or a key on a plain record (containers\n// decide, mirroring readSegment). A segment into an absent or non-container\n// value addresses no slot at all — a full-form parse would key that error at\n// the missing ANCESTOR, not here, so field-scoped validation must skip it\n// rather than parse the leaf's subschema against undefined (which would\n// fabricate a spurious \"required\" error the full pass never produces). Note\n// this checks the CONTAINER, not the value: a leaf that is undefined at the\n// FINAL step through a real record is still an addressable (legitimately\n// empty) slot. Returns the resolved value so callers don't walk twice.\nexport const slotAtPath = (obj: unknown, path: string): SlotAtPath => {\n const walked = parsePath(path).reduce<\n Readonly<{ ok: boolean; current: unknown }>\n >(\n (acc, segment) => {\n const container = acc.current;\n const addressable =\n typeof segment === \"number\"\n ? Array.isArray(container)\n ? segment < container.length\n : isPlainRecord(container)\n : isPlainRecord(container);\n return !acc.ok || !addressable\n ? { ok: false, current: undefined }\n : { ok: true, current: readSegment(container, segment) };\n },\n { ok: true, current: obj },\n );\n return walked.ok ? { exists: true, value: walked.current } : { exists: false };\n};\n\n// Whether the path addresses an OWN slot: every step reads a real\n// container's own key / in-range array index, the final segment included.\n// This is stricter than slotAtPath, which treats a MISSING final key on a\n// real record as an addressable (legitimately empty) slot — here it reads\n// false. The consumer is setValue's identity guard: writing `undefined`\n// over an ABSENT key is NOT a no-op (it creates the key, a real state\n// change — key-count dirtiness, persisted shape), while writing it over a\n// key that exists holding undefined is.\nexport const hasOwnSlot = (obj: unknown, path: string): boolean =>\n parsePath(path).reduce<Readonly<{ ok: boolean; current: unknown }>>(\n (acc, segment) => {\n const container = acc.current;\n const owns = Array.isArray(container)\n ? typeof segment === \"number\" && segment < container.length\n : isPlainRecord(container) && Object.hasOwn(container, String(segment));\n return !acc.ok || !owns\n ? { ok: false, current: undefined }\n : { ok: true, current: readSegment(container, segment) };\n },\n { ok: true, current: obj },\n ).ok;\n\nconst writeIntoArray = (\n arr: readonly unknown[],\n index: number,\n rest: readonly PathSegment[],\n value: unknown,\n): readonly unknown[] => {\n const length = Math.max(arr.length, index + 1);\n return Array.from({ length }, (_, i) =>\n i === index ? writeSegments(arr[i], rest, value) : arr[i],\n );\n};\n\nconst refusesHugeIndex = (index: number): boolean => {\n if (index > MAX_ARRAY_INDEX) {\n console.warn(\n `[formstand] refusing to write array index ${index} (max ${MAX_ARRAY_INDEX}); value left unchanged.`,\n );\n return true;\n }\n return false;\n};\n\nconst writeSegments = (\n obj: unknown,\n segments: readonly PathSegment[],\n value: unknown,\n): unknown => {\n const [head, ...rest] = segments;\n if (head === undefined) return value;\n\n if (Array.isArray(obj)) {\n if (typeof head !== \"number\") {\n console.warn(\n `[formstand] cannot write string key \"${head}\" into an array; value left unchanged.`,\n );\n return obj;\n }\n if (refusesHugeIndex(head)) return obj;\n return writeIntoArray(obj, head, rest, value);\n }\n\n // Records take any segment as a string key (mirroring readSegment), so a\n // z.record keyed by a large numeric id is writable — the array-index cap\n // only applies where an array actually exists or would be created.\n if (isPlainRecord(obj)) {\n // Spreading is only safe for PLAIN objects: spreading a Date/Map/Set or\n // class instance keeps own enumerable props but drops the prototype (and\n // for Map/Set, every entry) — the original would be silently destroyed.\n // Refuse, like the other unwritable shapes above.\n if (!isPlainObject(obj)) {\n console.warn(\n `[formstand] cannot write key \"${String(head)}\" through a non-plain object (Date/Map/Set/class instance); value left unchanged.`,\n );\n return obj;\n }\n const key = String(head);\n return { ...obj, [key]: writeSegments(obj[key], rest, value) };\n }\n\n // Absent (or non-container) value: create a container from the segment type.\n if (typeof head === \"number\") {\n if (refusesHugeIndex(head)) return obj;\n return writeIntoArray([], head, rest, value);\n }\n return { [head]: writeSegments(undefined, rest, value) };\n};\n\nexport const setAtPath = <T>(obj: T, path: string, value: unknown): T =>\n writeSegments(obj, parsePath(path), value) as T;\n","import type { z } from \"zod\";\nimport type { StoreApi } from \"zustand/vanilla\";\nimport { useStore } from \"zustand/react\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport type { Form } from \"../core/createForm\";\nimport type { DefaultPathDepth, PathDepth } from \"../core/fieldPath\";\nimport type { FormState } from \"../core/types\";\n\ntype ReadonlyStore<T> = Pick<\n StoreApi<T>,\n \"getState\" | \"getInitialState\" | \"subscribe\"\n>;\n\nexport type FormStateApi = Readonly<{\n store: ReadonlyStore<FormState<unknown>>;\n}>;\n\nexport function useFormSelector<\n TSchema extends z.ZodType,\n U,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n selector: (state: FormState<z.input<TSchema>>) => U,\n): U;\n// The `schema?: undefined` brand forces Form<TSchema> (which has a real\n// `schema` property) to bind only the typed overload above. Without\n// it, Form<TSchema> would structurally satisfy FormStateApi too and a\n// bad selector could silently widen to FormState<unknown>.\nexport function useFormSelector<U>(\n form: FormStateApi & { readonly schema?: undefined },\n selector: (state: FormState<unknown>) => U,\n): U;\nexport function useFormSelector<U>(\n form: FormStateApi,\n selector: (state: FormState<unknown>) => U,\n): U {\n return useStore(form.store, selector);\n}\n\nexport function useFormSelectorShallow<\n TSchema extends z.ZodType,\n U,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n selector: (state: FormState<z.input<TSchema>>) => U,\n): U;\nexport function useFormSelectorShallow<U>(\n form: FormStateApi & { readonly schema?: undefined },\n selector: (state: FormState<unknown>) => U,\n): U;\nexport function useFormSelectorShallow<U>(\n form: FormStateApi,\n selector: (state: FormState<unknown>) => U,\n): U {\n return useStore(form.store, useShallow(selector));\n}\n"]}
@@ -0,0 +1,190 @@
1
+ import { z } from 'zod';
2
+ import { StoreApi } from 'zustand/vanilla';
3
+
4
+ type LeafType = string | number | boolean | bigint | symbol | null | undefined | Date | RegExp;
5
+ type IsRecord<T> = T extends LeafType ? false : T extends readonly unknown[] ? false : T extends object ? true : false;
6
+ type IsArray<T> = T extends readonly unknown[] ? true : false;
7
+ type Prev = [
8
+ never,
9
+ 0,
10
+ 1,
11
+ 2,
12
+ 3,
13
+ 4,
14
+ 5,
15
+ 6,
16
+ 7,
17
+ 8,
18
+ 9,
19
+ 10,
20
+ 11,
21
+ 12,
22
+ 13,
23
+ 14,
24
+ 15,
25
+ 16,
26
+ 17,
27
+ 18,
28
+ 19,
29
+ 20,
30
+ 21,
31
+ 22,
32
+ 23,
33
+ 24
34
+ ];
35
+ type DefaultPathDepth = 9;
36
+ type PathDepth = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25;
37
+ type IsUnion<T, U = T> = [T] extends [never] ? false : T extends unknown ? [U] extends [T] ? false : true : never;
38
+ type NormalizeDepth<D extends number> = number extends D ? DefaultPathDepth : [D] extends [PathDepth] ? IsUnion<D> extends true ? DefaultPathDepth : D : DefaultPathDepth;
39
+ type FieldPath<T, D extends number = DefaultPathDepth> = FieldPathRec<T, NormalizeDepth<D>>;
40
+ type FieldPathRec<T, D extends number> = [D] extends [0] ? never : IsArray<T> extends true ? T extends readonly (infer U)[] ? `${number}` | (IsRecord<NonNullable<U>> extends true ? `${number}.${FieldPathRec<NonNullable<U>, Prev[D]>}` : IsArray<NonNullable<U>> extends true ? `${number}.${FieldPathRec<NonNullable<U>, Prev[D]>}` : never) : never : IsRecord<T> extends true ? {
41
+ [K in keyof T & string]: K | (IsRecord<NonNullable<T[K]>> extends true ? `${K}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : IsArray<NonNullable<T[K]>> extends true ? `${K}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : never);
42
+ }[keyof T & string] : never;
43
+ type StepValue<T, K extends string> = T extends readonly (infer U)[] ? K extends `${number}` ? U : never : T extends object ? K extends keyof T ? T[K] : never : never;
44
+ type FieldValue<T, P extends string> = [T] extends [NonNullable<T>] ? P extends `${infer Head}.${infer Tail}` ? FieldValue<StepValue<T, Head>, Tail> : StepValue<T, P> : FieldValue<NonNullable<T>, P> | undefined;
45
+
46
+ type ValidationMode = "onChange" | "onBlur" | "onSubmit" | "onTouched" | "all";
47
+ type ValidationTrigger = "change" | "blur";
48
+ declare const shouldValidateOn: (trigger: ValidationTrigger, mode: ValidationMode, reValidateMode: ValidationMode, submitAttempted: boolean, touched?: boolean) => boolean;
49
+
50
+ type ErrorMap = Readonly<Record<string, readonly string[]>>;
51
+ type BoolMap = Readonly<Record<string, boolean>>;
52
+ type FormState<TValues> = Readonly<{
53
+ values: TValues;
54
+ initialValues: TValues;
55
+ errors: ErrorMap;
56
+ schemaErrors: ErrorMap;
57
+ serverErrors: ErrorMap;
58
+ touched: BoolMap;
59
+ isSubmitting: boolean;
60
+ submitCount: number;
61
+ isValidating: BoolMap;
62
+ isValidatingForm: boolean;
63
+ mode: ValidationMode;
64
+ reValidateMode: ValidationMode;
65
+ }>;
66
+
67
+ type SettledValidationResult<TOutput> = Readonly<{
68
+ kind: "valid";
69
+ data: TOutput;
70
+ }> | Readonly<{
71
+ kind: "invalid";
72
+ errors: ErrorMap;
73
+ }>;
74
+ type ValidationResult<TOutput> = SettledValidationResult<TOutput> | Readonly<{
75
+ kind: "pending";
76
+ promise: Promise<SettledValidationResult<TOutput>>;
77
+ }>;
78
+ type SettledFieldValidationResult = Readonly<{
79
+ kind: "valid";
80
+ }> | Readonly<{
81
+ kind: "invalid";
82
+ errors: readonly string[];
83
+ }>;
84
+ type FieldValidationResult = SettledFieldValidationResult | Readonly<{
85
+ kind: "pending";
86
+ promise: Promise<SettledFieldValidationResult>;
87
+ }>;
88
+ type SettledFieldsValidationResult = Readonly<{
89
+ kind: "valid";
90
+ }> | Readonly<{
91
+ kind: "invalid";
92
+ errors: ErrorMap;
93
+ }>;
94
+ type FieldsValidationResult = SettledFieldsValidationResult | Readonly<{
95
+ kind: "pending";
96
+ promise: Promise<SettledFieldsValidationResult>;
97
+ }>;
98
+ declare const flattenIssues: (issues: readonly z.core.$ZodIssue[]) => ErrorMap;
99
+ declare const validateSync: <TSchema extends z.ZodType>(schema: TSchema, values: z.input<TSchema>) => SettledValidationResult<z.output<TSchema>>;
100
+ declare const validateAsync: <TSchema extends z.ZodType>(schema: TSchema, values: z.input<TSchema>) => Promise<SettledValidationResult<z.output<TSchema>>>;
101
+ declare const isAsyncRequiredError: (e: unknown) => boolean;
102
+ declare const emptyValueForSchema: (schema: z.ZodType) => null | undefined;
103
+
104
+ type ReadonlyStoreApi<T> = Pick<StoreApi<T>, "getState" | "getInitialState" | "subscribe">;
105
+ type CreateFormOptions<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = Readonly<{
106
+ initialValues: z.input<TSchema>;
107
+ pathDepth?: D & (IsUnion<D> extends true ? never : unknown);
108
+ mode?: ValidationMode;
109
+ reValidateMode?: ValidationMode;
110
+ validateOnMount?: boolean;
111
+ devtools?: boolean | string;
112
+ }>;
113
+ type SubmitHandler<TSchema extends z.ZodType> = (data: z.output<TSchema>) => void | Promise<void>;
114
+ type InvalidSubmitHandler = (errors: ErrorMap) => void;
115
+ type SubmitOptions = Readonly<{
116
+ force?: boolean;
117
+ }>;
118
+ type SubmitResult<TOutput> = Readonly<{
119
+ kind: "valid";
120
+ data: TOutput;
121
+ }> | Readonly<{
122
+ kind: "invalid";
123
+ errors: ErrorMap;
124
+ }> | Readonly<{
125
+ kind: "skipped";
126
+ }> | Readonly<{
127
+ kind: "error";
128
+ error: unknown;
129
+ }>;
130
+ type ArrayItemOf<T> = T extends readonly (infer U)[] ? U : never;
131
+ type ErrorPath<TValues, D extends PathDepth = DefaultPathDepth> = FieldPath<TValues, D> | "";
132
+ type ResetOptions = Readonly<{
133
+ keepErrors?: boolean;
134
+ keepTouched?: boolean;
135
+ keepSubmitCount?: boolean;
136
+ }>;
137
+ type FieldSnapshot<TValue> = Readonly<{
138
+ value: TValue;
139
+ error: readonly string[] | undefined;
140
+ touched: boolean;
141
+ dirty: boolean;
142
+ isValidating: boolean;
143
+ }>;
144
+ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = Readonly<{
145
+ "~pathDepth"?: D;
146
+ schema: TSchema;
147
+ store: ReadonlyStoreApi<FormState<z.input<TSchema>>>;
148
+ getState: () => FormState<z.input<TSchema>>;
149
+ subscribe: (listener: (state: FormState<z.input<TSchema>>, prev: FormState<z.input<TSchema>>) => void) => () => void;
150
+ getField: <P extends FieldPath<z.input<TSchema>, D>>(path: P) => FieldValue<z.input<TSchema>, P>;
151
+ watchField: <P extends FieldPath<z.input<TSchema>, D>>(path: P, listener: (snapshot: FieldSnapshot<FieldValue<z.input<TSchema>, P>>) => void) => () => void;
152
+ watchValue: <P extends FieldPath<z.input<TSchema>, D>>(path: P, listener: (value: FieldValue<z.input<TSchema>, P>, previous: FieldValue<z.input<TSchema>, P>) => void) => () => void;
153
+ watchValues: (listener: (values: z.input<TSchema>, previous: z.input<TSchema>) => void) => () => void;
154
+ setValue: <P extends FieldPath<z.input<TSchema>, D>>(path: P, value: FieldValue<z.input<TSchema>, P>) => void;
155
+ setValues: (next: z.input<TSchema>) => void;
156
+ setTouched: (path: FieldPath<z.input<TSchema>, D>, touched?: boolean) => void;
157
+ setSubmitting: (value: boolean) => void;
158
+ setMode: (mode: ValidationMode) => void;
159
+ setReValidateMode: (mode: ValidationMode) => void;
160
+ setError: (path: ErrorPath<z.input<TSchema>, D>, errors: string | readonly string[]) => void;
161
+ setErrors: (errors: ErrorMap) => void;
162
+ clearErrors: (path?: ErrorPath<z.input<TSchema>, D>) => void;
163
+ updateState: (updater: (state: FormState<z.input<TSchema>>) => Partial<Omit<FormState<z.input<TSchema>>, "errors">>) => void;
164
+ reset: (nextInitial?: Partial<z.input<TSchema>>, options?: ResetOptions) => void;
165
+ resetField: (path: FieldPath<z.input<TSchema>, D>) => void;
166
+ adoptValues: (values: z.input<TSchema>) => void;
167
+ getFieldState: <P extends FieldPath<z.input<TSchema>, D>>(path: P) => FieldSnapshot<FieldValue<z.input<TSchema>, P>>;
168
+ validate: () => ValidationResult<z.output<TSchema>>;
169
+ validateField: (path: ErrorPath<z.input<TSchema>, D>) => FieldValidationResult;
170
+ validateFields: (paths: readonly FieldPath<z.input<TSchema>, D>[]) => FieldsValidationResult;
171
+ validateAsync: () => Promise<ValidationResult<z.output<TSchema>>>;
172
+ validateFieldAsync: (path: ErrorPath<z.input<TSchema>, D>) => Promise<FieldValidationResult>;
173
+ validateFieldsAsync: (paths: readonly FieldPath<z.input<TSchema>, D>[]) => Promise<SettledFieldsValidationResult>;
174
+ submit: (onValid: SubmitHandler<TSchema>, onInvalid?: InvalidSubmitHandler, options?: SubmitOptions) => Promise<SubmitResult<z.output<TSchema>>>;
175
+ arrayPush: <P extends FieldPath<z.input<TSchema>, D>>(path: P, item: ArrayItemOf<NonNullable<FieldValue<z.input<TSchema>, P>>>) => void;
176
+ arrayRemove: (path: FieldPath<z.input<TSchema>, D>, index: number) => void;
177
+ arrayInsert: <P extends FieldPath<z.input<TSchema>, D>>(path: P, index: number, item: ArrayItemOf<NonNullable<FieldValue<z.input<TSchema>, P>>>) => void;
178
+ arrayMove: (path: FieldPath<z.input<TSchema>, D>, from: number, to: number) => void;
179
+ arraySwap: (path: FieldPath<z.input<TSchema>, D>, a: number, b: number) => void;
180
+ handleSubmit: (onValid: SubmitHandler<TSchema>, onInvalid?: InvalidSubmitHandler, options?: SubmitOptions) => (event?: {
181
+ preventDefault: () => void;
182
+ }) => Promise<SubmitResult<z.output<TSchema>>>;
183
+ diff: () => Readonly<Record<string, unknown>>;
184
+ dirtyFields: () => readonly string[];
185
+ snapshot: () => FormState<z.input<TSchema>>;
186
+ restore: (snapshot: FormState<z.input<TSchema>>) => void;
187
+ }>;
188
+ declare const createForm: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(schema: TSchema, options: CreateFormOptions<TSchema, D>) => Form<TSchema, D>;
189
+
190
+ export { type BoolMap as B, type CreateFormOptions as C, type DefaultPathDepth as D, type ErrorMap as E, type Form as F, type InvalidSubmitHandler as I, type PathDepth as P, type ReadonlyStoreApi as R, type SettledFieldValidationResult as S, type ValidationMode as V, type FieldValidationResult as a, type FormState as b, type FieldPath as c, type FieldValue as d, type FieldSnapshot as e, type FieldsValidationResult as f, type ResetOptions as g, type SettledFieldsValidationResult as h, type SettledValidationResult as i, type SubmitHandler as j, type SubmitOptions as k, type SubmitResult as l, type ValidationResult as m, type ValidationTrigger as n, createForm as o, emptyValueForSchema as p, flattenIssues as q, isAsyncRequiredError as r, shouldValidateOn as s, validateSync as t, validateAsync as v };
@@ -0,0 +1,190 @@
1
+ import { z } from 'zod';
2
+ import { StoreApi } from 'zustand/vanilla';
3
+
4
+ type LeafType = string | number | boolean | bigint | symbol | null | undefined | Date | RegExp;
5
+ type IsRecord<T> = T extends LeafType ? false : T extends readonly unknown[] ? false : T extends object ? true : false;
6
+ type IsArray<T> = T extends readonly unknown[] ? true : false;
7
+ type Prev = [
8
+ never,
9
+ 0,
10
+ 1,
11
+ 2,
12
+ 3,
13
+ 4,
14
+ 5,
15
+ 6,
16
+ 7,
17
+ 8,
18
+ 9,
19
+ 10,
20
+ 11,
21
+ 12,
22
+ 13,
23
+ 14,
24
+ 15,
25
+ 16,
26
+ 17,
27
+ 18,
28
+ 19,
29
+ 20,
30
+ 21,
31
+ 22,
32
+ 23,
33
+ 24
34
+ ];
35
+ type DefaultPathDepth = 9;
36
+ type PathDepth = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25;
37
+ type IsUnion<T, U = T> = [T] extends [never] ? false : T extends unknown ? [U] extends [T] ? false : true : never;
38
+ type NormalizeDepth<D extends number> = number extends D ? DefaultPathDepth : [D] extends [PathDepth] ? IsUnion<D> extends true ? DefaultPathDepth : D : DefaultPathDepth;
39
+ type FieldPath<T, D extends number = DefaultPathDepth> = FieldPathRec<T, NormalizeDepth<D>>;
40
+ type FieldPathRec<T, D extends number> = [D] extends [0] ? never : IsArray<T> extends true ? T extends readonly (infer U)[] ? `${number}` | (IsRecord<NonNullable<U>> extends true ? `${number}.${FieldPathRec<NonNullable<U>, Prev[D]>}` : IsArray<NonNullable<U>> extends true ? `${number}.${FieldPathRec<NonNullable<U>, Prev[D]>}` : never) : never : IsRecord<T> extends true ? {
41
+ [K in keyof T & string]: K | (IsRecord<NonNullable<T[K]>> extends true ? `${K}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : IsArray<NonNullable<T[K]>> extends true ? `${K}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : never);
42
+ }[keyof T & string] : never;
43
+ type StepValue<T, K extends string> = T extends readonly (infer U)[] ? K extends `${number}` ? U : never : T extends object ? K extends keyof T ? T[K] : never : never;
44
+ type FieldValue<T, P extends string> = [T] extends [NonNullable<T>] ? P extends `${infer Head}.${infer Tail}` ? FieldValue<StepValue<T, Head>, Tail> : StepValue<T, P> : FieldValue<NonNullable<T>, P> | undefined;
45
+
46
+ type ValidationMode = "onChange" | "onBlur" | "onSubmit" | "onTouched" | "all";
47
+ type ValidationTrigger = "change" | "blur";
48
+ declare const shouldValidateOn: (trigger: ValidationTrigger, mode: ValidationMode, reValidateMode: ValidationMode, submitAttempted: boolean, touched?: boolean) => boolean;
49
+
50
+ type ErrorMap = Readonly<Record<string, readonly string[]>>;
51
+ type BoolMap = Readonly<Record<string, boolean>>;
52
+ type FormState<TValues> = Readonly<{
53
+ values: TValues;
54
+ initialValues: TValues;
55
+ errors: ErrorMap;
56
+ schemaErrors: ErrorMap;
57
+ serverErrors: ErrorMap;
58
+ touched: BoolMap;
59
+ isSubmitting: boolean;
60
+ submitCount: number;
61
+ isValidating: BoolMap;
62
+ isValidatingForm: boolean;
63
+ mode: ValidationMode;
64
+ reValidateMode: ValidationMode;
65
+ }>;
66
+
67
+ type SettledValidationResult<TOutput> = Readonly<{
68
+ kind: "valid";
69
+ data: TOutput;
70
+ }> | Readonly<{
71
+ kind: "invalid";
72
+ errors: ErrorMap;
73
+ }>;
74
+ type ValidationResult<TOutput> = SettledValidationResult<TOutput> | Readonly<{
75
+ kind: "pending";
76
+ promise: Promise<SettledValidationResult<TOutput>>;
77
+ }>;
78
+ type SettledFieldValidationResult = Readonly<{
79
+ kind: "valid";
80
+ }> | Readonly<{
81
+ kind: "invalid";
82
+ errors: readonly string[];
83
+ }>;
84
+ type FieldValidationResult = SettledFieldValidationResult | Readonly<{
85
+ kind: "pending";
86
+ promise: Promise<SettledFieldValidationResult>;
87
+ }>;
88
+ type SettledFieldsValidationResult = Readonly<{
89
+ kind: "valid";
90
+ }> | Readonly<{
91
+ kind: "invalid";
92
+ errors: ErrorMap;
93
+ }>;
94
+ type FieldsValidationResult = SettledFieldsValidationResult | Readonly<{
95
+ kind: "pending";
96
+ promise: Promise<SettledFieldsValidationResult>;
97
+ }>;
98
+ declare const flattenIssues: (issues: readonly z.core.$ZodIssue[]) => ErrorMap;
99
+ declare const validateSync: <TSchema extends z.ZodType>(schema: TSchema, values: z.input<TSchema>) => SettledValidationResult<z.output<TSchema>>;
100
+ declare const validateAsync: <TSchema extends z.ZodType>(schema: TSchema, values: z.input<TSchema>) => Promise<SettledValidationResult<z.output<TSchema>>>;
101
+ declare const isAsyncRequiredError: (e: unknown) => boolean;
102
+ declare const emptyValueForSchema: (schema: z.ZodType) => null | undefined;
103
+
104
+ type ReadonlyStoreApi<T> = Pick<StoreApi<T>, "getState" | "getInitialState" | "subscribe">;
105
+ type CreateFormOptions<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = Readonly<{
106
+ initialValues: z.input<TSchema>;
107
+ pathDepth?: D & (IsUnion<D> extends true ? never : unknown);
108
+ mode?: ValidationMode;
109
+ reValidateMode?: ValidationMode;
110
+ validateOnMount?: boolean;
111
+ devtools?: boolean | string;
112
+ }>;
113
+ type SubmitHandler<TSchema extends z.ZodType> = (data: z.output<TSchema>) => void | Promise<void>;
114
+ type InvalidSubmitHandler = (errors: ErrorMap) => void;
115
+ type SubmitOptions = Readonly<{
116
+ force?: boolean;
117
+ }>;
118
+ type SubmitResult<TOutput> = Readonly<{
119
+ kind: "valid";
120
+ data: TOutput;
121
+ }> | Readonly<{
122
+ kind: "invalid";
123
+ errors: ErrorMap;
124
+ }> | Readonly<{
125
+ kind: "skipped";
126
+ }> | Readonly<{
127
+ kind: "error";
128
+ error: unknown;
129
+ }>;
130
+ type ArrayItemOf<T> = T extends readonly (infer U)[] ? U : never;
131
+ type ErrorPath<TValues, D extends PathDepth = DefaultPathDepth> = FieldPath<TValues, D> | "";
132
+ type ResetOptions = Readonly<{
133
+ keepErrors?: boolean;
134
+ keepTouched?: boolean;
135
+ keepSubmitCount?: boolean;
136
+ }>;
137
+ type FieldSnapshot<TValue> = Readonly<{
138
+ value: TValue;
139
+ error: readonly string[] | undefined;
140
+ touched: boolean;
141
+ dirty: boolean;
142
+ isValidating: boolean;
143
+ }>;
144
+ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = Readonly<{
145
+ "~pathDepth"?: D;
146
+ schema: TSchema;
147
+ store: ReadonlyStoreApi<FormState<z.input<TSchema>>>;
148
+ getState: () => FormState<z.input<TSchema>>;
149
+ subscribe: (listener: (state: FormState<z.input<TSchema>>, prev: FormState<z.input<TSchema>>) => void) => () => void;
150
+ getField: <P extends FieldPath<z.input<TSchema>, D>>(path: P) => FieldValue<z.input<TSchema>, P>;
151
+ watchField: <P extends FieldPath<z.input<TSchema>, D>>(path: P, listener: (snapshot: FieldSnapshot<FieldValue<z.input<TSchema>, P>>) => void) => () => void;
152
+ watchValue: <P extends FieldPath<z.input<TSchema>, D>>(path: P, listener: (value: FieldValue<z.input<TSchema>, P>, previous: FieldValue<z.input<TSchema>, P>) => void) => () => void;
153
+ watchValues: (listener: (values: z.input<TSchema>, previous: z.input<TSchema>) => void) => () => void;
154
+ setValue: <P extends FieldPath<z.input<TSchema>, D>>(path: P, value: FieldValue<z.input<TSchema>, P>) => void;
155
+ setValues: (next: z.input<TSchema>) => void;
156
+ setTouched: (path: FieldPath<z.input<TSchema>, D>, touched?: boolean) => void;
157
+ setSubmitting: (value: boolean) => void;
158
+ setMode: (mode: ValidationMode) => void;
159
+ setReValidateMode: (mode: ValidationMode) => void;
160
+ setError: (path: ErrorPath<z.input<TSchema>, D>, errors: string | readonly string[]) => void;
161
+ setErrors: (errors: ErrorMap) => void;
162
+ clearErrors: (path?: ErrorPath<z.input<TSchema>, D>) => void;
163
+ updateState: (updater: (state: FormState<z.input<TSchema>>) => Partial<Omit<FormState<z.input<TSchema>>, "errors">>) => void;
164
+ reset: (nextInitial?: Partial<z.input<TSchema>>, options?: ResetOptions) => void;
165
+ resetField: (path: FieldPath<z.input<TSchema>, D>) => void;
166
+ adoptValues: (values: z.input<TSchema>) => void;
167
+ getFieldState: <P extends FieldPath<z.input<TSchema>, D>>(path: P) => FieldSnapshot<FieldValue<z.input<TSchema>, P>>;
168
+ validate: () => ValidationResult<z.output<TSchema>>;
169
+ validateField: (path: ErrorPath<z.input<TSchema>, D>) => FieldValidationResult;
170
+ validateFields: (paths: readonly FieldPath<z.input<TSchema>, D>[]) => FieldsValidationResult;
171
+ validateAsync: () => Promise<ValidationResult<z.output<TSchema>>>;
172
+ validateFieldAsync: (path: ErrorPath<z.input<TSchema>, D>) => Promise<FieldValidationResult>;
173
+ validateFieldsAsync: (paths: readonly FieldPath<z.input<TSchema>, D>[]) => Promise<SettledFieldsValidationResult>;
174
+ submit: (onValid: SubmitHandler<TSchema>, onInvalid?: InvalidSubmitHandler, options?: SubmitOptions) => Promise<SubmitResult<z.output<TSchema>>>;
175
+ arrayPush: <P extends FieldPath<z.input<TSchema>, D>>(path: P, item: ArrayItemOf<NonNullable<FieldValue<z.input<TSchema>, P>>>) => void;
176
+ arrayRemove: (path: FieldPath<z.input<TSchema>, D>, index: number) => void;
177
+ arrayInsert: <P extends FieldPath<z.input<TSchema>, D>>(path: P, index: number, item: ArrayItemOf<NonNullable<FieldValue<z.input<TSchema>, P>>>) => void;
178
+ arrayMove: (path: FieldPath<z.input<TSchema>, D>, from: number, to: number) => void;
179
+ arraySwap: (path: FieldPath<z.input<TSchema>, D>, a: number, b: number) => void;
180
+ handleSubmit: (onValid: SubmitHandler<TSchema>, onInvalid?: InvalidSubmitHandler, options?: SubmitOptions) => (event?: {
181
+ preventDefault: () => void;
182
+ }) => Promise<SubmitResult<z.output<TSchema>>>;
183
+ diff: () => Readonly<Record<string, unknown>>;
184
+ dirtyFields: () => readonly string[];
185
+ snapshot: () => FormState<z.input<TSchema>>;
186
+ restore: (snapshot: FormState<z.input<TSchema>>) => void;
187
+ }>;
188
+ declare const createForm: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(schema: TSchema, options: CreateFormOptions<TSchema, D>) => Form<TSchema, D>;
189
+
190
+ export { type BoolMap as B, type CreateFormOptions as C, type DefaultPathDepth as D, type ErrorMap as E, type Form as F, type InvalidSubmitHandler as I, type PathDepth as P, type ReadonlyStoreApi as R, type SettledFieldValidationResult as S, type ValidationMode as V, type FieldValidationResult as a, type FormState as b, type FieldPath as c, type FieldValue as d, type FieldSnapshot as e, type FieldsValidationResult as f, type ResetOptions as g, type SettledFieldsValidationResult as h, type SettledValidationResult as i, type SubmitHandler as j, type SubmitOptions as k, type SubmitResult as l, type ValidationResult as m, type ValidationTrigger as n, createForm as o, emptyValueForSchema as p, flattenIssues as q, isAsyncRequiredError as r, shouldValidateOn as s, validateSync as t, validateAsync as v };