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/index.cjs
CHANGED
|
@@ -212,10 +212,12 @@ var hasOwnSlot = (obj, path) => parsePath(path).reduce(
|
|
|
212
212
|
).ok;
|
|
213
213
|
var writeIntoArray = (arr, index, rest, value, kindAt, prefix) => {
|
|
214
214
|
const length = Math.max(arr.length, index + 1);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
);
|
|
215
|
+
const child = writeSegments(arr[index], rest, value, kindAt, [
|
|
216
|
+
...prefix,
|
|
217
|
+
index
|
|
218
|
+
]);
|
|
219
|
+
if (index < arr.length && child === arr[index]) return arr;
|
|
220
|
+
return Array.from({ length }, (_, i) => i === index ? child : arr[i]);
|
|
219
221
|
};
|
|
220
222
|
var refusesHugeIndex = (index) => {
|
|
221
223
|
if (index > MAX_ARRAY_INDEX) {
|
|
@@ -247,10 +249,12 @@ var writeSegments = (obj, segments, value, kindAt, prefix) => {
|
|
|
247
249
|
return obj;
|
|
248
250
|
}
|
|
249
251
|
const key2 = String(head);
|
|
250
|
-
|
|
251
|
-
...
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
const child = writeSegments(obj[key2], rest, value, kindAt, [
|
|
253
|
+
...prefix,
|
|
254
|
+
key2
|
|
255
|
+
]);
|
|
256
|
+
if (child === obj[key2] && Object.hasOwn(obj, key2)) return obj;
|
|
257
|
+
return { ...obj, [key2]: child };
|
|
254
258
|
}
|
|
255
259
|
const kind = kindAt?.(prefix.map(String).join(".")) ?? (typeof head === "number" ? "array" : "record");
|
|
256
260
|
if (kind === "array") {
|
|
@@ -329,7 +333,11 @@ var unwrapForWalk = (s) => {
|
|
|
329
333
|
if (s instanceof zod.z.ZodDefault || s instanceof zod.z.ZodPrefault || s instanceof zod.z.ZodCatch || s instanceof zod.z.ZodReadonly || s instanceof zod.z.ZodNonOptional) {
|
|
330
334
|
return s.def.innerType;
|
|
331
335
|
}
|
|
332
|
-
if (s instanceof zod.z.ZodPipe)
|
|
336
|
+
if (s instanceof zod.z.ZodPipe) {
|
|
337
|
+
const input = s.def.in;
|
|
338
|
+
return input instanceof zod.z.ZodTransform ? s.def.out : input;
|
|
339
|
+
}
|
|
340
|
+
if (s instanceof zod.z.ZodLazy) return s.def.getter();
|
|
333
341
|
return null;
|
|
334
342
|
};
|
|
335
343
|
var isLeafSchema = (s) => s instanceof zod.z.ZodString || s instanceof zod.z.ZodNumber || s instanceof zod.z.ZodBoolean || s instanceof zod.z.ZodBigInt || s instanceof zod.z.ZodDate || s instanceof zod.z.ZodEnum || s instanceof zod.z.ZodLiteral || s instanceof zod.z.ZodNull || s instanceof zod.z.ZodUndefined || s instanceof zod.z.ZodVoid || s instanceof zod.z.ZodNaN || s instanceof zod.z.ZodSymbol;
|
|
@@ -395,10 +403,31 @@ var hasChecks = (s) => {
|
|
|
395
403
|
const checks = s.def.checks;
|
|
396
404
|
return checks !== void 0 && checks.length > 0;
|
|
397
405
|
};
|
|
398
|
-
var
|
|
399
|
-
const
|
|
406
|
+
var optionClaims = (option, discriminator, tag) => {
|
|
407
|
+
const strip = (s) => {
|
|
400
408
|
const inner = unwrapForWalk(s);
|
|
401
|
-
|
|
409
|
+
return inner === null ? s : strip(inner);
|
|
410
|
+
};
|
|
411
|
+
const node = strip(option);
|
|
412
|
+
if (!(node instanceof zod.z.ZodObject)) return false;
|
|
413
|
+
const shape = node.shape;
|
|
414
|
+
const at = Object.hasOwn(shape, discriminator) ? shape[discriminator] : void 0;
|
|
415
|
+
if (at === void 0) return false;
|
|
416
|
+
const leaf = strip(at);
|
|
417
|
+
if (leaf instanceof zod.z.ZodLiteral) {
|
|
418
|
+
return leaf.def.values.includes(tag);
|
|
419
|
+
}
|
|
420
|
+
if (leaf instanceof zod.z.ZodEnum) {
|
|
421
|
+
return Object.values(leaf.def.entries).includes(
|
|
422
|
+
tag
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
return false;
|
|
426
|
+
};
|
|
427
|
+
var fieldSchemaForIntrospection = (schema, path, values) => {
|
|
428
|
+
const childOf = (s, segment, holder) => {
|
|
429
|
+
const inner = unwrapForWalk(s);
|
|
430
|
+
if (inner !== null) return childOf(inner, segment, holder);
|
|
402
431
|
if (s instanceof zod.z.ZodObject && typeof segment === "string") {
|
|
403
432
|
const shape = s.shape;
|
|
404
433
|
return Object.hasOwn(shape, segment) ? shape[segment] ?? null : null;
|
|
@@ -412,19 +441,45 @@ var fieldSchemaForIntrospection = (schema, path) => {
|
|
|
412
441
|
if (s instanceof zod.z.ZodRecord) {
|
|
413
442
|
return s.valueType;
|
|
414
443
|
}
|
|
444
|
+
if (s instanceof zod.z.ZodIntersection) {
|
|
445
|
+
return childOf(s.def.left, segment, holder) ?? childOf(s.def.right, segment, holder);
|
|
446
|
+
}
|
|
415
447
|
if (s instanceof zod.z.ZodDiscriminatedUnion || s instanceof zod.z.ZodUnion) {
|
|
416
448
|
const options = s.def.options;
|
|
417
|
-
|
|
418
|
-
|
|
449
|
+
const discriminator = s instanceof zod.z.ZodDiscriminatedUnion ? s.def.discriminator : null;
|
|
450
|
+
const tag = discriminator !== null && typeof holder === "object" && holder !== null ? holder[discriminator] : void 0;
|
|
451
|
+
const live = discriminator === null || tag === void 0 ? void 0 : options.find((option) => optionClaims(option, discriminator, tag));
|
|
452
|
+
const candidates = live === void 0 ? options : [live];
|
|
453
|
+
return candidates.reduce(
|
|
454
|
+
(found, option) => found ?? childOf(option, segment, holder),
|
|
419
455
|
null
|
|
420
456
|
);
|
|
421
457
|
}
|
|
422
458
|
return null;
|
|
423
459
|
};
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
460
|
+
const segments = parsePath(path);
|
|
461
|
+
return segments.reduce(
|
|
462
|
+
(acc, segment) => acc.s === null ? acc : {
|
|
463
|
+
s: childOf(
|
|
464
|
+
acc.s,
|
|
465
|
+
segment,
|
|
466
|
+
values === void 0 ? void 0 : getAtPath(values, acc.prefix)
|
|
467
|
+
),
|
|
468
|
+
prefix: acc.prefix === "" ? String(segment) : `${acc.prefix}.${String(segment)}`
|
|
469
|
+
},
|
|
470
|
+
{ s: schema, prefix: "" }
|
|
471
|
+
).s;
|
|
472
|
+
};
|
|
473
|
+
var schemaAcceptsUndefined = (schema) => schema instanceof zod.z.ZodOptional || schema instanceof zod.z.ZodDefault || schema instanceof zod.z.ZodPrefault || schema instanceof zod.z.ZodNullable && schemaAcceptsUndefined(schema.unwrap());
|
|
474
|
+
var isObjectSchemaAt = (schema, path) => {
|
|
475
|
+
const sub = fieldSchemaForIntrospection(schema, path);
|
|
476
|
+
if (sub === null) return true;
|
|
477
|
+
const strip = (s) => {
|
|
478
|
+
const inner = unwrapForWalk(s);
|
|
479
|
+
return inner === null ? s : strip(inner);
|
|
480
|
+
};
|
|
481
|
+
const node = strip(sub);
|
|
482
|
+
return !(node instanceof zod.z.ZodUnion || node instanceof zod.z.ZodDiscriminatedUnion || node instanceof zod.z.ZodRecord || node instanceof zod.z.ZodMap || node instanceof zod.z.ZodSet || node instanceof zod.z.ZodArray || node instanceof zod.z.ZodTuple || isLeafSchema(node));
|
|
428
483
|
};
|
|
429
484
|
var containerKindAt = (schema) => (prefix) => {
|
|
430
485
|
const unwrapAll = (s) => {
|
|
@@ -544,7 +599,8 @@ var scopeParse = (result, path, viaSubschema) => {
|
|
|
544
599
|
var pruneUnreported = (prev, full) => {
|
|
545
600
|
const entries = Object.entries(prev);
|
|
546
601
|
const kept = entries.filter(([k]) => Object.hasOwn(full, k));
|
|
547
|
-
|
|
602
|
+
const unchanged = kept.length === entries.length && kept.every(([k, list]) => full[k] === list);
|
|
603
|
+
return unchanged ? prev : Object.fromEntries(kept.map(([k, list]) => [k, full[k] ?? list]));
|
|
548
604
|
};
|
|
549
605
|
var dropEmptyLists = (map) => {
|
|
550
606
|
const entries = Object.entries(map);
|
|
@@ -916,7 +972,14 @@ var createForm = (schema, options) => {
|
|
|
916
972
|
fresh.flatMap((path) => Object.entries(errorsFor(path)))
|
|
917
973
|
),
|
|
918
974
|
fresh,
|
|
919
|
-
|
|
975
|
+
// The prune map describes the values the batch STARTED with.
|
|
976
|
+
// A skip scope (an index into an empty array) still reads as
|
|
977
|
+
// fresh after an unrelated change, so a batch can commit under
|
|
978
|
+
// changed values; pruning with the stale map then dropped
|
|
979
|
+
// errors committed elsewhere in flight (an email error landing
|
|
980
|
+
// while a wizard step with such a path validated). The prune
|
|
981
|
+
// rides only while the whole values reference held.
|
|
982
|
+
parsed.kind === "full" && live.values === valuesAtStart ? parsed.errors : null
|
|
920
983
|
),
|
|
921
984
|
isValidating: omitKeys(state.isValidating, ownedKeys)
|
|
922
985
|
}));
|
|
@@ -1090,12 +1153,14 @@ var createForm = (schema, options) => {
|
|
|
1090
1153
|
}
|
|
1091
1154
|
}
|
|
1092
1155
|
};
|
|
1093
|
-
|
|
1156
|
+
const judgeBaseline = () => {
|
|
1157
|
+
if (options.validateOnMount !== true) return;
|
|
1094
1158
|
const result = validate();
|
|
1095
1159
|
if (result.kind === "pending") {
|
|
1096
1160
|
void result.promise.catch(reportBackgroundFailure);
|
|
1097
1161
|
}
|
|
1098
|
-
}
|
|
1162
|
+
};
|
|
1163
|
+
judgeBaseline();
|
|
1099
1164
|
return Object.freeze({
|
|
1100
1165
|
schema,
|
|
1101
1166
|
store,
|
|
@@ -1110,8 +1175,12 @@ var createForm = (schema, options) => {
|
|
|
1110
1175
|
return store.subscribe((state) => {
|
|
1111
1176
|
const next = snapshotField(state, path);
|
|
1112
1177
|
if (!shallowFieldEqual(ref.current, next)) {
|
|
1178
|
+
const prev = ref.current;
|
|
1113
1179
|
ref.current = next;
|
|
1114
|
-
listener(
|
|
1180
|
+
listener(
|
|
1181
|
+
next,
|
|
1182
|
+
prev
|
|
1183
|
+
);
|
|
1115
1184
|
}
|
|
1116
1185
|
});
|
|
1117
1186
|
},
|
|
@@ -1146,10 +1215,12 @@ var createForm = (schema, options) => {
|
|
|
1146
1215
|
if (Object.is(getAtPath(currentValues, path), value) && (value !== void 0 || hasOwnSlot(currentValues, path))) {
|
|
1147
1216
|
return;
|
|
1148
1217
|
}
|
|
1218
|
+
const next = setAtPath(currentValues, path, value, kindAt);
|
|
1219
|
+
if (next === currentValues) return;
|
|
1149
1220
|
clearArrayOpsUnder(store, path);
|
|
1150
1221
|
store.setState((state) => ({
|
|
1151
1222
|
...state,
|
|
1152
|
-
values:
|
|
1223
|
+
values: next,
|
|
1153
1224
|
...errorChannels(state, {
|
|
1154
1225
|
serverErrors: releaseSpine(state.serverErrors, path)
|
|
1155
1226
|
})
|
|
@@ -1256,6 +1327,7 @@ var createForm = (schema, options) => {
|
|
|
1256
1327
|
isValidating: emptyBools,
|
|
1257
1328
|
isValidatingForm: false
|
|
1258
1329
|
}));
|
|
1330
|
+
judgeBaseline();
|
|
1259
1331
|
},
|
|
1260
1332
|
reset: (nextInitial, resetOptions) => {
|
|
1261
1333
|
sequences.clear();
|
|
@@ -1280,6 +1352,7 @@ var createForm = (schema, options) => {
|
|
|
1280
1352
|
isValidatingForm: false
|
|
1281
1353
|
};
|
|
1282
1354
|
});
|
|
1355
|
+
if (resetOptions?.keepErrors !== true) judgeBaseline();
|
|
1283
1356
|
},
|
|
1284
1357
|
resetField: (path) => {
|
|
1285
1358
|
const initialSlot = slotAtPath(
|
|
@@ -1374,6 +1447,7 @@ var createForm = (schema, options) => {
|
|
|
1374
1447
|
},
|
|
1375
1448
|
snapshot: () => store.getState(),
|
|
1376
1449
|
restore: (snap) => {
|
|
1450
|
+
sequences.clear();
|
|
1377
1451
|
clearArrayOpsUnder(store, "");
|
|
1378
1452
|
store.setState(() => {
|
|
1379
1453
|
const schemaErrors = dropEmptyLists(snap.schemaErrors ?? emptyErrors);
|
|
@@ -1497,6 +1571,7 @@ var persistForm = (form, options) => {
|
|
|
1497
1571
|
const schema = form.schema;
|
|
1498
1572
|
const fillRequiredGaps = (draft, initial, path) => {
|
|
1499
1573
|
if (!isPlainObject(draft) || !isPlainObject(initial)) return draft;
|
|
1574
|
+
if (!isObjectSchemaAt(schema, path)) return draft;
|
|
1500
1575
|
return Object.keys(initial).reduce(
|
|
1501
1576
|
(acc, key) => {
|
|
1502
1577
|
const childPath = joinPath(path, key);
|
|
@@ -1506,21 +1581,34 @@ var persistForm = (form, options) => {
|
|
|
1506
1581
|
}
|
|
1507
1582
|
if (initial[key] === void 0) return acc;
|
|
1508
1583
|
const sub = fieldSchemaForIntrospection(schema, childPath);
|
|
1509
|
-
return sub !== null &&
|
|
1584
|
+
return sub !== null && schemaAcceptsUndefined(sub) ? acc : { ...acc, [key]: initial[key] };
|
|
1510
1585
|
},
|
|
1511
1586
|
draft
|
|
1512
1587
|
);
|
|
1513
1588
|
};
|
|
1589
|
+
const applying = { current: false };
|
|
1514
1590
|
const applyDraft = (values) => {
|
|
1515
1591
|
const filled = fillRequiredGaps(
|
|
1516
1592
|
values,
|
|
1517
1593
|
form.getState().initialValues,
|
|
1518
1594
|
""
|
|
1519
1595
|
);
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1596
|
+
applying.current = true;
|
|
1597
|
+
try {
|
|
1598
|
+
if (apply === "edit" || apply === "restore") {
|
|
1599
|
+
form.setValues(filled);
|
|
1600
|
+
} else {
|
|
1601
|
+
form.adoptValues(filled);
|
|
1602
|
+
}
|
|
1603
|
+
} finally {
|
|
1604
|
+
applying.current = false;
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
const isCurrent = (values) => {
|
|
1608
|
+
try {
|
|
1609
|
+
return JSON.stringify(values) === JSON.stringify(form.getState().values);
|
|
1610
|
+
} catch {
|
|
1611
|
+
return false;
|
|
1524
1612
|
}
|
|
1525
1613
|
};
|
|
1526
1614
|
const referenceShape = () => {
|
|
@@ -1531,7 +1619,7 @@ var persistForm = (form, options) => {
|
|
|
1531
1619
|
}
|
|
1532
1620
|
};
|
|
1533
1621
|
const flexibleAt = (path) => schemaAllowsKindChangeAt(schema, path);
|
|
1534
|
-
const inspect = () => {
|
|
1622
|
+
const inspect = (skipWhenCurrent) => {
|
|
1535
1623
|
const raw = (() => {
|
|
1536
1624
|
try {
|
|
1537
1625
|
return storage === null ? null : storage.getItem(options.key);
|
|
@@ -1577,14 +1665,15 @@ var persistForm = (form, options) => {
|
|
|
1577
1665
|
reason: `its shape conflicts with the form's initial values at "${conflict}" (set the version option when the schema changes)`
|
|
1578
1666
|
};
|
|
1579
1667
|
}
|
|
1668
|
+
if (skipWhenCurrent && isCurrent(values)) return { kind: "applied" };
|
|
1580
1669
|
applyDraft(values);
|
|
1581
1670
|
return { kind: "applied" };
|
|
1582
1671
|
} catch {
|
|
1583
1672
|
return { kind: "ignored", reason: "it is not valid JSON" };
|
|
1584
1673
|
}
|
|
1585
1674
|
};
|
|
1586
|
-
const
|
|
1587
|
-
const outcome = inspect();
|
|
1675
|
+
const loadWith = (skipWhenCurrent) => {
|
|
1676
|
+
const outcome = inspect(skipWhenCurrent);
|
|
1588
1677
|
if (outcome.kind === "ignored" && isDevBuild()) {
|
|
1589
1678
|
console.warn(
|
|
1590
1679
|
`[formstand] persistForm: ignored the draft under "${options.key}": ${outcome.reason}. The next edit overwrites it.`
|
|
@@ -1592,6 +1681,7 @@ var persistForm = (form, options) => {
|
|
|
1592
1681
|
}
|
|
1593
1682
|
return outcome.kind === "applied";
|
|
1594
1683
|
};
|
|
1684
|
+
const load = () => loadWith(false);
|
|
1595
1685
|
const writeDraft = (values) => {
|
|
1596
1686
|
try {
|
|
1597
1687
|
storage?.setItem(
|
|
@@ -1605,10 +1695,11 @@ var persistForm = (form, options) => {
|
|
|
1605
1695
|
};
|
|
1606
1696
|
const writes = createDebouncer(debounceMs);
|
|
1607
1697
|
if (apply !== "manual") {
|
|
1608
|
-
|
|
1698
|
+
loadWith(true);
|
|
1609
1699
|
}
|
|
1610
1700
|
const cleared = { current: false };
|
|
1611
1701
|
const unsubscribe = form.watchValues((values) => {
|
|
1702
|
+
if (applying.current) return;
|
|
1612
1703
|
if (cleared.current && values === form.getState().initialValues) return;
|
|
1613
1704
|
cleared.current = false;
|
|
1614
1705
|
writes.schedule(() => writeDraft(values));
|
|
@@ -1637,15 +1728,27 @@ var usePersistForm = (form, options) => {
|
|
|
1637
1728
|
latestOptions.current = options;
|
|
1638
1729
|
});
|
|
1639
1730
|
const key = options.key;
|
|
1640
|
-
const
|
|
1731
|
+
const hasStorage = options.storage !== void 0;
|
|
1732
|
+
const storageProxy = react.useMemo(
|
|
1733
|
+
() => ({
|
|
1734
|
+
getItem: (k) => latestOptions.current.storage?.getItem(k) ?? null,
|
|
1735
|
+
setItem: (k, v) => latestOptions.current.storage?.setItem(k, v),
|
|
1736
|
+
removeItem: (k) => latestOptions.current.storage?.removeItem(k)
|
|
1737
|
+
}),
|
|
1738
|
+
[]
|
|
1739
|
+
);
|
|
1641
1740
|
react.useEffect(() => {
|
|
1642
|
-
const
|
|
1741
|
+
const options2 = latestOptions.current;
|
|
1742
|
+
const handle = persistForm(form, {
|
|
1743
|
+
...options2,
|
|
1744
|
+
...options2.storage === void 0 ? {} : { storage: storageProxy }
|
|
1745
|
+
});
|
|
1643
1746
|
inner.current = handle;
|
|
1644
1747
|
return () => {
|
|
1645
1748
|
inner.current = null;
|
|
1646
1749
|
handle.dispose();
|
|
1647
1750
|
};
|
|
1648
|
-
}, [form, key,
|
|
1751
|
+
}, [form, key, hasStorage, storageProxy]);
|
|
1649
1752
|
return react.useMemo(
|
|
1650
1753
|
() => ({
|
|
1651
1754
|
load: () => inner.current?.load() ?? false,
|
|
@@ -1717,6 +1820,12 @@ var numberInputProps = (field) => ({
|
|
|
1717
1820
|
});
|
|
1718
1821
|
var dateToInputText = (value) => {
|
|
1719
1822
|
if (value === void 0 || value === null) return "";
|
|
1823
|
+
if (typeof value === "string") {
|
|
1824
|
+
const text = value;
|
|
1825
|
+
const parsed = parseDateText(text);
|
|
1826
|
+
if (parsed.kind === "date") return dateToInputText(parsed.value);
|
|
1827
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(text.trim())) return "";
|
|
1828
|
+
}
|
|
1720
1829
|
const date = value instanceof Date ? value : new Date(value);
|
|
1721
1830
|
if (Number.isNaN(date.getTime())) return "";
|
|
1722
1831
|
const pad = (part) => String(part).padStart(2, "0");
|
|
@@ -1825,6 +1934,15 @@ function useFormSelectorShallow(form, selector) {
|
|
|
1825
1934
|
}
|
|
1826
1935
|
|
|
1827
1936
|
// src/react/useFormSteps.ts
|
|
1937
|
+
var settleOnLiveValues = async (form, fields, round = 0) => {
|
|
1938
|
+
const before = fields.map((path) => form.getField(path));
|
|
1939
|
+
const result = await form.validateFieldsAsync(fields);
|
|
1940
|
+
const changed = fields.some(
|
|
1941
|
+
(path, i) => !Object.is(form.getField(path), before[i])
|
|
1942
|
+
);
|
|
1943
|
+
if (!changed) return result;
|
|
1944
|
+
return round < 3 ? settleOnLiveValues(form, fields, round + 1) : null;
|
|
1945
|
+
};
|
|
1828
1946
|
var useFormSteps = (form, steps) => {
|
|
1829
1947
|
const [step, setStep] = react.useState(0);
|
|
1830
1948
|
const [visited, setVisited] = react.useState(
|
|
@@ -1838,7 +1956,8 @@ var useFormSteps = (form, steps) => {
|
|
|
1838
1956
|
async (index) => {
|
|
1839
1957
|
const definition = latest.current[index];
|
|
1840
1958
|
if (definition === void 0) return true;
|
|
1841
|
-
const result = await form
|
|
1959
|
+
const result = await settleOnLiveValues(form, definition.fields);
|
|
1960
|
+
if (result === null) return false;
|
|
1842
1961
|
if (result.kind === "invalid") {
|
|
1843
1962
|
Object.keys(result.errors).forEach((key) => {
|
|
1844
1963
|
form.setTouched(key, true);
|
|
@@ -1850,46 +1969,49 @@ var useFormSteps = (form, steps) => {
|
|
|
1850
1969
|
[form]
|
|
1851
1970
|
);
|
|
1852
1971
|
const currentStep = react.useRef(step);
|
|
1853
|
-
react.
|
|
1854
|
-
currentStep.current =
|
|
1855
|
-
|
|
1972
|
+
const navigate = react.useCallback((target) => {
|
|
1973
|
+
currentStep.current = target;
|
|
1974
|
+
setStep(target);
|
|
1975
|
+
}, []);
|
|
1856
1976
|
const next = react.useCallback(async () => {
|
|
1857
|
-
const
|
|
1977
|
+
const from = currentStep.current;
|
|
1978
|
+
const ok = await validateStep(from);
|
|
1858
1979
|
if (!ok) return false;
|
|
1859
|
-
if (currentStep.current !==
|
|
1860
|
-
const target = Math.min(
|
|
1861
|
-
|
|
1980
|
+
if (currentStep.current !== from) return false;
|
|
1981
|
+
const target = Math.max(0, Math.min(from + 1, latest.current.length - 1));
|
|
1982
|
+
navigate(target);
|
|
1862
1983
|
setVisited((prev) => /* @__PURE__ */ new Set([...prev, target]));
|
|
1863
1984
|
return true;
|
|
1864
|
-
}, [validateStep,
|
|
1985
|
+
}, [validateStep, navigate]);
|
|
1865
1986
|
const back = react.useCallback(() => {
|
|
1866
|
-
|
|
1867
|
-
}, []);
|
|
1987
|
+
navigate(Math.max(currentStep.current - 1, 0));
|
|
1988
|
+
}, [navigate]);
|
|
1868
1989
|
const goTo = react.useCallback(
|
|
1869
1990
|
async (target) => {
|
|
1870
1991
|
const count = latest.current.length;
|
|
1871
1992
|
if (!Number.isFinite(target)) return currentStep.current;
|
|
1993
|
+
const from = currentStep.current;
|
|
1872
1994
|
const clamped = Math.max(0, Math.min(Math.trunc(target), count - 1));
|
|
1873
|
-
if (clamped <=
|
|
1874
|
-
|
|
1995
|
+
if (clamped <= from) {
|
|
1996
|
+
navigate(clamped);
|
|
1875
1997
|
return clamped;
|
|
1876
1998
|
}
|
|
1877
|
-
const walk = async (
|
|
1878
|
-
if (
|
|
1879
|
-
return await validateStep(
|
|
1999
|
+
const walk = async (at) => {
|
|
2000
|
+
if (at >= clamped) return clamped;
|
|
2001
|
+
return await validateStep(at) ? walk(at + 1) : at;
|
|
1880
2002
|
};
|
|
1881
|
-
const landed = await walk(
|
|
1882
|
-
if (currentStep.current !==
|
|
1883
|
-
|
|
2003
|
+
const landed = await walk(from);
|
|
2004
|
+
if (currentStep.current !== from) return currentStep.current;
|
|
2005
|
+
navigate(landed);
|
|
1884
2006
|
setVisited(
|
|
1885
2007
|
(prev) => /* @__PURE__ */ new Set([
|
|
1886
2008
|
...prev,
|
|
1887
|
-
...Array.from({ length: landed -
|
|
2009
|
+
...Array.from({ length: landed - from + 1 }, (_, i) => from + i)
|
|
1888
2010
|
])
|
|
1889
2011
|
);
|
|
1890
2012
|
return landed;
|
|
1891
2013
|
},
|
|
1892
|
-
[validateStep,
|
|
2014
|
+
[validateStep, navigate]
|
|
1893
2015
|
);
|
|
1894
2016
|
const errorFlags = useFormSelectorShallow(
|
|
1895
2017
|
form,
|
|
@@ -1982,6 +2104,7 @@ var createFormContext = () => {
|
|
|
1982
2104
|
return { Provider, useFormContext };
|
|
1983
2105
|
};
|
|
1984
2106
|
function useField(form, pathArg, options) {
|
|
2107
|
+
const formSchema = form.schema;
|
|
1985
2108
|
const slice = react$1.useStore(
|
|
1986
2109
|
form.store,
|
|
1987
2110
|
shallow.useShallow((state) => {
|
|
@@ -1997,25 +2120,26 @@ function useField(form, pathArg, options) {
|
|
|
1997
2120
|
error: entryAt(state.errors, p),
|
|
1998
2121
|
touched: flagAt(state.touched, p),
|
|
1999
2122
|
dirty: isFieldDirty(value, initialValue2),
|
|
2000
|
-
isValidating: flagAt(state.isValidating, p)
|
|
2123
|
+
isValidating: flagAt(state.isValidating, p),
|
|
2124
|
+
// Resolved inside the selector so a discriminant change re-reads
|
|
2125
|
+
// the ACTIVE variant's wrapper for a key the variants share; the
|
|
2126
|
+
// schema node is a stable reference, so the shallow compare holds.
|
|
2127
|
+
schema: formSchema === void 0 ? null : fieldSchemaForIntrospection(formSchema, p, state.values)
|
|
2001
2128
|
};
|
|
2002
2129
|
})
|
|
2003
2130
|
);
|
|
2004
2131
|
const path = slice.path;
|
|
2005
|
-
const
|
|
2132
|
+
const sub = slice.schema;
|
|
2006
2133
|
const initialValue = slice.initialValue;
|
|
2007
2134
|
const { emptyValue, clearable } = react.useMemo(() => {
|
|
2008
|
-
if (
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
clearable: isClearableSchema(sub)
|
|
2014
|
-
};
|
|
2015
|
-
}
|
|
2135
|
+
if (sub !== null) {
|
|
2136
|
+
return {
|
|
2137
|
+
emptyValue: emptyValueForSchema(sub),
|
|
2138
|
+
clearable: isClearableSchema(sub)
|
|
2139
|
+
};
|
|
2016
2140
|
}
|
|
2017
2141
|
return initialValue === null ? { emptyValue: null, clearable: true } : { emptyValue: void 0, clearable: false };
|
|
2018
|
-
}, [
|
|
2142
|
+
}, [sub, initialValue]);
|
|
2019
2143
|
const debounceMs = options?.debounceMs;
|
|
2020
2144
|
const debouncerRef = react.useRef(null);
|
|
2021
2145
|
react.useEffect(() => {
|
|
@@ -2125,10 +2249,11 @@ function useField(form, pathArg, options) {
|
|
|
2125
2249
|
}
|
|
2126
2250
|
|
|
2127
2251
|
// src/react/useVariantField.ts
|
|
2128
|
-
function useVariantField(form, unionPath, field) {
|
|
2252
|
+
function useVariantField(form, unionPath, field, options) {
|
|
2129
2253
|
return useField(
|
|
2130
2254
|
form,
|
|
2131
|
-
`${unionPath}.${field}
|
|
2255
|
+
`${unionPath}.${field}`,
|
|
2256
|
+
options
|
|
2132
2257
|
);
|
|
2133
2258
|
}
|
|
2134
2259
|
var EMPTY_ITEMS = [];
|
|
@@ -2291,6 +2416,12 @@ function useFieldArray(form, pathArg) {
|
|
|
2291
2416
|
const items = slice.items;
|
|
2292
2417
|
const error = slice.error;
|
|
2293
2418
|
const ids = deriveIds(form.store, path, items).ids;
|
|
2419
|
+
const validateNow = react.useCallback(() => {
|
|
2420
|
+
const result = form.validateField?.(path);
|
|
2421
|
+
if (result?.kind === "pending") {
|
|
2422
|
+
void result.promise.catch(reportBackgroundFailure);
|
|
2423
|
+
}
|
|
2424
|
+
}, [form, path]);
|
|
2294
2425
|
const revalidate = react.useCallback(() => {
|
|
2295
2426
|
const state = form.store.getState();
|
|
2296
2427
|
if (shouldValidateOn(
|
|
@@ -2300,9 +2431,9 @@ function useFieldArray(form, pathArg) {
|
|
|
2300
2431
|
state.submitCount > 0,
|
|
2301
2432
|
flagAt(state.touched, path)
|
|
2302
2433
|
)) {
|
|
2303
|
-
|
|
2434
|
+
validateNow();
|
|
2304
2435
|
}
|
|
2305
|
-
}, [form, path]);
|
|
2436
|
+
}, [form, path, validateNow]);
|
|
2306
2437
|
const push = react.useCallback(
|
|
2307
2438
|
(item) => {
|
|
2308
2439
|
form.arrayPush(path, item);
|
|
@@ -2367,13 +2498,17 @@ function useFieldArray(form, pathArg) {
|
|
|
2367
2498
|
state.submitCount > 0,
|
|
2368
2499
|
true
|
|
2369
2500
|
)) {
|
|
2370
|
-
|
|
2501
|
+
validateNow();
|
|
2371
2502
|
}
|
|
2372
|
-
}, [form, path]);
|
|
2503
|
+
}, [form, path, validateNow]);
|
|
2373
2504
|
const validate = react.useCallback(
|
|
2374
2505
|
() => form.validateField?.(path) ?? { kind: "valid" },
|
|
2375
2506
|
[form, path]
|
|
2376
2507
|
);
|
|
2508
|
+
const validateAsync2 = react.useCallback(
|
|
2509
|
+
() => form.validateFieldAsync?.(path) ?? Promise.resolve({ kind: "valid" }),
|
|
2510
|
+
[form, path]
|
|
2511
|
+
);
|
|
2377
2512
|
const replace = react.useCallback(
|
|
2378
2513
|
(next) => {
|
|
2379
2514
|
form.setValue?.(path, next);
|
|
@@ -2383,7 +2518,15 @@ function useFieldArray(form, pathArg) {
|
|
|
2383
2518
|
);
|
|
2384
2519
|
const update = react.useCallback(
|
|
2385
2520
|
(index, item) => {
|
|
2386
|
-
form.
|
|
2521
|
+
const current = getAtPath(form.store.getState().values, path);
|
|
2522
|
+
const length = Array.isArray(current) ? current.length : 0;
|
|
2523
|
+
if (!Number.isInteger(index) || index < 0 || index >= length) {
|
|
2524
|
+
console.warn(
|
|
2525
|
+
`[formstand] array op on "${path}" with an out-of-range or non-integer index (length ${String(length)}). Operation skipped.`
|
|
2526
|
+
);
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
form.setValue?.(`${path}.${String(index)}`, item);
|
|
2387
2530
|
revalidate();
|
|
2388
2531
|
},
|
|
2389
2532
|
[form, path, revalidate]
|
|
@@ -2415,6 +2558,7 @@ function useFieldArray(form, pathArg) {
|
|
|
2415
2558
|
setTouched,
|
|
2416
2559
|
onBlur,
|
|
2417
2560
|
validate,
|
|
2561
|
+
validateAsync: validateAsync2,
|
|
2418
2562
|
push,
|
|
2419
2563
|
remove,
|
|
2420
2564
|
insert,
|
|
@@ -2438,6 +2582,7 @@ function useFieldArray(form, pathArg) {
|
|
|
2438
2582
|
setTouched,
|
|
2439
2583
|
onBlur,
|
|
2440
2584
|
validate,
|
|
2585
|
+
validateAsync2,
|
|
2441
2586
|
push,
|
|
2442
2587
|
remove,
|
|
2443
2588
|
insert,
|
|
@@ -2465,7 +2610,7 @@ var useFormError = (form) => react$1.useStore(
|
|
|
2465
2610
|
function useFormValues(form) {
|
|
2466
2611
|
return react$1.useStore(form.store, (state) => state.values);
|
|
2467
2612
|
}
|
|
2468
|
-
function useFields(form, paths) {
|
|
2613
|
+
function useFields(form, paths, options) {
|
|
2469
2614
|
const [arity] = react.useState(paths.length);
|
|
2470
2615
|
if (paths.length !== arity) {
|
|
2471
2616
|
throw new Error(
|
|
@@ -2475,7 +2620,11 @@ function useFields(form, paths) {
|
|
|
2475
2620
|
const fields = paths.map(
|
|
2476
2621
|
(path) => (
|
|
2477
2622
|
// eslint-disable-next-line react-hooks/rules-of-hooks -- a fixed hook count by the stable-arity contract above; each element must be a REAL UseFieldReturn so it spreads into the prop builders
|
|
2478
|
-
useField(
|
|
2623
|
+
useField(
|
|
2624
|
+
form,
|
|
2625
|
+
path,
|
|
2626
|
+
options
|
|
2627
|
+
)
|
|
2479
2628
|
)
|
|
2480
2629
|
);
|
|
2481
2630
|
return react.useMemo(() => {
|
|
@@ -2538,10 +2687,11 @@ var capitalize = (name) => name.length === 0 ? name : name.charAt(0).toUpperCase
|
|
|
2538
2687
|
var createFormHooks = (form, name) => {
|
|
2539
2688
|
const structural = form;
|
|
2540
2689
|
const useBoundField = (path, options) => useField(structural, path, options);
|
|
2541
|
-
const useBoundVariantField = (unionPath, field) => useVariantField(
|
|
2690
|
+
const useBoundVariantField = (unionPath, field, options) => useVariantField(
|
|
2542
2691
|
structural,
|
|
2543
2692
|
unionPath,
|
|
2544
|
-
field
|
|
2693
|
+
field,
|
|
2694
|
+
options
|
|
2545
2695
|
);
|
|
2546
2696
|
const useBoundFieldArray = (path) => useFieldArray(structural, path);
|
|
2547
2697
|
const useBoundVariantFieldArray = (unionPath, field) => useVariantFieldArray(
|
|
@@ -2557,9 +2707,10 @@ var createFormHooks = (form, name) => {
|
|
|
2557
2707
|
const useBoundIsValidating = (path) => useIsValidating(structural, path);
|
|
2558
2708
|
const useBoundIsSubmitting = () => useIsSubmitting(structural);
|
|
2559
2709
|
const useBoundValues = () => useFormValues(structural);
|
|
2560
|
-
const useBoundFields = (paths) => useFields(
|
|
2710
|
+
const useBoundFields = (paths, options) => useFields(
|
|
2561
2711
|
structural,
|
|
2562
|
-
paths
|
|
2712
|
+
paths,
|
|
2713
|
+
options
|
|
2563
2714
|
);
|
|
2564
2715
|
const useBoundSubmitCount = () => useSubmitCount(structural);
|
|
2565
2716
|
const useBoundSteps = (steps) => useFormSteps(form, steps);
|
|
@@ -2609,7 +2760,7 @@ var FieldShell = ({
|
|
|
2609
2760
|
wrapLabel,
|
|
2610
2761
|
children
|
|
2611
2762
|
}) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "zf-field", children: [
|
|
2612
|
-
wrapLabel === true ? /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: id, className: "zf-label", children: [
|
|
2763
|
+
wrapLabel === true && label !== void 0 ? /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: id, className: "zf-label", children: [
|
|
2613
2764
|
children,
|
|
2614
2765
|
" ",
|
|
2615
2766
|
label
|