formstand 0.25.0 → 0.26.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 +112 -0
- package/dist/chunk-XJ6W5CKB.js +22 -0
- package/dist/chunk-XJ6W5CKB.js.map +1 -0
- package/dist/{createForm-CMVALnc3.d.cts → createForm-RE6Bjnfv.d.cts} +17 -6
- package/dist/{createForm-CMVALnc3.d.ts → createForm-RE6Bjnfv.d.ts} +17 -6
- package/dist/devtools/index.cjs +30 -54
- package/dist/devtools/index.cjs.map +1 -1
- package/dist/devtools/index.js +31 -21
- package/dist/devtools/index.js.map +1 -1
- package/dist/index.cjs +315 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -3
- package/dist/index.d.ts +13 -3
- package/dist/index.js +470 -97
- package/dist/index.js.map +1 -1
- package/dist/testing/index.cjs +6 -10
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +4 -4
- package/dist/testing/index.d.ts +4 -4
- package/dist/testing/index.js +6 -10
- package/dist/testing/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-EQIS5IVH.js +0 -172
- package/dist/chunk-EQIS5IVH.js.map +0 -1
package/dist/testing/index.cjs
CHANGED
|
@@ -25,33 +25,29 @@ var fillField = (form, path, value) => {
|
|
|
25
25
|
const before = form.store.getState();
|
|
26
26
|
form.setValue(path, value);
|
|
27
27
|
const state = form.store.getState();
|
|
28
|
-
if (state === before) return;
|
|
29
|
-
|
|
28
|
+
if (state === before) return void 0;
|
|
29
|
+
return shouldValidateOn(
|
|
30
30
|
"change",
|
|
31
31
|
state.mode,
|
|
32
32
|
state.reValidateMode,
|
|
33
33
|
state.submitCount > 0,
|
|
34
34
|
flagAt(state.touched, path)
|
|
35
|
-
))
|
|
36
|
-
form.validateField(path);
|
|
37
|
-
}
|
|
35
|
+
) ? form.validateField(path) : void 0;
|
|
38
36
|
};
|
|
39
37
|
var blurField = (form, path) => {
|
|
40
38
|
form.setTouched(path, true);
|
|
41
39
|
const state = form.store.getState();
|
|
42
|
-
|
|
40
|
+
return shouldValidateOn(
|
|
43
41
|
"blur",
|
|
44
42
|
state.mode,
|
|
45
43
|
state.reValidateMode,
|
|
46
44
|
state.submitCount > 0,
|
|
47
45
|
true
|
|
48
|
-
))
|
|
49
|
-
form.validateField(path);
|
|
50
|
-
}
|
|
46
|
+
) ? form.validateField(path) : void 0;
|
|
51
47
|
};
|
|
52
48
|
var fillAndBlurField = (form, path, value) => {
|
|
53
49
|
fillField(form, path, value);
|
|
54
|
-
blurField(form, path);
|
|
50
|
+
return blurField(form, path);
|
|
55
51
|
};
|
|
56
52
|
var fillFields = (form, values) => {
|
|
57
53
|
Object.entries(values).forEach(([path, value]) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/maps.ts","../../src/core/mode.ts","../../src/testing/index.ts"],"names":[],"mappings":";;;AAiBO,IAAM,MAAA,GAAS,CAAC,GAAA,EAAc,GAAA,KACnC,MAAA,CAAO,MAAA,CAAO,GAAA,EAAK,GAAG,CAAA,IAAK,GAAA,CAAI,GAAG,CAAA,KAAM,IAAA;;;ACPnC,IAAM,mBAAmB,CAC9B,OAAA,EACA,MACA,cAAA,EACA,eAAA,EACA,UAAmB,KAAA,KACP;AACZ,EAAA,MAAM,SAAA,GAAY,kBAAkB,cAAA,GAAiB,IAAA;AACrD,EAAA,QAAQ,SAAA;AAAW,IACjB,KAAK,UAAA;AACH,MAAA,OAAO,OAAA,KAAY,QAAA;AAAA,IACrB,KAAK,QAAA;AACH,MAAA,OAAO,OAAA,KAAY,MAAA;AAAA,IACrB,KAAK,UAAA;AACH,MAAA,OAAO,KAAA;AAAA,IACT,KAAK,WAAA;AACH,MAAA,OAAO,YAAY,MAAA,IAAU,OAAA;AAAA,IAC/B,KAAK,KAAA;AACH,MAAA,OAAO,IAAA;AAAA;AAEb,CAAA;;;
|
|
1
|
+
{"version":3,"sources":["../../src/core/maps.ts","../../src/core/mode.ts","../../src/testing/index.ts"],"names":[],"mappings":";;;AAiBO,IAAM,MAAA,GAAS,CAAC,GAAA,EAAc,GAAA,KACnC,MAAA,CAAO,MAAA,CAAO,GAAA,EAAK,GAAG,CAAA,IAAK,GAAA,CAAI,GAAG,CAAA,KAAM,IAAA;;;ACPnC,IAAM,mBAAmB,CAC9B,OAAA,EACA,MACA,cAAA,EACA,eAAA,EACA,UAAmB,KAAA,KACP;AACZ,EAAA,MAAM,SAAA,GAAY,kBAAkB,cAAA,GAAiB,IAAA;AACrD,EAAA,QAAQ,SAAA;AAAW,IACjB,KAAK,UAAA;AACH,MAAA,OAAO,OAAA,KAAY,QAAA;AAAA,IACrB,KAAK,QAAA;AACH,MAAA,OAAO,OAAA,KAAY,MAAA;AAAA,IACrB,KAAK,UAAA;AACH,MAAA,OAAO,KAAA;AAAA,IACT,KAAK,WAAA;AACH,MAAA,OAAO,YAAY,MAAA,IAAU,OAAA;AAAA,IAC/B,KAAK,KAAA;AACH,MAAA,OAAO,IAAA;AAAA;AAEb,CAAA;;;ACFO,IAAM,SAAA,GAAY,CAKvB,IAAA,EACA,IAAA,EACA,KAAA,KACsC;AACtC,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,QAAA,EAAS;AACnC,EAAA,IAAA,CAAK,QAAA,CAAS,MAAM,KAAK,CAAA;AACzB,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,QAAA,EAAS;AAClC,EAAA,IAAI,KAAA,KAAU,QAAQ,OAAO,MAAA;AAC7B,EAAA,OAAO,gBAAA;AAAA,IACL,QAAA;AAAA,IACA,KAAA,CAAM,IAAA;AAAA,IACN,KAAA,CAAM,cAAA;AAAA,IACN,MAAM,WAAA,GAAc,CAAA;AAAA,IACpB,MAAA,CAAO,KAAA,CAAM,OAAA,EAAS,IAAI;AAAA,GAC5B,GACI,IAAA,CAAK,aAAA,CAAc,IAAI,CAAA,GACvB,MAAA;AACN;AAIO,IAAM,SAAA,GAAY,CAIvB,IAAA,EACA,IAAA,KACsC;AACtC,EAAA,IAAA,CAAK,UAAA,CAAW,MAAM,IAAI,CAAA;AAC1B,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,QAAA,EAAS;AAClC,EAAA,OAAO,gBAAA;AAAA,IACL,MAAA;AAAA,IACA,KAAA,CAAM,IAAA;AAAA,IACN,KAAA,CAAM,cAAA;AAAA,IACN,MAAM,WAAA,GAAc,CAAA;AAAA,IACpB;AAAA,GACF,GACI,IAAA,CAAK,aAAA,CAAc,IAAI,CAAA,GACvB,MAAA;AACN;AAMO,IAAM,gBAAA,GAAmB,CAK9B,IAAA,EACA,IAAA,EACA,KAAA,KACsC;AACtC,EAAA,SAAA,CAAU,IAAA,EAAM,MAAM,KAAK,CAAA;AAC3B,EAAA,OAAO,SAAA,CAAU,MAAM,IAAI,CAAA;AAC7B;AAQO,IAAM,UAAA,GAAa,CAIxB,IAAA,EACA,MAAA,KAGS;AACT,EAAA,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAC,IAAA,EAAM,KAAK,CAAA,KAAM;AAChD,IAAA,SAAA;AAAA,MACE,IAAA;AAAA,MACA,IAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,CAAC,CAAA;AACH;AAQO,IAAM,aAAa,CAIxB,IAAA,KAC6C,IAAA,CAAK,MAAA,CAAO,MAAM,MAAS","file":"index.cjs","sourcesContent":["import type { BoolMap } from \"./types\";\n\n// Own-key reads for the path-keyed maps (errors, touched, isValidating).\n// Their keys are user-controlled strings: a schema may declare a field\n// named `constructor`, a server response may name `toString`, and\n// JSON.parse yields own `__proto__` keys. A bare `map[key]` on a plain\n// object walks the prototype chain for those, so an EMPTY error map read\n// `Object` as the error list at \"constructor\" (and `stringArraysEqual`\n// then threw inside setState on `.every`), while `touched[\"toString\"]` was\n// a truthy function. Every read of these maps by a runtime key goes\n// through here; writes are already safe (computed keys and\n// Object.fromEntries define own properties).\nexport const entryAt = <V>(\n map: Readonly<Record<string, V>>,\n key: string,\n): V | undefined => (Object.hasOwn(map, key) ? map[key] : undefined);\n\nexport const flagAt = (map: BoolMap, key: string): boolean =>\n Object.hasOwn(map, key) && map[key] === true;\n","export type ValidationMode =\n | \"onChange\"\n | \"onBlur\"\n | \"onSubmit\"\n // Validate on blur always; on change only once the field has been touched.\n | \"onTouched\"\n // Validate on both change and blur.\n | \"all\";\n\nexport type ValidationTrigger = \"change\" | \"blur\";\n\nexport const shouldValidateOn = (\n trigger: ValidationTrigger,\n mode: ValidationMode,\n reValidateMode: ValidationMode,\n submitAttempted: boolean,\n touched: boolean = false,\n): boolean => {\n const effective = submitAttempted ? reValidateMode : mode;\n switch (effective) {\n case \"onChange\":\n return trigger === \"change\";\n case \"onBlur\":\n return trigger === \"blur\";\n case \"onSubmit\":\n return false;\n case \"onTouched\":\n return trigger === \"blur\" || touched;\n case \"all\":\n return true;\n }\n};\n","import type { z } from \"zod\";\nimport type { Form, SubmitResult } from \"../core/createForm\";\nimport type { FieldValidationResult } from \"../core/validation\";\nimport type {\n DefaultPathDepth,\n FieldPath,\n FieldValue,\n PathDepth,\n} from \"../core/fieldPath\";\nimport { flagAt } from \"../core/maps\";\nimport { shouldValidateOn } from \"../core/mode\";\n\n// formstand/testing: drive a form in tests the way the HOOKS drive it —\n// mode-gated validation included — without mounting components. Tests\n// that call form.setValue directly get the imperative, validation-silent\n// write (correct for the imperative API, wrong for simulating a user),\n// and re-deriving the change/blur gates by hand is exactly the kind of\n// mode-semantics knowledge a test should not have to carry. These\n// helpers restate useField's own recipes over the core API, so a test\n// exercises the same validation flow a rendered input would, at store\n// speed. They are framework-agnostic on purpose: they return plain data\n// and write plain state; your test runner's assertions do the judging.\n\n// A user edit: write the value, then validate under the CHANGE gate —\n// exactly useField's setValue, including the no-op guard (writing the\n// identical value leaves state untouched, so the gate is skipped too).\n// Returns the validation's result when the gate validated (so a test that\n// hit an async refine can `await result.promise` instead of guessing a\n// delay), undefined when it did not.\nexport const fillField = <\n TSchema extends z.ZodType,\n P extends FieldPath<z.input<TSchema>, D>,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n path: P,\n value: FieldValue<z.input<TSchema>, P>,\n): FieldValidationResult | undefined => {\n const before = form.store.getState();\n form.setValue(path, value);\n const state = form.store.getState();\n if (state === before) return undefined;\n return shouldValidateOn(\n \"change\",\n state.mode,\n state.reValidateMode,\n state.submitCount > 0,\n flagAt(state.touched, path),\n )\n ? form.validateField(path)\n : undefined;\n};\n\n// A user leaving the field: mark it touched, then validate under the\n// BLUR gate — exactly useField's onBlur. Same return as fillField.\nexport const blurField = <\n TSchema extends z.ZodType,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n path: FieldPath<z.input<TSchema>, D>,\n): FieldValidationResult | undefined => {\n form.setTouched(path, true);\n const state = form.store.getState();\n return shouldValidateOn(\n \"blur\",\n state.mode,\n state.reValidateMode,\n state.submitCount > 0,\n true,\n )\n ? form.validateField(path)\n : undefined;\n};\n\n// Edit-then-leave, the way a user fills a form field. The common case in\n// a test that wants touched-gated error UIs to show what they would show.\n// Returns the blur's result (the later of the two passes, and the one\n// that is guaranteed to have run under every mode but onSubmit).\nexport const fillAndBlurField = <\n TSchema extends z.ZodType,\n P extends FieldPath<z.input<TSchema>, D>,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n path: P,\n value: FieldValue<z.input<TSchema>, P>,\n): FieldValidationResult | undefined => {\n fillField(form, path, value);\n return blurField(form, path);\n};\n\n// Fill several fields in one call: entries are applied in order, each\n// through the full fillField recipe. Each value is typed from its own key\n// (a mapped type over the path union), the same contract fillField has\n// per call: `fillFields(form, { age: \"x\" })` is a compile error exactly\n// as `fillField(form, \"age\", \"x\")` is. Fixtures that are genuinely\n// runtime-keyed cast at the call site.\nexport const fillFields = <\n TSchema extends z.ZodType,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n values: Readonly<{\n [P in FieldPath<z.input<TSchema>, D>]?: FieldValue<z.input<TSchema>, P>;\n }>,\n): void => {\n Object.entries(values).forEach(([path, value]) => {\n fillField(\n form,\n path as FieldPath<z.input<TSchema>, D>,\n value as never,\n );\n });\n};\n\n// Submit with a no-op handler and hand back the SubmitResult: \"valid\"\n// carries the parsed z.output data, \"invalid\" the ErrorMap (written to\n// the store and marked touched, like any failed submit), \"error\" cannot\n// occur (the handler cannot throw), and \"skipped\" means a submit was\n// already in flight. Await it and switch — the discriminant does the\n// asserting-shape work.\nexport const submitForm = <\n TSchema extends z.ZodType,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n): Promise<SubmitResult<z.output<TSchema>>> => form.submit(() => undefined);\n"]}
|
package/dist/testing/index.d.cts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { PathDepth, DefaultPathDepth, Form, FieldPath, FieldValue, SubmitResult } from '../index.cjs';
|
|
2
|
+
import { PathDepth, DefaultPathDepth, Form, FieldPath, FieldValidationResult, FieldValue, SubmitResult } from '../index.cjs';
|
|
3
3
|
import 'zustand/vanilla';
|
|
4
4
|
|
|
5
|
-
declare const fillField: <TSchema extends z.ZodType, P extends FieldPath<z.input<TSchema>, D>, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, path: P, value: FieldValue<z.input<TSchema>, P>) =>
|
|
6
|
-
declare const blurField: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, path: FieldPath<z.input<TSchema>, D>) =>
|
|
7
|
-
declare const fillAndBlurField: <TSchema extends z.ZodType, P extends FieldPath<z.input<TSchema>, D>, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, path: P, value: FieldValue<z.input<TSchema>, P>) =>
|
|
5
|
+
declare const fillField: <TSchema extends z.ZodType, P extends FieldPath<z.input<TSchema>, D>, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, path: P, value: FieldValue<z.input<TSchema>, P>) => FieldValidationResult | undefined;
|
|
6
|
+
declare const blurField: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, path: FieldPath<z.input<TSchema>, D>) => FieldValidationResult | undefined;
|
|
7
|
+
declare const fillAndBlurField: <TSchema extends z.ZodType, P extends FieldPath<z.input<TSchema>, D>, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, path: P, value: FieldValue<z.input<TSchema>, P>) => FieldValidationResult | undefined;
|
|
8
8
|
declare const fillFields: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, values: Readonly<{ [P in FieldPath<z.input<TSchema>, D>]?: FieldValue<z.input<TSchema>, P>; }>) => void;
|
|
9
9
|
declare const submitForm: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>) => Promise<SubmitResult<z.output<TSchema>>>;
|
|
10
10
|
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { PathDepth, DefaultPathDepth, Form, FieldPath, FieldValue, SubmitResult } from '../index.js';
|
|
2
|
+
import { PathDepth, DefaultPathDepth, Form, FieldPath, FieldValidationResult, FieldValue, SubmitResult } from '../index.js';
|
|
3
3
|
import 'zustand/vanilla';
|
|
4
4
|
|
|
5
|
-
declare const fillField: <TSchema extends z.ZodType, P extends FieldPath<z.input<TSchema>, D>, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, path: P, value: FieldValue<z.input<TSchema>, P>) =>
|
|
6
|
-
declare const blurField: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, path: FieldPath<z.input<TSchema>, D>) =>
|
|
7
|
-
declare const fillAndBlurField: <TSchema extends z.ZodType, P extends FieldPath<z.input<TSchema>, D>, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, path: P, value: FieldValue<z.input<TSchema>, P>) =>
|
|
5
|
+
declare const fillField: <TSchema extends z.ZodType, P extends FieldPath<z.input<TSchema>, D>, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, path: P, value: FieldValue<z.input<TSchema>, P>) => FieldValidationResult | undefined;
|
|
6
|
+
declare const blurField: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, path: FieldPath<z.input<TSchema>, D>) => FieldValidationResult | undefined;
|
|
7
|
+
declare const fillAndBlurField: <TSchema extends z.ZodType, P extends FieldPath<z.input<TSchema>, D>, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, path: P, value: FieldValue<z.input<TSchema>, P>) => FieldValidationResult | undefined;
|
|
8
8
|
declare const fillFields: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>, values: Readonly<{ [P in FieldPath<z.input<TSchema>, D>]?: FieldValue<z.input<TSchema>, P>; }>) => void;
|
|
9
9
|
declare const submitForm: <TSchema extends z.ZodType, D extends PathDepth = DefaultPathDepth>(form: Form<TSchema, D>) => Promise<SubmitResult<z.output<TSchema>>>;
|
|
10
10
|
|
package/dist/testing/index.js
CHANGED
|
@@ -5,33 +5,29 @@ var fillField = (form, path, value) => {
|
|
|
5
5
|
const before = form.store.getState();
|
|
6
6
|
form.setValue(path, value);
|
|
7
7
|
const state = form.store.getState();
|
|
8
|
-
if (state === before) return;
|
|
9
|
-
|
|
8
|
+
if (state === before) return void 0;
|
|
9
|
+
return shouldValidateOn(
|
|
10
10
|
"change",
|
|
11
11
|
state.mode,
|
|
12
12
|
state.reValidateMode,
|
|
13
13
|
state.submitCount > 0,
|
|
14
14
|
flagAt(state.touched, path)
|
|
15
|
-
))
|
|
16
|
-
form.validateField(path);
|
|
17
|
-
}
|
|
15
|
+
) ? form.validateField(path) : void 0;
|
|
18
16
|
};
|
|
19
17
|
var blurField = (form, path) => {
|
|
20
18
|
form.setTouched(path, true);
|
|
21
19
|
const state = form.store.getState();
|
|
22
|
-
|
|
20
|
+
return shouldValidateOn(
|
|
23
21
|
"blur",
|
|
24
22
|
state.mode,
|
|
25
23
|
state.reValidateMode,
|
|
26
24
|
state.submitCount > 0,
|
|
27
25
|
true
|
|
28
|
-
))
|
|
29
|
-
form.validateField(path);
|
|
30
|
-
}
|
|
26
|
+
) ? form.validateField(path) : void 0;
|
|
31
27
|
};
|
|
32
28
|
var fillAndBlurField = (form, path, value) => {
|
|
33
29
|
fillField(form, path, value);
|
|
34
|
-
blurField(form, path);
|
|
30
|
+
return blurField(form, path);
|
|
35
31
|
};
|
|
36
32
|
var fillFields = (form, values) => {
|
|
37
33
|
Object.entries(values).forEach(([path, value]) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/testing/index.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"sources":["../../src/testing/index.ts"],"names":[],"mappings":";;;AA6BO,IAAM,SAAA,GAAY,CAKvB,IAAA,EACA,IAAA,EACA,KAAA,KACsC;AACtC,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,QAAA,EAAS;AACnC,EAAA,IAAA,CAAK,QAAA,CAAS,MAAM,KAAK,CAAA;AACzB,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,QAAA,EAAS;AAClC,EAAA,IAAI,KAAA,KAAU,QAAQ,OAAO,MAAA;AAC7B,EAAA,OAAO,gBAAA;AAAA,IACL,QAAA;AAAA,IACA,KAAA,CAAM,IAAA;AAAA,IACN,KAAA,CAAM,cAAA;AAAA,IACN,MAAM,WAAA,GAAc,CAAA;AAAA,IACpB,MAAA,CAAO,KAAA,CAAM,OAAA,EAAS,IAAI;AAAA,GAC5B,GACI,IAAA,CAAK,aAAA,CAAc,IAAI,CAAA,GACvB,MAAA;AACN;AAIO,IAAM,SAAA,GAAY,CAIvB,IAAA,EACA,IAAA,KACsC;AACtC,EAAA,IAAA,CAAK,UAAA,CAAW,MAAM,IAAI,CAAA;AAC1B,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,QAAA,EAAS;AAClC,EAAA,OAAO,gBAAA;AAAA,IACL,MAAA;AAAA,IACA,KAAA,CAAM,IAAA;AAAA,IACN,KAAA,CAAM,cAAA;AAAA,IACN,MAAM,WAAA,GAAc,CAAA;AAAA,IACpB;AAAA,GACF,GACI,IAAA,CAAK,aAAA,CAAc,IAAI,CAAA,GACvB,MAAA;AACN;AAMO,IAAM,gBAAA,GAAmB,CAK9B,IAAA,EACA,IAAA,EACA,KAAA,KACsC;AACtC,EAAA,SAAA,CAAU,IAAA,EAAM,MAAM,KAAK,CAAA;AAC3B,EAAA,OAAO,SAAA,CAAU,MAAM,IAAI,CAAA;AAC7B;AAQO,IAAM,UAAA,GAAa,CAIxB,IAAA,EACA,MAAA,KAGS;AACT,EAAA,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAC,IAAA,EAAM,KAAK,CAAA,KAAM;AAChD,IAAA,SAAA;AAAA,MACE,IAAA;AAAA,MACA,IAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,CAAC,CAAA;AACH;AAQO,IAAM,aAAa,CAIxB,IAAA,KAC6C,IAAA,CAAK,MAAA,CAAO,MAAM,MAAS","file":"index.js","sourcesContent":["import type { z } from \"zod\";\nimport type { Form, SubmitResult } from \"../core/createForm\";\nimport type { FieldValidationResult } from \"../core/validation\";\nimport type {\n DefaultPathDepth,\n FieldPath,\n FieldValue,\n PathDepth,\n} from \"../core/fieldPath\";\nimport { flagAt } from \"../core/maps\";\nimport { shouldValidateOn } from \"../core/mode\";\n\n// formstand/testing: drive a form in tests the way the HOOKS drive it —\n// mode-gated validation included — without mounting components. Tests\n// that call form.setValue directly get the imperative, validation-silent\n// write (correct for the imperative API, wrong for simulating a user),\n// and re-deriving the change/blur gates by hand is exactly the kind of\n// mode-semantics knowledge a test should not have to carry. These\n// helpers restate useField's own recipes over the core API, so a test\n// exercises the same validation flow a rendered input would, at store\n// speed. They are framework-agnostic on purpose: they return plain data\n// and write plain state; your test runner's assertions do the judging.\n\n// A user edit: write the value, then validate under the CHANGE gate —\n// exactly useField's setValue, including the no-op guard (writing the\n// identical value leaves state untouched, so the gate is skipped too).\n// Returns the validation's result when the gate validated (so a test that\n// hit an async refine can `await result.promise` instead of guessing a\n// delay), undefined when it did not.\nexport const fillField = <\n TSchema extends z.ZodType,\n P extends FieldPath<z.input<TSchema>, D>,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n path: P,\n value: FieldValue<z.input<TSchema>, P>,\n): FieldValidationResult | undefined => {\n const before = form.store.getState();\n form.setValue(path, value);\n const state = form.store.getState();\n if (state === before) return undefined;\n return shouldValidateOn(\n \"change\",\n state.mode,\n state.reValidateMode,\n state.submitCount > 0,\n flagAt(state.touched, path),\n )\n ? form.validateField(path)\n : undefined;\n};\n\n// A user leaving the field: mark it touched, then validate under the\n// BLUR gate — exactly useField's onBlur. Same return as fillField.\nexport const blurField = <\n TSchema extends z.ZodType,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n path: FieldPath<z.input<TSchema>, D>,\n): FieldValidationResult | undefined => {\n form.setTouched(path, true);\n const state = form.store.getState();\n return shouldValidateOn(\n \"blur\",\n state.mode,\n state.reValidateMode,\n state.submitCount > 0,\n true,\n )\n ? form.validateField(path)\n : undefined;\n};\n\n// Edit-then-leave, the way a user fills a form field. The common case in\n// a test that wants touched-gated error UIs to show what they would show.\n// Returns the blur's result (the later of the two passes, and the one\n// that is guaranteed to have run under every mode but onSubmit).\nexport const fillAndBlurField = <\n TSchema extends z.ZodType,\n P extends FieldPath<z.input<TSchema>, D>,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n path: P,\n value: FieldValue<z.input<TSchema>, P>,\n): FieldValidationResult | undefined => {\n fillField(form, path, value);\n return blurField(form, path);\n};\n\n// Fill several fields in one call: entries are applied in order, each\n// through the full fillField recipe. Each value is typed from its own key\n// (a mapped type over the path union), the same contract fillField has\n// per call: `fillFields(form, { age: \"x\" })` is a compile error exactly\n// as `fillField(form, \"age\", \"x\")` is. Fixtures that are genuinely\n// runtime-keyed cast at the call site.\nexport const fillFields = <\n TSchema extends z.ZodType,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n values: Readonly<{\n [P in FieldPath<z.input<TSchema>, D>]?: FieldValue<z.input<TSchema>, P>;\n }>,\n): void => {\n Object.entries(values).forEach(([path, value]) => {\n fillField(\n form,\n path as FieldPath<z.input<TSchema>, D>,\n value as never,\n );\n });\n};\n\n// Submit with a no-op handler and hand back the SubmitResult: \"valid\"\n// carries the parsed z.output data, \"invalid\" the ErrorMap (written to\n// the store and marked touched, like any failed submit), \"error\" cannot\n// occur (the handler cannot throw), and \"skipped\" means a submit was\n// already in flight. Await it and switch — the discriminant does the\n// asserting-shape work.\nexport const submitForm = <\n TSchema extends z.ZodType,\n D extends PathDepth = DefaultPathDepth,\n>(\n form: Form<TSchema, D>,\n): Promise<SubmitResult<z.output<TSchema>>> => form.submit(() => undefined);\n"]}
|
package/package.json
CHANGED
package/dist/chunk-EQIS5IVH.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
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 (typeof a === "number" && typeof b === "number") return a === b;
|
|
13
|
-
if (a instanceof Date && b instanceof Date) {
|
|
14
|
-
const [ta, tb] = [a.getTime(), b.getTime()];
|
|
15
|
-
return ta === tb || Number.isNaN(ta) && Number.isNaN(tb);
|
|
16
|
-
}
|
|
17
|
-
if (Array.isArray(a) && Array.isArray(b)) {
|
|
18
|
-
return a.length === b.length && a.every((v, i) => valuesEqual(v, b[i]));
|
|
19
|
-
}
|
|
20
|
-
if (isPlainObject(a) && isPlainObject(b)) {
|
|
21
|
-
const aKeys = Object.keys(a);
|
|
22
|
-
const bKeys = Object.keys(b);
|
|
23
|
-
return aKeys.length === bKeys.length && aKeys.every((k) => Object.hasOwn(b, k) && valuesEqual(a[k], b[k]));
|
|
24
|
-
}
|
|
25
|
-
return false;
|
|
26
|
-
};
|
|
27
|
-
var dirtyCache = /* @__PURE__ */ new WeakMap();
|
|
28
|
-
var MAX_VERDICTS = 4;
|
|
29
|
-
var isFieldDirty = (value, initialValue) => {
|
|
30
|
-
if (Object.is(value, initialValue)) return false;
|
|
31
|
-
if (typeof value !== "object" || value === null) {
|
|
32
|
-
return !valuesEqual(value, initialValue);
|
|
33
|
-
}
|
|
34
|
-
const cached = dirtyCache.get(value) ?? [];
|
|
35
|
-
const hit = cached.find((v) => Object.is(v.initial, initialValue));
|
|
36
|
-
if (hit !== void 0) return hit.dirty;
|
|
37
|
-
const dirty = !valuesEqual(value, initialValue);
|
|
38
|
-
dirtyCache.set(
|
|
39
|
-
value,
|
|
40
|
-
[{ initial: initialValue, dirty }, ...cached].slice(0, MAX_VERDICTS)
|
|
41
|
-
);
|
|
42
|
-
return dirty;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
// src/core/path.ts
|
|
46
|
-
var MAX_ARRAY_INDEX = 1e5;
|
|
47
|
-
var isIndexSegment = (s) => {
|
|
48
|
-
const n = Number(s);
|
|
49
|
-
return Number.isInteger(n) && n >= 0 && String(n) === s;
|
|
50
|
-
};
|
|
51
|
-
var PARSE_CACHE_MAX = 4096;
|
|
52
|
-
var parseCache = /* @__PURE__ */ new Map();
|
|
53
|
-
var parsePath = (path) => {
|
|
54
|
-
const cached = parseCache.get(path);
|
|
55
|
-
if (cached !== void 0) {
|
|
56
|
-
parseCache.delete(path);
|
|
57
|
-
parseCache.set(path, cached);
|
|
58
|
-
return cached;
|
|
59
|
-
}
|
|
60
|
-
const segments = Object.freeze(
|
|
61
|
-
path === "" ? [] : path.split(".").map((s) => isIndexSegment(s) ? Number(s) : s)
|
|
62
|
-
);
|
|
63
|
-
if (parseCache.size >= PARSE_CACHE_MAX) {
|
|
64
|
-
const oldest = parseCache.keys().next().value;
|
|
65
|
-
if (oldest !== void 0) parseCache.delete(oldest);
|
|
66
|
-
}
|
|
67
|
-
parseCache.set(path, segments);
|
|
68
|
-
return segments;
|
|
69
|
-
};
|
|
70
|
-
var isPlainRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
71
|
-
var readSegment = (obj, segment) => {
|
|
72
|
-
if (obj == null) return void 0;
|
|
73
|
-
if (Array.isArray(obj)) {
|
|
74
|
-
return typeof segment === "number" ? obj[segment] : void 0;
|
|
75
|
-
}
|
|
76
|
-
return isPlainRecord(obj) && Object.hasOwn(obj, String(segment)) ? obj[String(segment)] : void 0;
|
|
77
|
-
};
|
|
78
|
-
var getAtPath = (obj, path) => parsePath(path).reduce(readSegment, obj);
|
|
79
|
-
var slotAtPath = (obj, path) => {
|
|
80
|
-
const walked = parsePath(path).reduce(
|
|
81
|
-
(acc, segment) => {
|
|
82
|
-
const container = acc.current;
|
|
83
|
-
const addressable = typeof segment === "number" ? Array.isArray(container) ? segment < container.length : isPlainRecord(container) : isPlainRecord(container);
|
|
84
|
-
return !acc.ok || !addressable ? { ok: false, current: void 0 } : { ok: true, current: readSegment(container, segment) };
|
|
85
|
-
},
|
|
86
|
-
{ ok: true, current: obj }
|
|
87
|
-
);
|
|
88
|
-
return walked.ok ? { exists: true, value: walked.current } : { exists: false };
|
|
89
|
-
};
|
|
90
|
-
var hasOwnSlot = (obj, path) => parsePath(path).reduce(
|
|
91
|
-
(acc, segment) => {
|
|
92
|
-
const container = acc.current;
|
|
93
|
-
const owns = Array.isArray(container) ? typeof segment === "number" && segment < container.length : isPlainRecord(container) && Object.hasOwn(container, String(segment));
|
|
94
|
-
return !acc.ok || !owns ? { ok: false, current: void 0 } : { ok: true, current: readSegment(container, segment) };
|
|
95
|
-
},
|
|
96
|
-
{ ok: true, current: obj }
|
|
97
|
-
).ok;
|
|
98
|
-
var writeIntoArray = (arr, index, rest, value, kindAt, prefix) => {
|
|
99
|
-
const length = Math.max(arr.length, index + 1);
|
|
100
|
-
return Array.from(
|
|
101
|
-
{ length },
|
|
102
|
-
(_, i) => i === index ? writeSegments(arr[i], rest, value, kindAt, [...prefix, index]) : arr[i]
|
|
103
|
-
);
|
|
104
|
-
};
|
|
105
|
-
var refusesHugeIndex = (index) => {
|
|
106
|
-
if (index > MAX_ARRAY_INDEX) {
|
|
107
|
-
console.warn(
|
|
108
|
-
`[formstand] refusing to write array index ${index} (max ${MAX_ARRAY_INDEX}); value left unchanged.`
|
|
109
|
-
);
|
|
110
|
-
return true;
|
|
111
|
-
}
|
|
112
|
-
return false;
|
|
113
|
-
};
|
|
114
|
-
var writeSegments = (obj, segments, value, kindAt, prefix) => {
|
|
115
|
-
const [head, ...rest] = segments;
|
|
116
|
-
if (head === void 0) return value;
|
|
117
|
-
if (Array.isArray(obj)) {
|
|
118
|
-
if (typeof head !== "number") {
|
|
119
|
-
console.warn(
|
|
120
|
-
`[formstand] cannot write string key "${head}" into an array; value left unchanged.`
|
|
121
|
-
);
|
|
122
|
-
return obj;
|
|
123
|
-
}
|
|
124
|
-
if (refusesHugeIndex(head)) return obj;
|
|
125
|
-
return writeIntoArray(obj, head, rest, value, kindAt, prefix);
|
|
126
|
-
}
|
|
127
|
-
if (isPlainRecord(obj)) {
|
|
128
|
-
if (!isPlainObject(obj)) {
|
|
129
|
-
console.warn(
|
|
130
|
-
`[formstand] cannot write key "${String(head)}" through a non-plain object (Date/Map/Set/class instance); value left unchanged.`
|
|
131
|
-
);
|
|
132
|
-
return obj;
|
|
133
|
-
}
|
|
134
|
-
const key2 = String(head);
|
|
135
|
-
return {
|
|
136
|
-
...obj,
|
|
137
|
-
[key2]: writeSegments(obj[key2], rest, value, kindAt, [...prefix, key2])
|
|
138
|
-
};
|
|
139
|
-
}
|
|
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
|
-
}
|
|
148
|
-
if (refusesHugeIndex(head)) return obj;
|
|
149
|
-
return writeIntoArray([], head, rest, value, kindAt, prefix);
|
|
150
|
-
}
|
|
151
|
-
const key = String(head);
|
|
152
|
-
return { [key]: writeSegments(void 0, rest, value, kindAt, [...prefix, key]) };
|
|
153
|
-
};
|
|
154
|
-
var setAtPath = (obj, path, value, containerKindAt) => writeSegments(obj, parsePath(path), value, containerKindAt, []);
|
|
155
|
-
function useFormSelector(form, selector) {
|
|
156
|
-
return useStore(form.store, selector);
|
|
157
|
-
}
|
|
158
|
-
function useFormSelectorShallow(form, selector) {
|
|
159
|
-
return useStore(form.store, useShallow(selector));
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// src/core/env.ts
|
|
163
|
-
var isDevBuild = () => typeof process === "undefined" || process.env?.["NODE_ENV"] !== "production";
|
|
164
|
-
var isDevToolsBuild = () => {
|
|
165
|
-
if (typeof process === "undefined") return false;
|
|
166
|
-
const env = process.env?.["NODE_ENV"];
|
|
167
|
-
return typeof env === "string" && env !== "production";
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
export { getAtPath, hasOwnSlot, isDevBuild, isDevToolsBuild, isFieldDirty, isPlainObject, parsePath, setAtPath, slotAtPath, useFormSelector, useFormSelectorShallow, valuesEqual };
|
|
171
|
-
//# sourceMappingURL=chunk-EQIS5IVH.js.map
|
|
172
|
-
//# sourceMappingURL=chunk-EQIS5IVH.js.map
|
|
@@ -1 +0,0 @@
|
|
|
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"]}
|