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.js
CHANGED
|
@@ -200,10 +200,12 @@ var hasOwnSlot = (obj, path) => parsePath(path).reduce(
|
|
|
200
200
|
).ok;
|
|
201
201
|
var writeIntoArray = (arr, index, rest, value, kindAt, prefix) => {
|
|
202
202
|
const length = Math.max(arr.length, index + 1);
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
);
|
|
203
|
+
const child = writeSegments(arr[index], rest, value, kindAt, [
|
|
204
|
+
...prefix,
|
|
205
|
+
index
|
|
206
|
+
]);
|
|
207
|
+
if (index < arr.length && child === arr[index]) return arr;
|
|
208
|
+
return Array.from({ length }, (_, i) => i === index ? child : arr[i]);
|
|
207
209
|
};
|
|
208
210
|
var refusesHugeIndex = (index) => {
|
|
209
211
|
if (index > MAX_ARRAY_INDEX) {
|
|
@@ -235,10 +237,12 @@ var writeSegments = (obj, segments, value, kindAt, prefix) => {
|
|
|
235
237
|
return obj;
|
|
236
238
|
}
|
|
237
239
|
const key2 = String(head);
|
|
238
|
-
|
|
239
|
-
...
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
const child = writeSegments(obj[key2], rest, value, kindAt, [
|
|
241
|
+
...prefix,
|
|
242
|
+
key2
|
|
243
|
+
]);
|
|
244
|
+
if (child === obj[key2] && Object.hasOwn(obj, key2)) return obj;
|
|
245
|
+
return { ...obj, [key2]: child };
|
|
242
246
|
}
|
|
243
247
|
const kind = kindAt?.(prefix.map(String).join(".")) ?? (typeof head === "number" ? "array" : "record");
|
|
244
248
|
if (kind === "array") {
|
|
@@ -317,7 +321,11 @@ var unwrapForWalk = (s) => {
|
|
|
317
321
|
if (s instanceof z.ZodDefault || s instanceof z.ZodPrefault || s instanceof z.ZodCatch || s instanceof z.ZodReadonly || s instanceof z.ZodNonOptional) {
|
|
318
322
|
return s.def.innerType;
|
|
319
323
|
}
|
|
320
|
-
if (s instanceof z.ZodPipe)
|
|
324
|
+
if (s instanceof z.ZodPipe) {
|
|
325
|
+
const input = s.def.in;
|
|
326
|
+
return input instanceof z.ZodTransform ? s.def.out : input;
|
|
327
|
+
}
|
|
328
|
+
if (s instanceof z.ZodLazy) return s.def.getter();
|
|
321
329
|
return null;
|
|
322
330
|
};
|
|
323
331
|
var isLeafSchema = (s) => s instanceof z.ZodString || s instanceof z.ZodNumber || s instanceof z.ZodBoolean || s instanceof z.ZodBigInt || s instanceof z.ZodDate || s instanceof z.ZodEnum || s instanceof z.ZodLiteral || s instanceof z.ZodNull || s instanceof z.ZodUndefined || s instanceof z.ZodVoid || s instanceof z.ZodNaN || s instanceof z.ZodSymbol;
|
|
@@ -383,10 +391,31 @@ var hasChecks = (s) => {
|
|
|
383
391
|
const checks = s.def.checks;
|
|
384
392
|
return checks !== void 0 && checks.length > 0;
|
|
385
393
|
};
|
|
386
|
-
var
|
|
387
|
-
const
|
|
394
|
+
var optionClaims = (option, discriminator, tag) => {
|
|
395
|
+
const strip = (s) => {
|
|
388
396
|
const inner = unwrapForWalk(s);
|
|
389
|
-
|
|
397
|
+
return inner === null ? s : strip(inner);
|
|
398
|
+
};
|
|
399
|
+
const node = strip(option);
|
|
400
|
+
if (!(node instanceof z.ZodObject)) return false;
|
|
401
|
+
const shape = node.shape;
|
|
402
|
+
const at = Object.hasOwn(shape, discriminator) ? shape[discriminator] : void 0;
|
|
403
|
+
if (at === void 0) return false;
|
|
404
|
+
const leaf = strip(at);
|
|
405
|
+
if (leaf instanceof z.ZodLiteral) {
|
|
406
|
+
return leaf.def.values.includes(tag);
|
|
407
|
+
}
|
|
408
|
+
if (leaf instanceof z.ZodEnum) {
|
|
409
|
+
return Object.values(leaf.def.entries).includes(
|
|
410
|
+
tag
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
return false;
|
|
414
|
+
};
|
|
415
|
+
var fieldSchemaForIntrospection = (schema, path, values) => {
|
|
416
|
+
const childOf = (s, segment, holder) => {
|
|
417
|
+
const inner = unwrapForWalk(s);
|
|
418
|
+
if (inner !== null) return childOf(inner, segment, holder);
|
|
390
419
|
if (s instanceof z.ZodObject && typeof segment === "string") {
|
|
391
420
|
const shape = s.shape;
|
|
392
421
|
return Object.hasOwn(shape, segment) ? shape[segment] ?? null : null;
|
|
@@ -400,19 +429,45 @@ var fieldSchemaForIntrospection = (schema, path) => {
|
|
|
400
429
|
if (s instanceof z.ZodRecord) {
|
|
401
430
|
return s.valueType;
|
|
402
431
|
}
|
|
432
|
+
if (s instanceof z.ZodIntersection) {
|
|
433
|
+
return childOf(s.def.left, segment, holder) ?? childOf(s.def.right, segment, holder);
|
|
434
|
+
}
|
|
403
435
|
if (s instanceof z.ZodDiscriminatedUnion || s instanceof z.ZodUnion) {
|
|
404
436
|
const options = s.def.options;
|
|
405
|
-
|
|
406
|
-
|
|
437
|
+
const discriminator = s instanceof z.ZodDiscriminatedUnion ? s.def.discriminator : null;
|
|
438
|
+
const tag = discriminator !== null && typeof holder === "object" && holder !== null ? holder[discriminator] : void 0;
|
|
439
|
+
const live = discriminator === null || tag === void 0 ? void 0 : options.find((option) => optionClaims(option, discriminator, tag));
|
|
440
|
+
const candidates = live === void 0 ? options : [live];
|
|
441
|
+
return candidates.reduce(
|
|
442
|
+
(found, option) => found ?? childOf(option, segment, holder),
|
|
407
443
|
null
|
|
408
444
|
);
|
|
409
445
|
}
|
|
410
446
|
return null;
|
|
411
447
|
};
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
448
|
+
const segments = parsePath(path);
|
|
449
|
+
return segments.reduce(
|
|
450
|
+
(acc, segment) => acc.s === null ? acc : {
|
|
451
|
+
s: childOf(
|
|
452
|
+
acc.s,
|
|
453
|
+
segment,
|
|
454
|
+
values === void 0 ? void 0 : getAtPath(values, acc.prefix)
|
|
455
|
+
),
|
|
456
|
+
prefix: acc.prefix === "" ? String(segment) : `${acc.prefix}.${String(segment)}`
|
|
457
|
+
},
|
|
458
|
+
{ s: schema, prefix: "" }
|
|
459
|
+
).s;
|
|
460
|
+
};
|
|
461
|
+
var schemaAcceptsUndefined = (schema) => schema instanceof z.ZodOptional || schema instanceof z.ZodDefault || schema instanceof z.ZodPrefault || schema instanceof z.ZodNullable && schemaAcceptsUndefined(schema.unwrap());
|
|
462
|
+
var isObjectSchemaAt = (schema, path) => {
|
|
463
|
+
const sub = fieldSchemaForIntrospection(schema, path);
|
|
464
|
+
if (sub === null) return true;
|
|
465
|
+
const strip = (s) => {
|
|
466
|
+
const inner = unwrapForWalk(s);
|
|
467
|
+
return inner === null ? s : strip(inner);
|
|
468
|
+
};
|
|
469
|
+
const node = strip(sub);
|
|
470
|
+
return !(node instanceof z.ZodUnion || node instanceof z.ZodDiscriminatedUnion || node instanceof z.ZodRecord || node instanceof z.ZodMap || node instanceof z.ZodSet || node instanceof z.ZodArray || node instanceof z.ZodTuple || isLeafSchema(node));
|
|
416
471
|
};
|
|
417
472
|
var containerKindAt = (schema) => (prefix) => {
|
|
418
473
|
const unwrapAll = (s) => {
|
|
@@ -532,7 +587,8 @@ var scopeParse = (result, path, viaSubschema) => {
|
|
|
532
587
|
var pruneUnreported = (prev, full) => {
|
|
533
588
|
const entries = Object.entries(prev);
|
|
534
589
|
const kept = entries.filter(([k]) => Object.hasOwn(full, k));
|
|
535
|
-
|
|
590
|
+
const unchanged = kept.length === entries.length && kept.every(([k, list]) => full[k] === list);
|
|
591
|
+
return unchanged ? prev : Object.fromEntries(kept.map(([k, list]) => [k, full[k] ?? list]));
|
|
536
592
|
};
|
|
537
593
|
var dropEmptyLists = (map) => {
|
|
538
594
|
const entries = Object.entries(map);
|
|
@@ -904,7 +960,14 @@ var createForm = (schema, options) => {
|
|
|
904
960
|
fresh.flatMap((path) => Object.entries(errorsFor(path)))
|
|
905
961
|
),
|
|
906
962
|
fresh,
|
|
907
|
-
|
|
963
|
+
// The prune map describes the values the batch STARTED with.
|
|
964
|
+
// A skip scope (an index into an empty array) still reads as
|
|
965
|
+
// fresh after an unrelated change, so a batch can commit under
|
|
966
|
+
// changed values; pruning with the stale map then dropped
|
|
967
|
+
// errors committed elsewhere in flight (an email error landing
|
|
968
|
+
// while a wizard step with such a path validated). The prune
|
|
969
|
+
// rides only while the whole values reference held.
|
|
970
|
+
parsed.kind === "full" && live.values === valuesAtStart ? parsed.errors : null
|
|
908
971
|
),
|
|
909
972
|
isValidating: omitKeys(state.isValidating, ownedKeys)
|
|
910
973
|
}));
|
|
@@ -1078,12 +1141,14 @@ var createForm = (schema, options) => {
|
|
|
1078
1141
|
}
|
|
1079
1142
|
}
|
|
1080
1143
|
};
|
|
1081
|
-
|
|
1144
|
+
const judgeBaseline = () => {
|
|
1145
|
+
if (options.validateOnMount !== true) return;
|
|
1082
1146
|
const result = validate();
|
|
1083
1147
|
if (result.kind === "pending") {
|
|
1084
1148
|
void result.promise.catch(reportBackgroundFailure);
|
|
1085
1149
|
}
|
|
1086
|
-
}
|
|
1150
|
+
};
|
|
1151
|
+
judgeBaseline();
|
|
1087
1152
|
return Object.freeze({
|
|
1088
1153
|
schema,
|
|
1089
1154
|
store,
|
|
@@ -1098,8 +1163,12 @@ var createForm = (schema, options) => {
|
|
|
1098
1163
|
return store.subscribe((state) => {
|
|
1099
1164
|
const next = snapshotField(state, path);
|
|
1100
1165
|
if (!shallowFieldEqual(ref.current, next)) {
|
|
1166
|
+
const prev = ref.current;
|
|
1101
1167
|
ref.current = next;
|
|
1102
|
-
listener(
|
|
1168
|
+
listener(
|
|
1169
|
+
next,
|
|
1170
|
+
prev
|
|
1171
|
+
);
|
|
1103
1172
|
}
|
|
1104
1173
|
});
|
|
1105
1174
|
},
|
|
@@ -1134,10 +1203,12 @@ var createForm = (schema, options) => {
|
|
|
1134
1203
|
if (Object.is(getAtPath(currentValues, path), value) && (value !== void 0 || hasOwnSlot(currentValues, path))) {
|
|
1135
1204
|
return;
|
|
1136
1205
|
}
|
|
1206
|
+
const next = setAtPath(currentValues, path, value, kindAt);
|
|
1207
|
+
if (next === currentValues) return;
|
|
1137
1208
|
clearArrayOpsUnder(store, path);
|
|
1138
1209
|
store.setState((state) => ({
|
|
1139
1210
|
...state,
|
|
1140
|
-
values:
|
|
1211
|
+
values: next,
|
|
1141
1212
|
...errorChannels(state, {
|
|
1142
1213
|
serverErrors: releaseSpine(state.serverErrors, path)
|
|
1143
1214
|
})
|
|
@@ -1244,6 +1315,7 @@ var createForm = (schema, options) => {
|
|
|
1244
1315
|
isValidating: emptyBools,
|
|
1245
1316
|
isValidatingForm: false
|
|
1246
1317
|
}));
|
|
1318
|
+
judgeBaseline();
|
|
1247
1319
|
},
|
|
1248
1320
|
reset: (nextInitial, resetOptions) => {
|
|
1249
1321
|
sequences.clear();
|
|
@@ -1268,6 +1340,7 @@ var createForm = (schema, options) => {
|
|
|
1268
1340
|
isValidatingForm: false
|
|
1269
1341
|
};
|
|
1270
1342
|
});
|
|
1343
|
+
if (resetOptions?.keepErrors !== true) judgeBaseline();
|
|
1271
1344
|
},
|
|
1272
1345
|
resetField: (path) => {
|
|
1273
1346
|
const initialSlot = slotAtPath(
|
|
@@ -1362,6 +1435,7 @@ var createForm = (schema, options) => {
|
|
|
1362
1435
|
},
|
|
1363
1436
|
snapshot: () => store.getState(),
|
|
1364
1437
|
restore: (snap) => {
|
|
1438
|
+
sequences.clear();
|
|
1365
1439
|
clearArrayOpsUnder(store, "");
|
|
1366
1440
|
store.setState(() => {
|
|
1367
1441
|
const schemaErrors = dropEmptyLists(snap.schemaErrors ?? emptyErrors);
|
|
@@ -1468,6 +1542,7 @@ var persistForm = (form, options) => {
|
|
|
1468
1542
|
const schema = form.schema;
|
|
1469
1543
|
const fillRequiredGaps = (draft, initial, path) => {
|
|
1470
1544
|
if (!isPlainObject(draft) || !isPlainObject(initial)) return draft;
|
|
1545
|
+
if (!isObjectSchemaAt(schema, path)) return draft;
|
|
1471
1546
|
return Object.keys(initial).reduce(
|
|
1472
1547
|
(acc, key) => {
|
|
1473
1548
|
const childPath = joinPath(path, key);
|
|
@@ -1477,21 +1552,34 @@ var persistForm = (form, options) => {
|
|
|
1477
1552
|
}
|
|
1478
1553
|
if (initial[key] === void 0) return acc;
|
|
1479
1554
|
const sub = fieldSchemaForIntrospection(schema, childPath);
|
|
1480
|
-
return sub !== null &&
|
|
1555
|
+
return sub !== null && schemaAcceptsUndefined(sub) ? acc : { ...acc, [key]: initial[key] };
|
|
1481
1556
|
},
|
|
1482
1557
|
draft
|
|
1483
1558
|
);
|
|
1484
1559
|
};
|
|
1560
|
+
const applying = { current: false };
|
|
1485
1561
|
const applyDraft = (values) => {
|
|
1486
1562
|
const filled = fillRequiredGaps(
|
|
1487
1563
|
values,
|
|
1488
1564
|
form.getState().initialValues,
|
|
1489
1565
|
""
|
|
1490
1566
|
);
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1567
|
+
applying.current = true;
|
|
1568
|
+
try {
|
|
1569
|
+
if (apply === "edit" || apply === "restore") {
|
|
1570
|
+
form.setValues(filled);
|
|
1571
|
+
} else {
|
|
1572
|
+
form.adoptValues(filled);
|
|
1573
|
+
}
|
|
1574
|
+
} finally {
|
|
1575
|
+
applying.current = false;
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
const isCurrent = (values) => {
|
|
1579
|
+
try {
|
|
1580
|
+
return JSON.stringify(values) === JSON.stringify(form.getState().values);
|
|
1581
|
+
} catch {
|
|
1582
|
+
return false;
|
|
1495
1583
|
}
|
|
1496
1584
|
};
|
|
1497
1585
|
const referenceShape = () => {
|
|
@@ -1502,7 +1590,7 @@ var persistForm = (form, options) => {
|
|
|
1502
1590
|
}
|
|
1503
1591
|
};
|
|
1504
1592
|
const flexibleAt = (path) => schemaAllowsKindChangeAt(schema, path);
|
|
1505
|
-
const inspect = () => {
|
|
1593
|
+
const inspect = (skipWhenCurrent) => {
|
|
1506
1594
|
const raw = (() => {
|
|
1507
1595
|
try {
|
|
1508
1596
|
return storage === null ? null : storage.getItem(options.key);
|
|
@@ -1548,14 +1636,15 @@ var persistForm = (form, options) => {
|
|
|
1548
1636
|
reason: `its shape conflicts with the form's initial values at "${conflict}" (set the version option when the schema changes)`
|
|
1549
1637
|
};
|
|
1550
1638
|
}
|
|
1639
|
+
if (skipWhenCurrent && isCurrent(values)) return { kind: "applied" };
|
|
1551
1640
|
applyDraft(values);
|
|
1552
1641
|
return { kind: "applied" };
|
|
1553
1642
|
} catch {
|
|
1554
1643
|
return { kind: "ignored", reason: "it is not valid JSON" };
|
|
1555
1644
|
}
|
|
1556
1645
|
};
|
|
1557
|
-
const
|
|
1558
|
-
const outcome = inspect();
|
|
1646
|
+
const loadWith = (skipWhenCurrent) => {
|
|
1647
|
+
const outcome = inspect(skipWhenCurrent);
|
|
1559
1648
|
if (outcome.kind === "ignored" && isDevBuild()) {
|
|
1560
1649
|
console.warn(
|
|
1561
1650
|
`[formstand] persistForm: ignored the draft under "${options.key}": ${outcome.reason}. The next edit overwrites it.`
|
|
@@ -1563,6 +1652,7 @@ var persistForm = (form, options) => {
|
|
|
1563
1652
|
}
|
|
1564
1653
|
return outcome.kind === "applied";
|
|
1565
1654
|
};
|
|
1655
|
+
const load = () => loadWith(false);
|
|
1566
1656
|
const writeDraft = (values) => {
|
|
1567
1657
|
try {
|
|
1568
1658
|
storage?.setItem(
|
|
@@ -1576,10 +1666,11 @@ var persistForm = (form, options) => {
|
|
|
1576
1666
|
};
|
|
1577
1667
|
const writes = createDebouncer(debounceMs);
|
|
1578
1668
|
if (apply !== "manual") {
|
|
1579
|
-
|
|
1669
|
+
loadWith(true);
|
|
1580
1670
|
}
|
|
1581
1671
|
const cleared = { current: false };
|
|
1582
1672
|
const unsubscribe = form.watchValues((values) => {
|
|
1673
|
+
if (applying.current) return;
|
|
1583
1674
|
if (cleared.current && values === form.getState().initialValues) return;
|
|
1584
1675
|
cleared.current = false;
|
|
1585
1676
|
writes.schedule(() => writeDraft(values));
|
|
@@ -1608,15 +1699,27 @@ var usePersistForm = (form, options) => {
|
|
|
1608
1699
|
latestOptions.current = options;
|
|
1609
1700
|
});
|
|
1610
1701
|
const key = options.key;
|
|
1611
|
-
const
|
|
1702
|
+
const hasStorage = options.storage !== void 0;
|
|
1703
|
+
const storageProxy = useMemo(
|
|
1704
|
+
() => ({
|
|
1705
|
+
getItem: (k) => latestOptions.current.storage?.getItem(k) ?? null,
|
|
1706
|
+
setItem: (k, v) => latestOptions.current.storage?.setItem(k, v),
|
|
1707
|
+
removeItem: (k) => latestOptions.current.storage?.removeItem(k)
|
|
1708
|
+
}),
|
|
1709
|
+
[]
|
|
1710
|
+
);
|
|
1612
1711
|
useEffect(() => {
|
|
1613
|
-
const
|
|
1712
|
+
const options2 = latestOptions.current;
|
|
1713
|
+
const handle = persistForm(form, {
|
|
1714
|
+
...options2,
|
|
1715
|
+
...options2.storage === void 0 ? {} : { storage: storageProxy }
|
|
1716
|
+
});
|
|
1614
1717
|
inner.current = handle;
|
|
1615
1718
|
return () => {
|
|
1616
1719
|
inner.current = null;
|
|
1617
1720
|
handle.dispose();
|
|
1618
1721
|
};
|
|
1619
|
-
}, [form, key,
|
|
1722
|
+
}, [form, key, hasStorage, storageProxy]);
|
|
1620
1723
|
return useMemo(
|
|
1621
1724
|
() => ({
|
|
1622
1725
|
load: () => inner.current?.load() ?? false,
|
|
@@ -1688,6 +1791,12 @@ var numberInputProps = (field) => ({
|
|
|
1688
1791
|
});
|
|
1689
1792
|
var dateToInputText = (value) => {
|
|
1690
1793
|
if (value === void 0 || value === null) return "";
|
|
1794
|
+
if (typeof value === "string") {
|
|
1795
|
+
const text = value;
|
|
1796
|
+
const parsed = parseDateText(text);
|
|
1797
|
+
if (parsed.kind === "date") return dateToInputText(parsed.value);
|
|
1798
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(text.trim())) return "";
|
|
1799
|
+
}
|
|
1691
1800
|
const date = value instanceof Date ? value : new Date(value);
|
|
1692
1801
|
if (Number.isNaN(date.getTime())) return "";
|
|
1693
1802
|
const pad = (part) => String(part).padStart(2, "0");
|
|
@@ -1788,6 +1897,15 @@ var useMaskedInput = (field, options) => {
|
|
|
1788
1897
|
}
|
|
1789
1898
|
};
|
|
1790
1899
|
};
|
|
1900
|
+
var settleOnLiveValues = async (form, fields, round = 0) => {
|
|
1901
|
+
const before = fields.map((path) => form.getField(path));
|
|
1902
|
+
const result = await form.validateFieldsAsync(fields);
|
|
1903
|
+
const changed = fields.some(
|
|
1904
|
+
(path, i) => !Object.is(form.getField(path), before[i])
|
|
1905
|
+
);
|
|
1906
|
+
if (!changed) return result;
|
|
1907
|
+
return round < 3 ? settleOnLiveValues(form, fields, round + 1) : null;
|
|
1908
|
+
};
|
|
1791
1909
|
var useFormSteps = (form, steps) => {
|
|
1792
1910
|
const [step, setStep] = useState(0);
|
|
1793
1911
|
const [visited, setVisited] = useState(
|
|
@@ -1801,7 +1919,8 @@ var useFormSteps = (form, steps) => {
|
|
|
1801
1919
|
async (index) => {
|
|
1802
1920
|
const definition = latest.current[index];
|
|
1803
1921
|
if (definition === void 0) return true;
|
|
1804
|
-
const result = await form
|
|
1922
|
+
const result = await settleOnLiveValues(form, definition.fields);
|
|
1923
|
+
if (result === null) return false;
|
|
1805
1924
|
if (result.kind === "invalid") {
|
|
1806
1925
|
Object.keys(result.errors).forEach((key) => {
|
|
1807
1926
|
form.setTouched(key, true);
|
|
@@ -1813,46 +1932,49 @@ var useFormSteps = (form, steps) => {
|
|
|
1813
1932
|
[form]
|
|
1814
1933
|
);
|
|
1815
1934
|
const currentStep = useRef(step);
|
|
1816
|
-
|
|
1817
|
-
currentStep.current =
|
|
1818
|
-
|
|
1935
|
+
const navigate = useCallback((target) => {
|
|
1936
|
+
currentStep.current = target;
|
|
1937
|
+
setStep(target);
|
|
1938
|
+
}, []);
|
|
1819
1939
|
const next = useCallback(async () => {
|
|
1820
|
-
const
|
|
1940
|
+
const from = currentStep.current;
|
|
1941
|
+
const ok = await validateStep(from);
|
|
1821
1942
|
if (!ok) return false;
|
|
1822
|
-
if (currentStep.current !==
|
|
1823
|
-
const target = Math.min(
|
|
1824
|
-
|
|
1943
|
+
if (currentStep.current !== from) return false;
|
|
1944
|
+
const target = Math.max(0, Math.min(from + 1, latest.current.length - 1));
|
|
1945
|
+
navigate(target);
|
|
1825
1946
|
setVisited((prev) => /* @__PURE__ */ new Set([...prev, target]));
|
|
1826
1947
|
return true;
|
|
1827
|
-
}, [validateStep,
|
|
1948
|
+
}, [validateStep, navigate]);
|
|
1828
1949
|
const back = useCallback(() => {
|
|
1829
|
-
|
|
1830
|
-
}, []);
|
|
1950
|
+
navigate(Math.max(currentStep.current - 1, 0));
|
|
1951
|
+
}, [navigate]);
|
|
1831
1952
|
const goTo = useCallback(
|
|
1832
1953
|
async (target) => {
|
|
1833
1954
|
const count = latest.current.length;
|
|
1834
1955
|
if (!Number.isFinite(target)) return currentStep.current;
|
|
1956
|
+
const from = currentStep.current;
|
|
1835
1957
|
const clamped = Math.max(0, Math.min(Math.trunc(target), count - 1));
|
|
1836
|
-
if (clamped <=
|
|
1837
|
-
|
|
1958
|
+
if (clamped <= from) {
|
|
1959
|
+
navigate(clamped);
|
|
1838
1960
|
return clamped;
|
|
1839
1961
|
}
|
|
1840
|
-
const walk = async (
|
|
1841
|
-
if (
|
|
1842
|
-
return await validateStep(
|
|
1962
|
+
const walk = async (at) => {
|
|
1963
|
+
if (at >= clamped) return clamped;
|
|
1964
|
+
return await validateStep(at) ? walk(at + 1) : at;
|
|
1843
1965
|
};
|
|
1844
|
-
const landed = await walk(
|
|
1845
|
-
if (currentStep.current !==
|
|
1846
|
-
|
|
1966
|
+
const landed = await walk(from);
|
|
1967
|
+
if (currentStep.current !== from) return currentStep.current;
|
|
1968
|
+
navigate(landed);
|
|
1847
1969
|
setVisited(
|
|
1848
1970
|
(prev) => /* @__PURE__ */ new Set([
|
|
1849
1971
|
...prev,
|
|
1850
|
-
...Array.from({ length: landed -
|
|
1972
|
+
...Array.from({ length: landed - from + 1 }, (_, i) => from + i)
|
|
1851
1973
|
])
|
|
1852
1974
|
);
|
|
1853
1975
|
return landed;
|
|
1854
1976
|
},
|
|
1855
|
-
[validateStep,
|
|
1977
|
+
[validateStep, navigate]
|
|
1856
1978
|
);
|
|
1857
1979
|
const errorFlags = useFormSelectorShallow(
|
|
1858
1980
|
form,
|
|
@@ -1945,6 +2067,7 @@ var createFormContext = () => {
|
|
|
1945
2067
|
return { Provider, useFormContext };
|
|
1946
2068
|
};
|
|
1947
2069
|
function useField(form, pathArg, options) {
|
|
2070
|
+
const formSchema = form.schema;
|
|
1948
2071
|
const slice = useStore(
|
|
1949
2072
|
form.store,
|
|
1950
2073
|
useShallow((state) => {
|
|
@@ -1960,25 +2083,26 @@ function useField(form, pathArg, options) {
|
|
|
1960
2083
|
error: entryAt(state.errors, p),
|
|
1961
2084
|
touched: flagAt(state.touched, p),
|
|
1962
2085
|
dirty: isFieldDirty(value, initialValue2),
|
|
1963
|
-
isValidating: flagAt(state.isValidating, p)
|
|
2086
|
+
isValidating: flagAt(state.isValidating, p),
|
|
2087
|
+
// Resolved inside the selector so a discriminant change re-reads
|
|
2088
|
+
// the ACTIVE variant's wrapper for a key the variants share; the
|
|
2089
|
+
// schema node is a stable reference, so the shallow compare holds.
|
|
2090
|
+
schema: formSchema === void 0 ? null : fieldSchemaForIntrospection(formSchema, p, state.values)
|
|
1964
2091
|
};
|
|
1965
2092
|
})
|
|
1966
2093
|
);
|
|
1967
2094
|
const path = slice.path;
|
|
1968
|
-
const
|
|
2095
|
+
const sub = slice.schema;
|
|
1969
2096
|
const initialValue = slice.initialValue;
|
|
1970
2097
|
const { emptyValue, clearable } = useMemo(() => {
|
|
1971
|
-
if (
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
clearable: isClearableSchema(sub)
|
|
1977
|
-
};
|
|
1978
|
-
}
|
|
2098
|
+
if (sub !== null) {
|
|
2099
|
+
return {
|
|
2100
|
+
emptyValue: emptyValueForSchema(sub),
|
|
2101
|
+
clearable: isClearableSchema(sub)
|
|
2102
|
+
};
|
|
1979
2103
|
}
|
|
1980
2104
|
return initialValue === null ? { emptyValue: null, clearable: true } : { emptyValue: void 0, clearable: false };
|
|
1981
|
-
}, [
|
|
2105
|
+
}, [sub, initialValue]);
|
|
1982
2106
|
const debounceMs = options?.debounceMs;
|
|
1983
2107
|
const debouncerRef = useRef(null);
|
|
1984
2108
|
useEffect(() => {
|
|
@@ -2088,10 +2212,11 @@ function useField(form, pathArg, options) {
|
|
|
2088
2212
|
}
|
|
2089
2213
|
|
|
2090
2214
|
// src/react/useVariantField.ts
|
|
2091
|
-
function useVariantField(form, unionPath, field) {
|
|
2215
|
+
function useVariantField(form, unionPath, field, options) {
|
|
2092
2216
|
return useField(
|
|
2093
2217
|
form,
|
|
2094
|
-
`${unionPath}.${field}
|
|
2218
|
+
`${unionPath}.${field}`,
|
|
2219
|
+
options
|
|
2095
2220
|
);
|
|
2096
2221
|
}
|
|
2097
2222
|
var EMPTY_ITEMS = [];
|
|
@@ -2254,6 +2379,12 @@ function useFieldArray(form, pathArg) {
|
|
|
2254
2379
|
const items = slice.items;
|
|
2255
2380
|
const error = slice.error;
|
|
2256
2381
|
const ids = deriveIds(form.store, path, items).ids;
|
|
2382
|
+
const validateNow = useCallback(() => {
|
|
2383
|
+
const result = form.validateField?.(path);
|
|
2384
|
+
if (result?.kind === "pending") {
|
|
2385
|
+
void result.promise.catch(reportBackgroundFailure);
|
|
2386
|
+
}
|
|
2387
|
+
}, [form, path]);
|
|
2257
2388
|
const revalidate = useCallback(() => {
|
|
2258
2389
|
const state = form.store.getState();
|
|
2259
2390
|
if (shouldValidateOn(
|
|
@@ -2263,9 +2394,9 @@ function useFieldArray(form, pathArg) {
|
|
|
2263
2394
|
state.submitCount > 0,
|
|
2264
2395
|
flagAt(state.touched, path)
|
|
2265
2396
|
)) {
|
|
2266
|
-
|
|
2397
|
+
validateNow();
|
|
2267
2398
|
}
|
|
2268
|
-
}, [form, path]);
|
|
2399
|
+
}, [form, path, validateNow]);
|
|
2269
2400
|
const push = useCallback(
|
|
2270
2401
|
(item) => {
|
|
2271
2402
|
form.arrayPush(path, item);
|
|
@@ -2330,13 +2461,17 @@ function useFieldArray(form, pathArg) {
|
|
|
2330
2461
|
state.submitCount > 0,
|
|
2331
2462
|
true
|
|
2332
2463
|
)) {
|
|
2333
|
-
|
|
2464
|
+
validateNow();
|
|
2334
2465
|
}
|
|
2335
|
-
}, [form, path]);
|
|
2466
|
+
}, [form, path, validateNow]);
|
|
2336
2467
|
const validate = useCallback(
|
|
2337
2468
|
() => form.validateField?.(path) ?? { kind: "valid" },
|
|
2338
2469
|
[form, path]
|
|
2339
2470
|
);
|
|
2471
|
+
const validateAsync2 = useCallback(
|
|
2472
|
+
() => form.validateFieldAsync?.(path) ?? Promise.resolve({ kind: "valid" }),
|
|
2473
|
+
[form, path]
|
|
2474
|
+
);
|
|
2340
2475
|
const replace = useCallback(
|
|
2341
2476
|
(next) => {
|
|
2342
2477
|
form.setValue?.(path, next);
|
|
@@ -2346,7 +2481,15 @@ function useFieldArray(form, pathArg) {
|
|
|
2346
2481
|
);
|
|
2347
2482
|
const update = useCallback(
|
|
2348
2483
|
(index, item) => {
|
|
2349
|
-
form.
|
|
2484
|
+
const current = getAtPath(form.store.getState().values, path);
|
|
2485
|
+
const length = Array.isArray(current) ? current.length : 0;
|
|
2486
|
+
if (!Number.isInteger(index) || index < 0 || index >= length) {
|
|
2487
|
+
console.warn(
|
|
2488
|
+
`[formstand] array op on "${path}" with an out-of-range or non-integer index (length ${String(length)}). Operation skipped.`
|
|
2489
|
+
);
|
|
2490
|
+
return;
|
|
2491
|
+
}
|
|
2492
|
+
form.setValue?.(`${path}.${String(index)}`, item);
|
|
2350
2493
|
revalidate();
|
|
2351
2494
|
},
|
|
2352
2495
|
[form, path, revalidate]
|
|
@@ -2378,6 +2521,7 @@ function useFieldArray(form, pathArg) {
|
|
|
2378
2521
|
setTouched,
|
|
2379
2522
|
onBlur,
|
|
2380
2523
|
validate,
|
|
2524
|
+
validateAsync: validateAsync2,
|
|
2381
2525
|
push,
|
|
2382
2526
|
remove,
|
|
2383
2527
|
insert,
|
|
@@ -2401,6 +2545,7 @@ function useFieldArray(form, pathArg) {
|
|
|
2401
2545
|
setTouched,
|
|
2402
2546
|
onBlur,
|
|
2403
2547
|
validate,
|
|
2548
|
+
validateAsync2,
|
|
2404
2549
|
push,
|
|
2405
2550
|
remove,
|
|
2406
2551
|
insert,
|
|
@@ -2428,7 +2573,7 @@ var useFormError = (form) => useStore(
|
|
|
2428
2573
|
function useFormValues(form) {
|
|
2429
2574
|
return useStore(form.store, (state) => state.values);
|
|
2430
2575
|
}
|
|
2431
|
-
function useFields(form, paths) {
|
|
2576
|
+
function useFields(form, paths, options) {
|
|
2432
2577
|
const [arity] = useState(paths.length);
|
|
2433
2578
|
if (paths.length !== arity) {
|
|
2434
2579
|
throw new Error(
|
|
@@ -2438,7 +2583,11 @@ function useFields(form, paths) {
|
|
|
2438
2583
|
const fields = paths.map(
|
|
2439
2584
|
(path) => (
|
|
2440
2585
|
// 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
|
|
2441
|
-
useField(
|
|
2586
|
+
useField(
|
|
2587
|
+
form,
|
|
2588
|
+
path,
|
|
2589
|
+
options
|
|
2590
|
+
)
|
|
2442
2591
|
)
|
|
2443
2592
|
);
|
|
2444
2593
|
return useMemo(() => {
|
|
@@ -2501,10 +2650,11 @@ var capitalize = (name) => name.length === 0 ? name : name.charAt(0).toUpperCase
|
|
|
2501
2650
|
var createFormHooks = (form, name) => {
|
|
2502
2651
|
const structural = form;
|
|
2503
2652
|
const useBoundField = (path, options) => useField(structural, path, options);
|
|
2504
|
-
const useBoundVariantField = (unionPath, field) => useVariantField(
|
|
2653
|
+
const useBoundVariantField = (unionPath, field, options) => useVariantField(
|
|
2505
2654
|
structural,
|
|
2506
2655
|
unionPath,
|
|
2507
|
-
field
|
|
2656
|
+
field,
|
|
2657
|
+
options
|
|
2508
2658
|
);
|
|
2509
2659
|
const useBoundFieldArray = (path) => useFieldArray(structural, path);
|
|
2510
2660
|
const useBoundVariantFieldArray = (unionPath, field) => useVariantFieldArray(
|
|
@@ -2520,9 +2670,10 @@ var createFormHooks = (form, name) => {
|
|
|
2520
2670
|
const useBoundIsValidating = (path) => useIsValidating(structural, path);
|
|
2521
2671
|
const useBoundIsSubmitting = () => useIsSubmitting(structural);
|
|
2522
2672
|
const useBoundValues = () => useFormValues(structural);
|
|
2523
|
-
const useBoundFields = (paths) => useFields(
|
|
2673
|
+
const useBoundFields = (paths, options) => useFields(
|
|
2524
2674
|
structural,
|
|
2525
|
-
paths
|
|
2675
|
+
paths,
|
|
2676
|
+
options
|
|
2526
2677
|
);
|
|
2527
2678
|
const useBoundSubmitCount = () => useSubmitCount(structural);
|
|
2528
2679
|
const useBoundSteps = (steps) => useFormSteps(form, steps);
|
|
@@ -2572,7 +2723,7 @@ var FieldShell = ({
|
|
|
2572
2723
|
wrapLabel,
|
|
2573
2724
|
children
|
|
2574
2725
|
}) => /* @__PURE__ */ jsxs("div", { className: "zf-field", children: [
|
|
2575
|
-
wrapLabel === true ? /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "zf-label", children: [
|
|
2726
|
+
wrapLabel === true && label !== void 0 ? /* @__PURE__ */ jsxs("label", { htmlFor: id, className: "zf-label", children: [
|
|
2576
2727
|
children,
|
|
2577
2728
|
" ",
|
|
2578
2729
|
label
|