formstand 0.22.0 → 0.23.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/CHANGELOG.md CHANGED
@@ -1,5 +1,232 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.23.0 — 2026-10-22
4
+
5
+ ### Added
6
+
7
+ - `useFieldArray` parity with `useField`: the return carries the array
8
+ path's own `touched`, `dirty` and `isValidating` flags, `setTouched`,
9
+ `onBlur` (marks the array touched and validates it under the blur
10
+ gate) and `validate` (array-scoped, the same `{ kind }` result), plus
11
+ the whole-array writes `replace(items)`, `update(index, item)`,
12
+ `prepend(item)` and `clear()`, revalidating under the change gate like
13
+ the index ops. `replace` and `clear` go through `form.setValue`, so ids
14
+ follow surviving row references (and a fresh row at the same index
15
+ keeps that index's id); `update` writes one row in place and the row
16
+ keeps its id. `FieldArrayFormApi` gains optional `setTouched` and
17
+ `setValue` members for hand-rolled forms (absent members make the
18
+ corresponding functions no-ops, like `setError`).
19
+ - `setAtPath` takes an optional container resolver, and `containerKindAt(
20
+ schema)` builds one from a zod schema.
21
+
22
+ ### Fixed
23
+
24
+ - A numeric-string key written into an absent optional `z.record` created
25
+ an array (`setValue("lookup.0", "x")` on `lookup: undefined` yielded
26
+ `lookup: ["x"]`, which the record schema then rejected). `createForm`
27
+ now hands every value write the schema's answer for the slot being
28
+ created, so a record stays a record and an array an array; only where
29
+ the schema has no answer (a union, or the bare utility) does the
30
+ segment type still decide. The field-schema introspection walk also
31
+ descends into records, so a record child's `emptyValue` and `clearable`
32
+ come from its value schema instead of the heuristic.
33
+ - A number-keyed record (`z.record(z.number(), z.string())`) offered no
34
+ typed paths under it, so nothing inside could be bound. Its keys are
35
+ now offered as `` `${number}` `` paths and `FieldValue` resolves them to
36
+ the value type.
37
+
38
+ ### Docs
39
+
40
+ - Roadmap: rewritten for the October state. The shipped "Now" items
41
+ (StackBlitz links, brand collateral, generated tests) and the October
42
+ review rounds move to the shipped history; a "Toward 1.0" section
43
+ states the release criteria (a cycle with no `Changed` entries, a
44
+ written public API list, error message ownership, a deprecation
45
+ policy); Now holds the devtools playground tab, async coordination, a
46
+ CI bundle size budget, the Standard Schema decision and the VitePress 2
47
+ wait with the vite 5 alerts recorded; Later gains a starter template, a
48
+ Next.js server-actions example, a React Native compatibility statement,
49
+ the CLI as an agent tool surface and a UI kit adapter guide. The
50
+ deprecation policy is written into "How releases happen".
51
+ - Playground: the Schema builder's component name, field and section
52
+ names, and enum options describe their error lines through the shared
53
+ `fieldA11y` helper, like the other hand-rolled demos; a section's
54
+ "needs at least one field" line and the import dialog's parse error
55
+ are announced, and the "fix the highlighted fields" line is a status
56
+ region. The row ids are the field paths, so both row levels stay
57
+ unique.
58
+
59
+ ## 0.22.1 — 2026-10-22
60
+
61
+ ### Fixed
62
+
63
+ - Nested `useFieldArray` row ids survive an outer reorder. Inner ids are
64
+ stored per path (`teams.0.members`), so an outer `move`, `swap`, `remove`,
65
+ `insert`, or whole-array write left the entry at `teams.2.members` meeting
66
+ team A's members with team C's history: the same-index fallback handed the
67
+ first rows the other team's ids, the rest reminted, and React remounted
68
+ inputs a user may have been typing in. The outer hook now re-seats every
69
+ descendant path's id entry to follow its row (by id, so an exact op
70
+ mapping and a by-value reconciliation re-seat the same way), and drops the
71
+ entries of a removed row's slot. Inner-only trees that reorder the outer
72
+ array imperatively keep the value fallback.
73
+
74
+ ### Docs
75
+
76
+ - Field arrays: the array-op revalidation sentence now matches the gate.
77
+ Ops run under the change trigger, so in the default `onBlur` mode (and
78
+ `onSubmit`) they stay silent until the first failed submit switches to
79
+ `reValidateMode`; `onTouched` needs the array path itself touched;
80
+ `onChange` and `all` revalidate at once. A "Render cost" note says the
81
+ hook re-renders its caller on every keystroke in any row and how to
82
+ keep rows cheap.
83
+ - Persistence: a loaded draft is adopted without validation, so a draft
84
+ that fails the schema reads valid until something validates;
85
+ `validateOnMount` runs before `persistForm` applies the draft and does
86
+ not cover it, so call `form.validate()` after the load.
87
+ - `useFormValues` re-renders on every keystroke anywhere in the form; keep
88
+ it out of per-row components.
89
+ - A `"use server"` action must parse `data` with the schema on the server
90
+ on every path, not only the no-JS one.
91
+ - Masked inputs: `"empty"` writes `emptyValue` for clearable and non-string
92
+ fields and `""` for a required string, as the 0.22.0 binding does.
93
+ - Components: the `SelectField` props row names the real option type, and
94
+ the guide states the by-value `path` rule (`TextField` on a `z.number()`
95
+ is a compile error) that the API page already had.
96
+ - CLI: the schema module and `formstand.config.ts` are executed through
97
+ jiti, so point the CLI only at files you would run with `node`; the
98
+ overview names all three inputs.
99
+ - Snippets import every name they use (`Form`, `FormStateApi`,
100
+ `createForm`, `textInputProps`); the `onSubmit` snippet is in a tsx
101
+ fence; `fillFields` is documented as typed per key.
102
+ - A "Security notes" section on validation.md collects server
103
+ re-validation, untrusted drafts, and the CLI executing modules, with
104
+ links to the sections that hold the detail.
105
+ - Roadmap: `--path-depth` moves to the shipped list. The remaining em
106
+ dashes in the roadmap and the VitePress theme comments are reworded.
107
+ - Playground: every demo is a lazy chunk. The main bundle drops from
108
+ 2.95 MB (779 kB gzip) to 381 kB (116 kB gzip) and the main stylesheet
109
+ from 601 kB to 34 kB; each tab's component, kit and stylesheet load on
110
+ first visit, and the View code panel fetches source text on demand
111
+ behind a static file tree. Mantine's sheet loads as its `@layer` variant
112
+ so the shell's unlayered rules keep winning regardless of load order.
113
+ - Playground: the Conditional demo is a `z.discriminatedUnion` bound with
114
+ `useField` for the discriminant and `useVariantField` for the branch
115
+ fields, and the payload carries only the active branch (the flat version
116
+ submitted the hidden card number alongside the bank fields).
117
+ - Playground: the hand-rolled demos wire each error line to its input
118
+ (`aria-describedby`, `aria-invalid`, `aria-live`) through one shared
119
+ helper and focus the first errored control after a failed submit; the
120
+ View state / View code toggles expose `aria-pressed`; the MUI Invoice,
121
+ shadcn Team and shadcn Onboarding contact rows label their
122
+ placeholder-only cells.
123
+ - Playground: the Autosave status reads "Draft restored" after a restore
124
+ instead of "Unsaved changes"; the Schema builder's Zod paste tab says
125
+ that pasted code runs in the page and its View code list includes
126
+ `ImportModal.tsx` and `parseZod.ts`; the Perf demo's inputs follow the
127
+ theme; the light theme's eyebrow (6.76:1) and muted text (4.88:1) clear
128
+ the AA floor; the theme preference is written only when the user
129
+ toggles, so a docs visitor's "auto" survives opening the playground.
130
+ - Playground: the Onboarding module's field files, its emergency-contact
131
+ rows, and the CLI command builder wire each error line to its control
132
+ through the shared `fieldA11y` helper, like the other hand-rolled
133
+ demos since 0.22.0; the contacts array's own error line is a live
134
+ region. The command builder's file set ships the helper, so it opens
135
+ in StackBlitz.
136
+ - Playground: the generated kitchen sink's input schema carries human
137
+ messages on every check, so an empty submit no longer shows zod's raw
138
+ messages, and the regenerated plain output carries the CLI's new aria
139
+ wiring. `examples/src` is now linted with the library's rule set.
140
+
141
+ ## formstand-cli 0.24.0 — 2026-10-20
142
+
143
+ ### Added
144
+
145
+ - `--no-live` and `--no-form-prop`: an explicit flag turns the mode off for
146
+ one run when the config file sets `live: true` / `formProp: true`. Before,
147
+ nothing could override a configured mode (`--no-live` was an unknown
148
+ flag).
149
+ - JSON Schema mode carries `pattern` and `multipleOf` into the generated
150
+ validator, as `.regex(new RegExp("..."))` and `.multipleOf(n)`. A pattern
151
+ JavaScript's `RegExp` refuses degrades to a TODO instead of a module that
152
+ throws on load. Both, and any `format` outside email/uri/uuid/date, are
153
+ recorded as unmodeled checks, so the generated spec skips its blind round
154
+ trip over them and `--tests` warns on stderr; they were dropped silently
155
+ before. The IR gains `StringChecks.regex` and `NumberChecks.multipleOf`.
156
+ - Type mode walks intersections (`A & B`, the merged member set, later wins
157
+ on a collision, a TODO where two members disagree on a key) and
158
+ discriminated unions of object types sharing a string-literal key, to
159
+ the same IR union the zod and JSON Schema walks produce. A union of
160
+ numeric literals names the string-only enum as its reason instead of
161
+ "unsupported union type".
162
+
163
+ ### Fixed
164
+
165
+ - Type mode had no recursion guard: `type Tree = { value: string; left?:
166
+ Tree; right?: Tree }` expanded to the depth budget (6142 IR nodes, a
167
+ 188 KB file, 518 module files). The walk tracks the declared types on the
168
+ current path and cuts a repeat with a "recursive type" TODO, the way the
169
+ zod walker treats its getter idiom. A member typed as the global
170
+ `Function` (no call signature of its own) walked Function.prototype to
171
+ the budget; it now degrades like a method.
172
+ - `--type someConst` (a value, not a type) generated an "any" TODO form; it
173
+ is now an error naming the symbol kind. An index signature beside named
174
+ members (`{ a: string; [k: string]: string }`) was dropped without a
175
+ trace; the members bind and a TODO sits on the section, mirrored on
176
+ stderr and above the generated schema.
177
+ - OpenAPI 3.0 idioms in JSON Schema mode: `{ $ref, nullable: true }` was
178
+ not nullable (the ref walk returned before the flag applied);
179
+ `{ allOf: [{ $ref }], nullable: true }` lost the outer keywords in the
180
+ merge; `allOf: [{ $ref: StringEnum }]` (the 3.0 way to annotate a ref)
181
+ was "allOf with non-object branches"; `enum: ["a", "b", null]` with
182
+ `nullable: true` (3.0) or `type: ["string", "null"]` (3.1) was a
183
+ "non-string enum" TODO; an `allOf` branch that is itself an `allOf`, and
184
+ `$ref` chains, degraded; `#/$defs/My%20Type` was not percent-decoded.
185
+ All six now walk to the expected IR, and a self-referring `allOf` branch
186
+ is named in its TODO.
187
+ - Draft-04 `exclusiveMinimum: true` / `exclusiveMaximum: true` beside
188
+ `minimum` / `maximum` silently read as an inclusive bound; the flag now
189
+ makes the bound exclusive.
190
+ - An empty enum list (`enum: []` in a document, `z.enum([])` in a schema,
191
+ which zod constructs and then rejects every value for) emitted a select
192
+ with no option; it degrades to a TODO in both walkers.
193
+ - `--out some/dir/` (a trailing separator, or an existing directory) in the
194
+ single-file layout wrote a file literally named after the folder, or
195
+ refused to "overwrite" it. It is refused with an error that says to name
196
+ a `.tsx` file or use `--layout module`.
197
+ - `--name profileForm` passed validation and the generated spec rendered
198
+ `<profileForm />`, which JSX reads as a DOM tag; the name must now start
199
+ with an uppercase letter.
200
+ - `--force` over a module folder left the previous run's files in place
201
+ silently (a removed section's `sections/GoneSection.tsx` survived and
202
+ still compiled). Files that carry the generator header on their first
203
+ line and are not in the new set are now named on stderr as
204
+ `warning: stale: <path>`, so the leftover is visible. They are not
205
+ deleted: the header says the file is yours to edit, and a folder can hold
206
+ a single-file component from another run under the same header, so
207
+ removal is left to you.
208
+ - `--schema constructor` (or `toString`, `__proto__`) resolved a prototype
209
+ member of the component store and generated a degraded form named after
210
+ it; lookups and pointer segments use own keys only, so it reports "no
211
+ schema named".
212
+ - A string-literal export name (`export { inner as "my schema" }`) emitted
213
+ `import { my schema } from ...`; it is refused with the rule, for an
214
+ explicit `--export` and for the sole-export fallback.
215
+ - The generated spec's blank-submit case picked its control among the
216
+ blank-invalid fields only, so a required "Age" beside an optional "Age"
217
+ in another section made `getByLabelText` throw. Uniqueness is now judged
218
+ against every label the blank render shows, for the label fills too.
219
+ - **The plain backend's error lines are described to their controls.** The
220
+ in-file `AutocompleteField`, `TextareaField`, `PlaceholderField` and
221
+ `EnumSelectField`, the union variant leaves, and the module layout's field
222
+ files and array rows emitted a bare `<p role="alert">` with no id and a
223
+ control with no `aria-describedby`, so assistive tech heard the field but
224
+ never its message. The error line now owns `${field.path}-error` and the
225
+ control points at it while the error shows (the same runtime derivation
226
+ the autocomplete datalist id already used, unique per array row with no
227
+ `useId`); `aria-invalid` keeps coming from the prop builders. The kit
228
+ backends were already wired through their own components.
229
+
3
230
  ## 0.22.0 — 2026-10-19
4
231
 
5
232
  ### Fixed
@@ -95,11 +95,11 @@ var hasOwnSlot = (obj, path) => parsePath(path).reduce(
95
95
  },
96
96
  { ok: true, current: obj }
97
97
  ).ok;
98
- var writeIntoArray = (arr, index, rest, value) => {
98
+ var writeIntoArray = (arr, index, rest, value, kindAt, prefix) => {
99
99
  const length = Math.max(arr.length, index + 1);
100
100
  return Array.from(
101
101
  { length },
102
- (_, i) => i === index ? writeSegments(arr[i], rest, value) : arr[i]
102
+ (_, i) => i === index ? writeSegments(arr[i], rest, value, kindAt, [...prefix, index]) : arr[i]
103
103
  );
104
104
  };
105
105
  var refusesHugeIndex = (index) => {
@@ -111,7 +111,7 @@ var refusesHugeIndex = (index) => {
111
111
  }
112
112
  return false;
113
113
  };
114
- var writeSegments = (obj, segments, value) => {
114
+ var writeSegments = (obj, segments, value, kindAt, prefix) => {
115
115
  const [head, ...rest] = segments;
116
116
  if (head === void 0) return value;
117
117
  if (Array.isArray(obj)) {
@@ -122,7 +122,7 @@ var writeSegments = (obj, segments, value) => {
122
122
  return obj;
123
123
  }
124
124
  if (refusesHugeIndex(head)) return obj;
125
- return writeIntoArray(obj, head, rest, value);
125
+ return writeIntoArray(obj, head, rest, value, kindAt, prefix);
126
126
  }
127
127
  if (isPlainRecord(obj)) {
128
128
  if (!isPlainObject(obj)) {
@@ -131,16 +131,27 @@ var writeSegments = (obj, segments, value) => {
131
131
  );
132
132
  return obj;
133
133
  }
134
- const key = String(head);
135
- return { ...obj, [key]: writeSegments(obj[key], rest, value) };
134
+ const key2 = String(head);
135
+ return {
136
+ ...obj,
137
+ [key2]: writeSegments(obj[key2], rest, value, kindAt, [...prefix, key2])
138
+ };
136
139
  }
137
- if (typeof head === "number") {
140
+ const kind = kindAt?.(prefix.map(String).join(".")) ?? (typeof head === "number" ? "array" : "record");
141
+ if (kind === "array") {
142
+ if (typeof head !== "number") {
143
+ console.warn(
144
+ `[formstand] cannot write string key "${head}" into an array; value left unchanged.`
145
+ );
146
+ return obj;
147
+ }
138
148
  if (refusesHugeIndex(head)) return obj;
139
- return writeIntoArray([], head, rest, value);
149
+ return writeIntoArray([], head, rest, value, kindAt, prefix);
140
150
  }
141
- return { [head]: writeSegments(void 0, rest, value) };
151
+ const key = String(head);
152
+ return { [key]: writeSegments(void 0, rest, value, kindAt, [...prefix, key]) };
142
153
  };
143
- var setAtPath = (obj, path, value) => writeSegments(obj, parsePath(path), value);
154
+ var setAtPath = (obj, path, value, containerKindAt) => writeSegments(obj, parsePath(path), value, containerKindAt, []);
144
155
  function useFormSelector(form, selector) {
145
156
  return useStore(form.store, selector);
146
157
  }
@@ -157,5 +168,5 @@ var isDevToolsBuild = () => {
157
168
  };
158
169
 
159
170
  export { getAtPath, hasOwnSlot, isDevBuild, isDevToolsBuild, isFieldDirty, isPlainObject, parsePath, setAtPath, slotAtPath, useFormSelector, useFormSelectorShallow, valuesEqual };
160
- //# sourceMappingURL=chunk-BMXIZEI4.js.map
161
- //# sourceMappingURL=chunk-BMXIZEI4.js.map
171
+ //# sourceMappingURL=chunk-EQIS5IVH.js.map
172
+ //# sourceMappingURL=chunk-EQIS5IVH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/equality.ts","../src/core/path.ts","../src/react/useFormSelector.ts","../src/core/env.ts"],"names":["key"],"mappings":"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","file":"chunk-EQIS5IVH.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 // Numbers compare with `===` semantics on top of Object.is: Object.is\n // alone keeps NaN equal to itself (wanted) but tells -0 from 0, and a\n // number input renders both as \"0\", so a field holding -0 against an\n // initial 0 read as permanently dirty with nothing visible to undo.\n if (typeof a === \"number\" && typeof b === \"number\") return a === b;\n if (a instanceof Date && b instanceof Date) {\n // Two invalid Dates are equal: their timestamps are both NaN, which\n // `===` denies, so a cleared or half-typed date against an invalid\n // initial read as dirty forever.\n const [ta, tb] = [a.getTime(), b.getTime()];\n return ta === tb || (Number.isNaN(ta) && Number.isNaN(tb));\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// What setAtPath creates at an ABSENT slot. The segment type alone cannot\n// tell a z.record keyed by numeric strings from an array (both read\n// \"lookup.0\"), so a caller that knows the schema hands in a resolver; it\n// answers for the dotted prefix being created and returns null where it\n// has no opinion, which falls back to the segment-type rule.\nexport type ContainerKind = \"array\" | \"record\";\nexport type ContainerKindAt = (prefix: string) => ContainerKind | null;\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 as an LRU over Map insertion order:\n// a hit re-inserts its key (moving it to the back) and an overflow evicts\n// the front, so an app whose LIVE path set exceeds the cap keeps its hot\n// paths cached instead of re-parsing every one of them after each reset\n// (the old wholesale clear). Eviction only costs re-parsing, never\n// correctness. Same delete-then-set recency idiom as useFieldArray's\n// per-path id cache.\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) {\n parseCache.delete(path);\n parseCache.set(path, cached);\n return cached;\n }\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) {\n const oldest = parseCache.keys().next().value;\n if (oldest !== undefined) parseCache.delete(oldest);\n }\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 kindAt: ContainerKindAt | undefined,\n prefix: readonly PathSegment[],\n): readonly unknown[] => {\n const length = Math.max(arr.length, index + 1);\n return Array.from({ length }, (_, i) =>\n i === index\n ? writeSegments(arr[i], rest, value, kindAt, [...prefix, index])\n : 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 kindAt: ContainerKindAt | undefined,\n prefix: readonly PathSegment[],\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, kindAt, prefix);\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 {\n ...obj,\n [key]: writeSegments(obj[key], rest, value, kindAt, [...prefix, key]),\n };\n }\n\n // Absent (or non-container) value: create a container. The schema's\n // answer for this slot wins when the caller supplied one (a z.record\n // keyed \"0\" must not become an array, and a z.array must not become a\n // record); otherwise the segment type decides, as the docs state.\n const kind =\n kindAt?.(prefix.map(String).join(\".\")) ??\n (typeof head === \"number\" ? \"array\" : \"record\");\n if (kind === \"array\") {\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([], head, rest, value, kindAt, prefix);\n }\n const key = String(head);\n return { [key]: writeSegments(undefined, rest, value, kindAt, [...prefix, key]) };\n};\n\n// `containerKindAt` is optional so the bare utility keeps its segment-type\n// rule; createForm passes the schema-derived resolver from validation.ts.\nexport const setAtPath = <T>(\n obj: T,\n path: string,\n value: unknown,\n containerKindAt?: ContainerKindAt,\n): T => writeSegments(obj, parsePath(path), value, containerKindAt, []) 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","// The dev-build test the library consults before emitting a console\n// warning. Bundlers that define NODE_ENV fold it to a constant and drop the\n// dev-only branches; the `typeof process` guard keeps the SAME expression\n// from throwing in a runtime that defines no `process` at all (a CDN/ESM\n// consumer, a test runner without the define). That last case matters most\n// on the submit path: handleSubmit promises never to reject, and the\n// warning that fires only when a save failed was the one line that could\n// break the promise exactly on the failure path. It FAILS OPEN on purpose:\n// an unknowable environment gets the warnings, which cost nothing but a\n// console line.\nexport const isDevBuild = (): boolean =>\n typeof process === \"undefined\" ||\n process.env?.[\"NODE_ENV\"] !== \"production\";\n\n// The gate for the two sites that EXPOSE form state: the FormstandDevtools\n// panel (renders every value and error into the page) and the zustand\n// devtools middleware (streams every write to the Redux extension). These\n// must FAIL CLOSED: isDevBuild's \"no process means dev\" reading let a\n// CDN/ESM, Deno, or worker production build mount the panel and keep the\n// middleware live for any end user with the extension installed. Only a\n// readable NODE_ENV that is a string other than \"production\" counts as\n// development; no process, no env, or an unset NODE_ENV all read as\n// production here.\nexport const isDevToolsBuild = (): boolean => {\n if (typeof process === \"undefined\") return false;\n const env = process.env?.[\"NODE_ENV\"];\n return typeof env === \"string\" && env !== \"production\";\n};\n"]}
@@ -9,6 +9,7 @@ type BlobLike = Readonly<{
9
9
  type LeafType = string | number | boolean | bigint | symbol | null | undefined | Date | RegExp | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown> | ((...args: never[]) => unknown) | BlobLike;
10
10
  type IsRecord<T> = T extends LeafType ? false : T extends readonly unknown[] ? false : T extends object ? true : false;
11
11
  type IsArray<T> = T extends readonly unknown[] ? true : false;
12
+ type PathKey<K extends number> = number extends K ? `${number}` : `${K}`;
12
13
  type IsTuple<T> = T extends readonly unknown[] ? number extends T["length"] ? false : true : false;
13
14
  type Prev = [
14
15
  never,
@@ -47,8 +48,10 @@ type FieldPathRec<T, D extends number> = [D] extends [0] ? never : IsTuple<T> ex
47
48
  [K in keyof T & `${number}`]: 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);
48
49
  }[keyof T & `${number}`] : 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 ? {
49
50
  [K in keyof T & string]: K extends `${string}.${string}` ? never : 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);
50
- }[keyof T & string] : never;
51
- type StepValue<T, K extends string> = T extends readonly (infer U)[] ? K extends keyof T ? T[K] : K extends `${number}` ? U : never : T extends object ? K extends keyof T ? T[K] : never : never;
51
+ }[keyof T & string] | {
52
+ [K in keyof T & number]: PathKey<K> | (IsRecord<NonNullable<T[K]>> extends true ? `${PathKey<K>}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : IsArray<NonNullable<T[K]>> extends true ? `${PathKey<K>}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : never);
53
+ }[keyof T & number] : never;
54
+ type StepValue<T, K extends string> = T extends readonly (infer U)[] ? K extends keyof T ? T[K] : K extends `${number}` ? U : never : T extends object ? K extends keyof T ? T[K] : K extends `${infer N extends number}` ? N extends keyof T ? T[N] : never : never : never;
52
55
  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;
53
56
  type ArrayItemOf<T> = [NonNullable<T>] extends [readonly (infer U)[]] ? number extends NonNullable<T>["length"] ? U : never : never;
54
57
  type IsListValue<V> = [NonNullable<V>] extends [readonly unknown[]] ? number extends NonNullable<V>["length"] ? true : false : false;
@@ -201,4 +204,5 @@ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = R
201
204
  }>;
202
205
  declare const createForm: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(schema: TSchema, options: CreateFormOptions<TSchema, D>) => Form<TSchema, D>;
203
206
 
204
- export { type ArrayFieldPath as A, 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 FormState as a, type FieldValidationResult as b, type FieldPath as c, type FieldValue as d, type ArrayItemOf as e, type FieldSnapshot as f, type FieldsValidationResult as g, type ResetOptions as h, type SettledFieldsValidationResult as i, type SettledValidationResult as j, type SubmitHandler as k, type SubmitOptions as l, type SubmitResult as m, type ValidationResult as n, type ValidationTrigger as o, createForm as p, emptyValueForSchema as q, flattenIssues as r, isAsyncRequiredError as s, isClearableSchema as t, shouldValidateOn as u, validateAsync as v, validateSync as w };
207
+ export { createForm as p, emptyValueForSchema as q, flattenIssues as r, isAsyncRequiredError as s, isClearableSchema as t, shouldValidateOn as u, validateAsync as v, validateSync as w };
208
+ export type { ArrayFieldPath as A, BoolMap as B, CreateFormOptions as C, DefaultPathDepth as D, ErrorMap as E, Form as F, InvalidSubmitHandler as I, PathDepth as P, ReadonlyStoreApi as R, SettledFieldValidationResult as S, ValidationMode as V, FormState as a, FieldValidationResult as b, FieldPath as c, FieldValue as d, ArrayItemOf as e, FieldSnapshot as f, FieldsValidationResult as g, ResetOptions as h, SettledFieldsValidationResult as i, SettledValidationResult as j, SubmitHandler as k, SubmitOptions as l, SubmitResult as m, ValidationResult as n, ValidationTrigger as o };
@@ -9,6 +9,7 @@ type BlobLike = Readonly<{
9
9
  type LeafType = string | number | boolean | bigint | symbol | null | undefined | Date | RegExp | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | WeakMap<object, unknown> | WeakSet<object> | Promise<unknown> | ((...args: never[]) => unknown) | BlobLike;
10
10
  type IsRecord<T> = T extends LeafType ? false : T extends readonly unknown[] ? false : T extends object ? true : false;
11
11
  type IsArray<T> = T extends readonly unknown[] ? true : false;
12
+ type PathKey<K extends number> = number extends K ? `${number}` : `${K}`;
12
13
  type IsTuple<T> = T extends readonly unknown[] ? number extends T["length"] ? false : true : false;
13
14
  type Prev = [
14
15
  never,
@@ -47,8 +48,10 @@ type FieldPathRec<T, D extends number> = [D] extends [0] ? never : IsTuple<T> ex
47
48
  [K in keyof T & `${number}`]: 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);
48
49
  }[keyof T & `${number}`] : 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 ? {
49
50
  [K in keyof T & string]: K extends `${string}.${string}` ? never : 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);
50
- }[keyof T & string] : never;
51
- type StepValue<T, K extends string> = T extends readonly (infer U)[] ? K extends keyof T ? T[K] : K extends `${number}` ? U : never : T extends object ? K extends keyof T ? T[K] : never : never;
51
+ }[keyof T & string] | {
52
+ [K in keyof T & number]: PathKey<K> | (IsRecord<NonNullable<T[K]>> extends true ? `${PathKey<K>}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : IsArray<NonNullable<T[K]>> extends true ? `${PathKey<K>}.${FieldPathRec<NonNullable<T[K]>, Prev[D]>}` : never);
53
+ }[keyof T & number] : never;
54
+ type StepValue<T, K extends string> = T extends readonly (infer U)[] ? K extends keyof T ? T[K] : K extends `${number}` ? U : never : T extends object ? K extends keyof T ? T[K] : K extends `${infer N extends number}` ? N extends keyof T ? T[N] : never : never : never;
52
55
  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;
53
56
  type ArrayItemOf<T> = [NonNullable<T>] extends [readonly (infer U)[]] ? number extends NonNullable<T>["length"] ? U : never : never;
54
57
  type IsListValue<V> = [NonNullable<V>] extends [readonly unknown[]] ? number extends NonNullable<V>["length"] ? true : false : false;
@@ -201,4 +204,5 @@ type Form<TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth> = R
201
204
  }>;
202
205
  declare const createForm: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(schema: TSchema, options: CreateFormOptions<TSchema, D>) => Form<TSchema, D>;
203
206
 
204
- export { type ArrayFieldPath as A, 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 FormState as a, type FieldValidationResult as b, type FieldPath as c, type FieldValue as d, type ArrayItemOf as e, type FieldSnapshot as f, type FieldsValidationResult as g, type ResetOptions as h, type SettledFieldsValidationResult as i, type SettledValidationResult as j, type SubmitHandler as k, type SubmitOptions as l, type SubmitResult as m, type ValidationResult as n, type ValidationTrigger as o, createForm as p, emptyValueForSchema as q, flattenIssues as r, isAsyncRequiredError as s, isClearableSchema as t, shouldValidateOn as u, validateAsync as v, validateSync as w };
207
+ export { createForm as p, emptyValueForSchema as q, flattenIssues as r, isAsyncRequiredError as s, isClearableSchema as t, shouldValidateOn as u, validateAsync as v, validateSync as w };
208
+ export type { ArrayFieldPath as A, BoolMap as B, CreateFormOptions as C, DefaultPathDepth as D, ErrorMap as E, Form as F, InvalidSubmitHandler as I, PathDepth as P, ReadonlyStoreApi as R, SettledFieldValidationResult as S, ValidationMode as V, FormState as a, FieldValidationResult as b, FieldPath as c, FieldValue as d, ArrayItemOf as e, FieldSnapshot as f, FieldsValidationResult as g, ResetOptions as h, SettledFieldsValidationResult as i, SettledValidationResult as j, SubmitHandler as k, SubmitOptions as l, SubmitResult as m, ValidationResult as n, ValidationTrigger as o };