formstand 0.26.0 → 0.26.1
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 +93 -0
- package/dist/{createForm-RE6Bjnfv.d.cts → createForm-BXDd9Tg4.d.cts} +1 -1
- package/dist/{createForm-RE6Bjnfv.d.ts → createForm-BXDd9Tg4.d.ts} +1 -1
- package/dist/devtools/index.cjs +11 -3
- package/dist/devtools/index.cjs.map +1 -1
- package/dist/devtools/index.js +11 -3
- package/dist/devtools/index.js.map +1 -1
- package/dist/index.cjs +233 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -10
- package/dist/index.d.ts +19 -10
- package/dist/index.js +233 -82
- package/dist/index.js.map +1 -1
- package/dist/testing/index.cjs +2 -2
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.js +2 -2
- package/dist/testing/index.js.map +1 -1
- package/package.json +1 -1
package/dist/testing/index.cjs
CHANGED
|
@@ -46,8 +46,8 @@ var blurField = (form, path) => {
|
|
|
46
46
|
) ? form.validateField(path) : void 0;
|
|
47
47
|
};
|
|
48
48
|
var fillAndBlurField = (form, path, value) => {
|
|
49
|
-
fillField(form, path, value);
|
|
50
|
-
return blurField(form, path);
|
|
49
|
+
const filled = fillField(form, path, value);
|
|
50
|
+
return blurField(form, path) ?? filled;
|
|
51
51
|
};
|
|
52
52
|
var fillFields = (form, values) => {
|
|
53
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;;;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;
|
|
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;AASO,IAAM,gBAAA,GAAmB,CAK9B,IAAA,EACA,IAAA,EACA,KAAA,KACsC;AACtC,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,IAAA,EAAM,IAAA,EAAM,KAAK,CAAA;AAC1C,EAAA,OAAO,SAAA,CAAU,IAAA,EAAM,IAAI,CAAA,IAAK,MAAA;AAClC;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) when the blur\n// gate validated, else the fill's: under mode \"onChange\", and after a\n// submit with the default reValidateMode, the fill runs the pass and the\n// blur does not, and a test awaiting an async rule needs that pending\n// result rather than undefined.\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 const filled = fillField(form, path, value);\n return blurField(form, path) ?? filled;\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.js
CHANGED
|
@@ -26,8 +26,8 @@ var blurField = (form, path) => {
|
|
|
26
26
|
) ? form.validateField(path) : void 0;
|
|
27
27
|
};
|
|
28
28
|
var fillAndBlurField = (form, path, value) => {
|
|
29
|
-
fillField(form, path, value);
|
|
30
|
-
return blurField(form, path);
|
|
29
|
+
const filled = fillField(form, path, value);
|
|
30
|
+
return blurField(form, path) ?? filled;
|
|
31
31
|
};
|
|
32
32
|
var fillFields = (form, values) => {
|
|
33
33
|
Object.entries(values).forEach(([path, value]) => {
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AASO,IAAM,gBAAA,GAAmB,CAK9B,IAAA,EACA,IAAA,EACA,KAAA,KACsC;AACtC,EAAA,MAAM,MAAA,GAAS,SAAA,CAAU,IAAA,EAAM,IAAA,EAAM,KAAK,CAAA;AAC1C,EAAA,OAAO,SAAA,CAAU,IAAA,EAAM,IAAI,CAAA,IAAK,MAAA;AAClC;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) when the blur\n// gate validated, else the fill's: under mode \"onChange\", and after a\n// submit with the default reValidateMode, the fill runs the pass and the\n// blur does not, and a test awaiting an async rule needs that pending\n// result rather than undefined.\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 const filled = fillField(form, path, value);\n return blurField(form, path) ?? filled;\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"]}
|