formstand 0.19.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 +63 -0
- package/dist/chunk-MQ6262S3.js +20 -0
- package/dist/chunk-MQ6262S3.js.map +1 -0
- package/dist/index.cjs +241 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +71 -25
- package/dist/index.d.ts +71 -25
- package/dist/index.js +235 -115
- package/dist/index.js.map +1 -1
- package/dist/testing/index.cjs +70 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +11 -0
- package/dist/testing/index.d.ts +11 -0
- package/dist/testing/index.js +49 -0
- package/dist/testing/index.js.map +1 -0
- package/package.json +11 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
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
|
+
|
|
3
51
|
## 0.19.0 — 2026-09-17
|
|
4
52
|
|
|
5
53
|
### Added
|
|
@@ -50,6 +98,21 @@
|
|
|
50
98
|
back to the null-initial heuristic. **`isClearableSchema(schema)`** is
|
|
51
99
|
the exported rule behind it, next to `emptyValueForSchema`.
|
|
52
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
|
+
|
|
53
116
|
## formstand-cli 0.18.0 — 2026-09-17
|
|
54
117
|
|
|
55
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,241 @@ 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
|
+
};
|
|
1320
1555
|
var warnUnobserved = (result, options, site) => {
|
|
1321
1556
|
if (result.kind === "error" && options?.onError === void 0 && process.env.NODE_ENV !== "production") {
|
|
1322
1557
|
console.warn(
|
|
@@ -1785,12 +2020,6 @@ function useIsValidating(form, path) {
|
|
|
1785
2020
|
}
|
|
1786
2021
|
var useIsSubmitting = (form) => react$1.useStore(form.store, (state) => state.isSubmitting);
|
|
1787
2022
|
var useSubmitCount = (form) => react$1.useStore(form.store, (state) => state.submitCount);
|
|
1788
|
-
function useFormSelector(form, selector) {
|
|
1789
|
-
return react$1.useStore(form.store, selector);
|
|
1790
|
-
}
|
|
1791
|
-
function useFormSelectorShallow(form, selector) {
|
|
1792
|
-
return react$1.useStore(form.store, shallow.useShallow(selector));
|
|
1793
|
-
}
|
|
1794
2023
|
|
|
1795
2024
|
// src/react/createFormHooks.ts
|
|
1796
2025
|
var capitalize = (name) => name.length === 0 ? name : name.charAt(0).toUpperCase() + name.slice(1);
|
|
@@ -1816,6 +2045,7 @@ var createFormHooks = (form, name) => {
|
|
|
1816
2045
|
paths
|
|
1817
2046
|
);
|
|
1818
2047
|
const useBoundSubmitCount = () => useSubmitCount(structural);
|
|
2048
|
+
const useBoundSteps = (steps) => useFormSteps(form, steps);
|
|
1819
2049
|
const prefix = capitalize(name ?? "");
|
|
1820
2050
|
return {
|
|
1821
2051
|
[`use${prefix}Field`]: useBoundField,
|
|
@@ -1830,103 +2060,10 @@ var createFormHooks = (form, name) => {
|
|
|
1830
2060
|
[`use${prefix}IsSubmitting`]: useBoundIsSubmitting,
|
|
1831
2061
|
[`use${prefix}SubmitCount`]: useBoundSubmitCount,
|
|
1832
2062
|
[`use${prefix}Values`]: useBoundValues,
|
|
1833
|
-
[`use${prefix}Fields`]: useBoundFields
|
|
2063
|
+
[`use${prefix}Fields`]: useBoundFields,
|
|
2064
|
+
[`use${prefix}Steps`]: useBoundSteps
|
|
1834
2065
|
};
|
|
1835
2066
|
};
|
|
1836
|
-
|
|
1837
|
-
// src/react/inputProps.ts
|
|
1838
|
-
var hasFieldError = (error) => error !== void 0 && error.length > 0;
|
|
1839
|
-
var ariaInvalid = (field) => hasFieldError(field.error) ? true : void 0;
|
|
1840
|
-
var numberToInputText = (value) => value === void 0 || value === null || Number.isNaN(value) ? "" : String(value);
|
|
1841
|
-
var parseNumberText = (text) => {
|
|
1842
|
-
if (text.trim() === "") return { kind: "empty" };
|
|
1843
|
-
const parsed = Number(text);
|
|
1844
|
-
return Number.isFinite(parsed) ? { kind: "number", value: parsed } : { kind: "invalid" };
|
|
1845
|
-
};
|
|
1846
|
-
var textInputProps = (field) => ({
|
|
1847
|
-
name: field.path,
|
|
1848
|
-
value: field.value ?? "",
|
|
1849
|
-
"aria-invalid": ariaInvalid(field),
|
|
1850
|
-
onChange: (e) => {
|
|
1851
|
-
const text = e.target.value;
|
|
1852
|
-
field.setValue(text === "" && field.clearable ? field.emptyValue : text);
|
|
1853
|
-
},
|
|
1854
|
-
onBlur: field.onBlur
|
|
1855
|
-
});
|
|
1856
|
-
var numberInputProps = (field) => ({
|
|
1857
|
-
type: "number",
|
|
1858
|
-
name: field.path,
|
|
1859
|
-
value: numberToInputText(field.value),
|
|
1860
|
-
"aria-invalid": ariaInvalid(field),
|
|
1861
|
-
onChange: (e) => {
|
|
1862
|
-
const parsed = parseNumberText(e.target.value);
|
|
1863
|
-
if (parsed.kind === "invalid") return;
|
|
1864
|
-
field.setValue(
|
|
1865
|
-
parsed.kind === "number" ? parsed.value : field.emptyValue
|
|
1866
|
-
);
|
|
1867
|
-
},
|
|
1868
|
-
onBlur: field.onBlur
|
|
1869
|
-
});
|
|
1870
|
-
var dateToInputText = (value) => {
|
|
1871
|
-
if (value === void 0 || value === null || Number.isNaN(value.getTime())) {
|
|
1872
|
-
return "";
|
|
1873
|
-
}
|
|
1874
|
-
const pad = (part) => String(part).padStart(2, "0");
|
|
1875
|
-
return `${String(value.getFullYear()).padStart(4, "0")}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
|
|
1876
|
-
};
|
|
1877
|
-
var parseDateText = (text) => {
|
|
1878
|
-
if (text.trim() === "") return { kind: "empty" };
|
|
1879
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
|
1880
|
-
if (match === null) return { kind: "invalid" };
|
|
1881
|
-
const [, year, month, day] = match;
|
|
1882
|
-
const y = Number(year);
|
|
1883
|
-
const date = new Date(y, Number(month) - 1, Number(day));
|
|
1884
|
-
if (y < 100) date.setFullYear(y);
|
|
1885
|
-
return date.getFullYear() === y && date.getMonth() === Number(month) - 1 && date.getDate() === Number(day) ? { kind: "date", value: date } : { kind: "invalid" };
|
|
1886
|
-
};
|
|
1887
|
-
var dateInputProps = (field) => ({
|
|
1888
|
-
type: "date",
|
|
1889
|
-
name: field.path,
|
|
1890
|
-
value: dateToInputText(field.value),
|
|
1891
|
-
"aria-invalid": ariaInvalid(field),
|
|
1892
|
-
onChange: (e) => {
|
|
1893
|
-
const parsed = parseDateText(e.target.value);
|
|
1894
|
-
if (parsed.kind !== "date") {
|
|
1895
|
-
field.setValue(field.emptyValue);
|
|
1896
|
-
return;
|
|
1897
|
-
}
|
|
1898
|
-
const current = field.value;
|
|
1899
|
-
const next = current instanceof Date && !Number.isNaN(current.getTime()) ? new Date(
|
|
1900
|
-
parsed.value.getFullYear(),
|
|
1901
|
-
parsed.value.getMonth(),
|
|
1902
|
-
parsed.value.getDate(),
|
|
1903
|
-
current.getHours(),
|
|
1904
|
-
current.getMinutes(),
|
|
1905
|
-
current.getSeconds(),
|
|
1906
|
-
current.getMilliseconds()
|
|
1907
|
-
) : parsed.value;
|
|
1908
|
-
field.setValue(next);
|
|
1909
|
-
},
|
|
1910
|
-
onBlur: field.onBlur
|
|
1911
|
-
});
|
|
1912
|
-
var checkboxProps = (field) => ({
|
|
1913
|
-
type: "checkbox",
|
|
1914
|
-
name: field.path,
|
|
1915
|
-
checked: field.value ?? false,
|
|
1916
|
-
"aria-invalid": ariaInvalid(field),
|
|
1917
|
-
onChange: (e) => field.setValue(e.target.checked),
|
|
1918
|
-
onBlur: field.onBlur
|
|
1919
|
-
});
|
|
1920
|
-
var selectProps = (field) => ({
|
|
1921
|
-
name: field.path,
|
|
1922
|
-
value: field.value ?? "",
|
|
1923
|
-
"aria-invalid": ariaInvalid(field),
|
|
1924
|
-
onChange: (e) => {
|
|
1925
|
-
const v = e.target.value;
|
|
1926
|
-
field.setValue(v === "" && field.clearable ? field.emptyValue : v);
|
|
1927
|
-
},
|
|
1928
|
-
onBlur: field.onBlur
|
|
1929
|
-
});
|
|
1930
2067
|
var useFieldA11y = (error) => {
|
|
1931
2068
|
const id = react.useId();
|
|
1932
2069
|
const errorId = `${id}-error`;
|
|
@@ -2257,11 +2394,13 @@ exports.useFormActionState = useFormActionState;
|
|
|
2257
2394
|
exports.useFormError = useFormError;
|
|
2258
2395
|
exports.useFormSelector = useFormSelector;
|
|
2259
2396
|
exports.useFormSelectorShallow = useFormSelectorShallow;
|
|
2397
|
+
exports.useFormSteps = useFormSteps;
|
|
2260
2398
|
exports.useFormValues = useFormValues;
|
|
2261
2399
|
exports.useIsDirty = useIsDirty;
|
|
2262
2400
|
exports.useIsSubmitting = useIsSubmitting;
|
|
2263
2401
|
exports.useIsValid = useIsValid;
|
|
2264
2402
|
exports.useIsValidating = useIsValidating;
|
|
2403
|
+
exports.useMaskedInput = useMaskedInput;
|
|
2265
2404
|
exports.useNumberInput = useNumberInput;
|
|
2266
2405
|
exports.usePersistForm = usePersistForm;
|
|
2267
2406
|
exports.useSubmitCount = useSubmitCount;
|