formstand 0.18.0 → 0.20.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,73 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.20.0 — 2026-09-18
4
+
5
+ ### Added
6
+
7
+ - **`useMaskedInput(field, { parse, format })`.** The raw-text editing
8
+ pattern behind `useNumberInput`, generalized to any parsed/formatted
9
+ value (phone, currency, locale formats): partial entries stay visible
10
+ as local text without touching the form, keystrokes that parse push
11
+ the REAL value immediately (the schema validates data, never display
12
+ text), `"empty"` writes the field's schema-aware `emptyValue`, blur
13
+ snaps the display to `format(value)`, and an external write wins over
14
+ local text. Recipes gains a phone-mask walkthrough.
15
+ - **`formstand/testing`: drive a form in tests the way the hooks do.**
16
+ `fillField` writes and validates under the CHANGE gate (no-op guard
17
+ included), `blurField` marks touched and validates under the BLUR
18
+ gate, `fillAndBlurField` is the edit-then-leave a user performs,
19
+ `fillFields` applies a fixture object through the full recipe, and
20
+ `submitForm` hands back the `SubmitResult` to switch on. Tests using
21
+ the imperative `form.setValue` were skipping the validation gates by
22
+ design; these helpers restate `useField`'s own recipes over the core
23
+ API, so a store-level test sees exactly what a rendered input would —
24
+ without mounting one. Framework-agnostic; new Testing forms guide.
25
+ - **Variant sub-paths: `useVariantField`'s field argument is a PATH.**
26
+ `useVariantField(form, "payment", "billing.zip")` reaches inside a
27
+ variant-only object and `"span.0"` binds a variant-only tuple
28
+ position — the last shape the typed paths could not carry. The field
29
+ offer is `VariantFieldPath` (exported): every member's `FieldPath`,
30
+ minus paths rooted at a common key (those bind with plain `useField`,
31
+ the discriminant included), riding the same leaf classification,
32
+ tuple positionality, and depth budget as everywhere else. The value
33
+ distributes over the members that resolve the path, widened with
34
+ `| undefined` as before; `VariantFieldValue` now derives through
35
+ `FieldValue`, and the runtime is unchanged (the joined path always
36
+ worked — only its type was unreachable).
37
+
38
+ - **`useFormSteps(form, steps)`: the multi-step story.** One form, one
39
+ schema, and per-step validation scopes: `next()` validates the current
40
+ step's fields (async refines included; the full schema parses, so
41
+ cross-field rules inside a step fire, with errors scoped to the step)
42
+ and advances only on clean, marking errored fields touched exactly
43
+ like a failed submit; `back()` never validates; a forward `goTo()`
44
+ validates every step on the way and lands on the first one that
45
+ fails, so clickable step headers cannot skip past an invalid step.
46
+ `steps` is index-aligned live status (`name`, `visited`, `hasErrors`)
47
+ for progress UIs. The step index is React state on purpose — it is
48
+ navigation, not form data. `createFormHooks` binds it as
49
+ `use{Name}Steps`, and the docs gain a Multi-step forms guide.
50
+
51
+ ## 0.19.0 — 2026-09-17
52
+
53
+ ### Added
54
+
55
+ - **The React 19 form-actions bridge.** `useFormAction(form, onValid,
56
+ options?)` returns an action for `<form action={...}>` that validates
57
+ first and hands the handler parsed `z.output` data (plus the raw
58
+ `FormData` last, for file pickers and uncontrolled co-inputs) under
59
+ the full submit lifecycle — isSubmitting/submitCount, errors written
60
+ and marked touched on failure, `onInvalid`/`onError` mirroring
61
+ `handleSubmit`, the same dev warning when a failure resolves
62
+ unobserved, and a reference-stable identity. `useFormActionState(form,
63
+ action, initialState, options?)` is `useActionState` with
64
+ schema-validated data: the handler's `(prevState, data, formData)`
65
+ shape lets a `"use server"` function slot straight in, and invalid,
66
+ skipped, or throwing submissions keep the previous state, because
67
+ field errors already live in the form store. Client-side integration
68
+ by design: no-JS progressive enhancement bypasses any client wrapper,
69
+ so that path validates server-side with the same zod schema.
70
+
3
71
  ## 0.18.0 — 2026-09-17
4
72
 
5
73
  ### Changed
@@ -30,6 +98,21 @@
30
98
  back to the null-initial heuristic. **`isClearableSchema(schema)`** is
31
99
  the exported rule behind it, next to `emptyValueForSchema`.
32
100
 
101
+ ## formstand-cli Unreleased
102
+
103
+ ### Added
104
+
105
+ - **Containers inside union variants generate.** The generator's last
106
+ TODO class: an object or tuple nested in a variant flattens to its
107
+ scalar leaves, each bound as a dotted variant sub-path
108
+ (`useVariantField(form, path, "billing.zip")`, formstand 0.20+) — in
109
+ both layouts, every emission site (static unions, union rows, union
110
+ row-object fields, unions under nested arrays), with joined labels
111
+ ("Billing Zip") and the kit number-props hoists riding along. Depth
112
+ is judged on the full bound path (a too-deep leaf keeps the depth
113
+ TODO), and an array or union inside a variant still degrades — their
114
+ bindings need hooks and row machinery of their own.
115
+
33
116
  ## formstand-cli 0.18.0 — 2026-09-17
34
117
 
35
118
  ### Changed
@@ -0,0 +1,20 @@
1
+ // src/core/mode.ts
2
+ var shouldValidateOn = (trigger, mode, reValidateMode, submitAttempted, touched = false) => {
3
+ const effective = submitAttempted ? reValidateMode : mode;
4
+ switch (effective) {
5
+ case "onChange":
6
+ return trigger === "change";
7
+ case "onBlur":
8
+ return trigger === "blur";
9
+ case "onSubmit":
10
+ return false;
11
+ case "onTouched":
12
+ return trigger === "blur" || touched;
13
+ case "all":
14
+ return true;
15
+ }
16
+ };
17
+
18
+ export { shouldValidateOn };
19
+ //# sourceMappingURL=chunk-MQ6262S3.js.map
20
+ //# sourceMappingURL=chunk-MQ6262S3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/mode.ts"],"names":[],"mappings":";AAWO,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","file":"chunk-MQ6262S3.js","sourcesContent":["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"]}
package/dist/index.cjs CHANGED
@@ -4,9 +4,9 @@ var vanilla = require('zustand/vanilla');
4
4
  var middleware = require('zustand/middleware');
5
5
  var zod = require('zod');
6
6
  var react = require('react');
7
- var jsxRuntime = require('react/jsx-runtime');
8
7
  var react$1 = require('zustand/react');
9
8
  var shallow = require('zustand/react/shallow');
9
+ var jsxRuntime = require('react/jsx-runtime');
10
10
 
11
11
  // src/core/createForm.ts
12
12
 
@@ -1317,6 +1317,286 @@ var useForm = (schema, options) => {
1317
1317
  }, [form, schema, options.initialValues]);
1318
1318
  return form;
1319
1319
  };
1320
+
1321
+ // src/react/inputProps.ts
1322
+ var hasFieldError = (error) => error !== void 0 && error.length > 0;
1323
+ var ariaInvalid = (field) => hasFieldError(field.error) ? true : void 0;
1324
+ var numberToInputText = (value) => value === void 0 || value === null || Number.isNaN(value) ? "" : String(value);
1325
+ var parseNumberText = (text) => {
1326
+ if (text.trim() === "") return { kind: "empty" };
1327
+ const parsed = Number(text);
1328
+ return Number.isFinite(parsed) ? { kind: "number", value: parsed } : { kind: "invalid" };
1329
+ };
1330
+ var textInputProps = (field) => ({
1331
+ name: field.path,
1332
+ value: field.value ?? "",
1333
+ "aria-invalid": ariaInvalid(field),
1334
+ onChange: (e) => {
1335
+ const text = e.target.value;
1336
+ field.setValue(text === "" && field.clearable ? field.emptyValue : text);
1337
+ },
1338
+ onBlur: field.onBlur
1339
+ });
1340
+ var numberInputProps = (field) => ({
1341
+ type: "number",
1342
+ name: field.path,
1343
+ value: numberToInputText(field.value),
1344
+ "aria-invalid": ariaInvalid(field),
1345
+ onChange: (e) => {
1346
+ const parsed = parseNumberText(e.target.value);
1347
+ if (parsed.kind === "invalid") return;
1348
+ field.setValue(
1349
+ parsed.kind === "number" ? parsed.value : field.emptyValue
1350
+ );
1351
+ },
1352
+ onBlur: field.onBlur
1353
+ });
1354
+ var dateToInputText = (value) => {
1355
+ if (value === void 0 || value === null || Number.isNaN(value.getTime())) {
1356
+ return "";
1357
+ }
1358
+ const pad = (part) => String(part).padStart(2, "0");
1359
+ return `${String(value.getFullYear()).padStart(4, "0")}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
1360
+ };
1361
+ var parseDateText = (text) => {
1362
+ if (text.trim() === "") return { kind: "empty" };
1363
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
1364
+ if (match === null) return { kind: "invalid" };
1365
+ const [, year, month, day] = match;
1366
+ const y = Number(year);
1367
+ const date = new Date(y, Number(month) - 1, Number(day));
1368
+ if (y < 100) date.setFullYear(y);
1369
+ return date.getFullYear() === y && date.getMonth() === Number(month) - 1 && date.getDate() === Number(day) ? { kind: "date", value: date } : { kind: "invalid" };
1370
+ };
1371
+ var dateInputProps = (field) => ({
1372
+ type: "date",
1373
+ name: field.path,
1374
+ value: dateToInputText(field.value),
1375
+ "aria-invalid": ariaInvalid(field),
1376
+ onChange: (e) => {
1377
+ const parsed = parseDateText(e.target.value);
1378
+ if (parsed.kind !== "date") {
1379
+ field.setValue(field.emptyValue);
1380
+ return;
1381
+ }
1382
+ const current = field.value;
1383
+ const next = current instanceof Date && !Number.isNaN(current.getTime()) ? new Date(
1384
+ parsed.value.getFullYear(),
1385
+ parsed.value.getMonth(),
1386
+ parsed.value.getDate(),
1387
+ current.getHours(),
1388
+ current.getMinutes(),
1389
+ current.getSeconds(),
1390
+ current.getMilliseconds()
1391
+ ) : parsed.value;
1392
+ field.setValue(next);
1393
+ },
1394
+ onBlur: field.onBlur
1395
+ });
1396
+ var checkboxProps = (field) => ({
1397
+ type: "checkbox",
1398
+ name: field.path,
1399
+ checked: field.value ?? false,
1400
+ "aria-invalid": ariaInvalid(field),
1401
+ onChange: (e) => field.setValue(e.target.checked),
1402
+ onBlur: field.onBlur
1403
+ });
1404
+ var selectProps = (field) => ({
1405
+ name: field.path,
1406
+ value: field.value ?? "",
1407
+ "aria-invalid": ariaInvalid(field),
1408
+ onChange: (e) => {
1409
+ const v = e.target.value;
1410
+ field.setValue(v === "" && field.clearable ? field.emptyValue : v);
1411
+ },
1412
+ onBlur: field.onBlur
1413
+ });
1414
+
1415
+ // src/react/useMaskedInput.ts
1416
+ var useMaskedInput = (field, options) => {
1417
+ const [edit, setEdit] = react.useState({
1418
+ raw: null,
1419
+ pushed: void 0
1420
+ });
1421
+ const externallyChanged = edit.raw !== null && !Object.is(field.value, edit.pushed);
1422
+ if (externallyChanged) {
1423
+ setEdit({ raw: null, pushed: void 0 });
1424
+ }
1425
+ const raw = externallyChanged ? null : edit.raw;
1426
+ return {
1427
+ name: field.path,
1428
+ value: raw ?? (field.value === void 0 || field.value === null ? "" : options.format(field.value)),
1429
+ "aria-invalid": hasFieldError(field.error) ? true : void 0,
1430
+ onChange: (e) => {
1431
+ const text = e.target.value;
1432
+ const parsed = options.parse(text);
1433
+ switch (parsed.kind) {
1434
+ case "value":
1435
+ setEdit({ raw: text, pushed: parsed.value });
1436
+ field.setValue(parsed.value);
1437
+ return;
1438
+ case "empty": {
1439
+ const empty = field.emptyValue;
1440
+ setEdit({ raw: text, pushed: empty });
1441
+ field.setValue(empty);
1442
+ return;
1443
+ }
1444
+ case "invalid":
1445
+ setEdit({ raw: text, pushed: field.value });
1446
+ return;
1447
+ }
1448
+ },
1449
+ onBlur: () => {
1450
+ setEdit({ raw: null, pushed: void 0 });
1451
+ field.onBlur();
1452
+ }
1453
+ };
1454
+ };
1455
+ function useFormSelector(form, selector) {
1456
+ return react$1.useStore(form.store, selector);
1457
+ }
1458
+ function useFormSelectorShallow(form, selector) {
1459
+ return react$1.useStore(form.store, shallow.useShallow(selector));
1460
+ }
1461
+
1462
+ // src/react/useFormSteps.ts
1463
+ var useFormSteps = (form, steps) => {
1464
+ const [step, setStep] = react.useState(0);
1465
+ const [visited, setVisited] = react.useState(
1466
+ () => /* @__PURE__ */ new Set([0])
1467
+ );
1468
+ const latest = react.useRef(steps);
1469
+ react.useEffect(() => {
1470
+ latest.current = steps;
1471
+ });
1472
+ const validateStep = react.useCallback(
1473
+ async (index) => {
1474
+ const definition = latest.current[index];
1475
+ if (definition === void 0) return true;
1476
+ const result = await form.validateFieldsAsync(definition.fields);
1477
+ if (result.kind === "invalid") {
1478
+ Object.keys(result.errors).forEach((key) => {
1479
+ form.setTouched(key, true);
1480
+ });
1481
+ return false;
1482
+ }
1483
+ return true;
1484
+ },
1485
+ [form]
1486
+ );
1487
+ const next = react.useCallback(async () => {
1488
+ const ok = await validateStep(step);
1489
+ if (!ok) return false;
1490
+ const target = Math.min(step + 1, latest.current.length - 1);
1491
+ setStep(target);
1492
+ setVisited((prev) => /* @__PURE__ */ new Set([...prev, target]));
1493
+ return true;
1494
+ }, [validateStep, step]);
1495
+ const back = react.useCallback(() => {
1496
+ setStep((prev) => Math.max(prev - 1, 0));
1497
+ }, []);
1498
+ const goTo = react.useCallback(
1499
+ async (target) => {
1500
+ const count = latest.current.length;
1501
+ const clamped = Math.max(0, Math.min(target, count - 1));
1502
+ if (clamped <= step) {
1503
+ setStep(clamped);
1504
+ return clamped;
1505
+ }
1506
+ const walk = async (from) => {
1507
+ if (from >= clamped) return clamped;
1508
+ return await validateStep(from) ? walk(from + 1) : from;
1509
+ };
1510
+ const landed = await walk(step);
1511
+ setStep(landed);
1512
+ setVisited(
1513
+ (prev) => /* @__PURE__ */ new Set([
1514
+ ...prev,
1515
+ ...Array.from({ length: landed - step + 1 }, (_, i) => step + i)
1516
+ ])
1517
+ );
1518
+ return landed;
1519
+ },
1520
+ [validateStep, step]
1521
+ );
1522
+ const errorFlags = useFormSelectorShallow(
1523
+ form,
1524
+ (state) => steps.map(
1525
+ (definition) => definition.fields.some(
1526
+ (path) => Object.keys(state.errors).some((key) => isPathOrChild(key, path))
1527
+ )
1528
+ )
1529
+ );
1530
+ const statuses = react.useMemo(
1531
+ () => steps.map(
1532
+ (definition, index) => ({
1533
+ name: definition.name,
1534
+ visited: visited.has(index),
1535
+ hasErrors: errorFlags[index] ?? false
1536
+ })
1537
+ ),
1538
+ [steps, visited, errorFlags]
1539
+ );
1540
+ return react.useMemo(
1541
+ () => ({
1542
+ step,
1543
+ name: steps[step]?.name,
1544
+ count: steps.length,
1545
+ isFirst: step === 0,
1546
+ isLast: step === steps.length - 1,
1547
+ next,
1548
+ back,
1549
+ goTo,
1550
+ steps: statuses
1551
+ }),
1552
+ [step, steps, next, back, goTo, statuses]
1553
+ );
1554
+ };
1555
+ var warnUnobserved = (result, options, site) => {
1556
+ if (result.kind === "error" && options?.onError === void 0 && process.env.NODE_ENV !== "production") {
1557
+ console.warn(
1558
+ `[formstand] action handler threw and nothing observed it \u2014 the form shows no feedback. Pass { onError } to ${site} (or setError inside your handler):`,
1559
+ result.error
1560
+ );
1561
+ }
1562
+ };
1563
+ var useFormAction = (form, onValid, options) => {
1564
+ const latest = react.useRef({ onValid, options });
1565
+ react.useEffect(() => {
1566
+ latest.current = { onValid, options };
1567
+ });
1568
+ return react.useCallback(
1569
+ async (formData) => {
1570
+ const { onValid: handler, options: opts } = latest.current;
1571
+ const result = await form.submit(
1572
+ (data) => handler(data, formData),
1573
+ opts?.onInvalid,
1574
+ opts?.onError === void 0 ? void 0 : { onError: opts.onError }
1575
+ );
1576
+ warnUnobserved(result, opts, "useFormAction");
1577
+ },
1578
+ [form]
1579
+ );
1580
+ };
1581
+ var useFormActionState = (form, action, initialState, options) => {
1582
+ const [state, formAction, isPending] = react.useActionState(
1583
+ async (prevState, formData) => {
1584
+ const prev = prevState;
1585
+ const nextRef = { current: prev };
1586
+ const result = await form.submit(
1587
+ async (data) => {
1588
+ nextRef.current = await action(prev, data, formData);
1589
+ },
1590
+ options?.onInvalid,
1591
+ options?.onError === void 0 ? void 0 : { onError: options.onError }
1592
+ );
1593
+ warnUnobserved(result, options, "useFormActionState");
1594
+ return nextRef.current;
1595
+ },
1596
+ initialState
1597
+ );
1598
+ return [state, formAction, isPending];
1599
+ };
1320
1600
  var createFormContext = () => {
1321
1601
  const Context = react.createContext(null);
1322
1602
  const Provider = ({ form, children }) => /* @__PURE__ */ jsxRuntime.jsx(Context.Provider, { value: form, children });
@@ -1740,12 +2020,6 @@ function useIsValidating(form, path) {
1740
2020
  }
1741
2021
  var useIsSubmitting = (form) => react$1.useStore(form.store, (state) => state.isSubmitting);
1742
2022
  var useSubmitCount = (form) => react$1.useStore(form.store, (state) => state.submitCount);
1743
- function useFormSelector(form, selector) {
1744
- return react$1.useStore(form.store, selector);
1745
- }
1746
- function useFormSelectorShallow(form, selector) {
1747
- return react$1.useStore(form.store, shallow.useShallow(selector));
1748
- }
1749
2023
 
1750
2024
  // src/react/createFormHooks.ts
1751
2025
  var capitalize = (name) => name.length === 0 ? name : name.charAt(0).toUpperCase() + name.slice(1);
@@ -1771,6 +2045,7 @@ var createFormHooks = (form, name) => {
1771
2045
  paths
1772
2046
  );
1773
2047
  const useBoundSubmitCount = () => useSubmitCount(structural);
2048
+ const useBoundSteps = (steps) => useFormSteps(form, steps);
1774
2049
  const prefix = capitalize(name ?? "");
1775
2050
  return {
1776
2051
  [`use${prefix}Field`]: useBoundField,
@@ -1785,103 +2060,10 @@ var createFormHooks = (form, name) => {
1785
2060
  [`use${prefix}IsSubmitting`]: useBoundIsSubmitting,
1786
2061
  [`use${prefix}SubmitCount`]: useBoundSubmitCount,
1787
2062
  [`use${prefix}Values`]: useBoundValues,
1788
- [`use${prefix}Fields`]: useBoundFields
2063
+ [`use${prefix}Fields`]: useBoundFields,
2064
+ [`use${prefix}Steps`]: useBoundSteps
1789
2065
  };
1790
2066
  };
1791
-
1792
- // src/react/inputProps.ts
1793
- var hasFieldError = (error) => error !== void 0 && error.length > 0;
1794
- var ariaInvalid = (field) => hasFieldError(field.error) ? true : void 0;
1795
- var numberToInputText = (value) => value === void 0 || value === null || Number.isNaN(value) ? "" : String(value);
1796
- var parseNumberText = (text) => {
1797
- if (text.trim() === "") return { kind: "empty" };
1798
- const parsed = Number(text);
1799
- return Number.isFinite(parsed) ? { kind: "number", value: parsed } : { kind: "invalid" };
1800
- };
1801
- var textInputProps = (field) => ({
1802
- name: field.path,
1803
- value: field.value ?? "",
1804
- "aria-invalid": ariaInvalid(field),
1805
- onChange: (e) => {
1806
- const text = e.target.value;
1807
- field.setValue(text === "" && field.clearable ? field.emptyValue : text);
1808
- },
1809
- onBlur: field.onBlur
1810
- });
1811
- var numberInputProps = (field) => ({
1812
- type: "number",
1813
- name: field.path,
1814
- value: numberToInputText(field.value),
1815
- "aria-invalid": ariaInvalid(field),
1816
- onChange: (e) => {
1817
- const parsed = parseNumberText(e.target.value);
1818
- if (parsed.kind === "invalid") return;
1819
- field.setValue(
1820
- parsed.kind === "number" ? parsed.value : field.emptyValue
1821
- );
1822
- },
1823
- onBlur: field.onBlur
1824
- });
1825
- var dateToInputText = (value) => {
1826
- if (value === void 0 || value === null || Number.isNaN(value.getTime())) {
1827
- return "";
1828
- }
1829
- const pad = (part) => String(part).padStart(2, "0");
1830
- return `${String(value.getFullYear()).padStart(4, "0")}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
1831
- };
1832
- var parseDateText = (text) => {
1833
- if (text.trim() === "") return { kind: "empty" };
1834
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
1835
- if (match === null) return { kind: "invalid" };
1836
- const [, year, month, day] = match;
1837
- const y = Number(year);
1838
- const date = new Date(y, Number(month) - 1, Number(day));
1839
- if (y < 100) date.setFullYear(y);
1840
- return date.getFullYear() === y && date.getMonth() === Number(month) - 1 && date.getDate() === Number(day) ? { kind: "date", value: date } : { kind: "invalid" };
1841
- };
1842
- var dateInputProps = (field) => ({
1843
- type: "date",
1844
- name: field.path,
1845
- value: dateToInputText(field.value),
1846
- "aria-invalid": ariaInvalid(field),
1847
- onChange: (e) => {
1848
- const parsed = parseDateText(e.target.value);
1849
- if (parsed.kind !== "date") {
1850
- field.setValue(field.emptyValue);
1851
- return;
1852
- }
1853
- const current = field.value;
1854
- const next = current instanceof Date && !Number.isNaN(current.getTime()) ? new Date(
1855
- parsed.value.getFullYear(),
1856
- parsed.value.getMonth(),
1857
- parsed.value.getDate(),
1858
- current.getHours(),
1859
- current.getMinutes(),
1860
- current.getSeconds(),
1861
- current.getMilliseconds()
1862
- ) : parsed.value;
1863
- field.setValue(next);
1864
- },
1865
- onBlur: field.onBlur
1866
- });
1867
- var checkboxProps = (field) => ({
1868
- type: "checkbox",
1869
- name: field.path,
1870
- checked: field.value ?? false,
1871
- "aria-invalid": ariaInvalid(field),
1872
- onChange: (e) => field.setValue(e.target.checked),
1873
- onBlur: field.onBlur
1874
- });
1875
- var selectProps = (field) => ({
1876
- name: field.path,
1877
- value: field.value ?? "",
1878
- "aria-invalid": ariaInvalid(field),
1879
- onChange: (e) => {
1880
- const v = e.target.value;
1881
- field.setValue(v === "" && field.clearable ? field.emptyValue : v);
1882
- },
1883
- onBlur: field.onBlur
1884
- });
1885
2067
  var useFieldA11y = (error) => {
1886
2068
  const id = react.useId();
1887
2069
  const errorId = `${id}-error`;
@@ -2207,14 +2389,18 @@ exports.useField = useField;
2207
2389
  exports.useFieldArray = useFieldArray;
2208
2390
  exports.useFields = useFields;
2209
2391
  exports.useForm = useForm;
2392
+ exports.useFormAction = useFormAction;
2393
+ exports.useFormActionState = useFormActionState;
2210
2394
  exports.useFormError = useFormError;
2211
2395
  exports.useFormSelector = useFormSelector;
2212
2396
  exports.useFormSelectorShallow = useFormSelectorShallow;
2397
+ exports.useFormSteps = useFormSteps;
2213
2398
  exports.useFormValues = useFormValues;
2214
2399
  exports.useIsDirty = useIsDirty;
2215
2400
  exports.useIsSubmitting = useIsSubmitting;
2216
2401
  exports.useIsValid = useIsValid;
2217
2402
  exports.useIsValidating = useIsValidating;
2403
+ exports.useMaskedInput = useMaskedInput;
2218
2404
  exports.useNumberInput = useNumberInput;
2219
2405
  exports.usePersistForm = usePersistForm;
2220
2406
  exports.useSubmitCount = useSubmitCount;