formstand 0.9.0 → 0.11.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 +622 -1
- package/README.md +17 -13
- package/dist/index.cjs +485 -271
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -57
- package/dist/index.d.ts +117 -57
- package/dist/index.js +486 -274
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/dist/index.cjs
CHANGED
|
@@ -20,6 +20,20 @@ var moveFromTo = (from, to) => (n) => {
|
|
|
20
20
|
return n;
|
|
21
21
|
};
|
|
22
22
|
var swapIndices = (a, b) => (n) => n === a ? b : n === b ? a : n;
|
|
23
|
+
var invertMapper = (mapper, oldLength, newLength) => {
|
|
24
|
+
const oldByNew = new Map(
|
|
25
|
+
Array.from({ length: oldLength }, (_, oldIndex) => oldIndex).flatMap(
|
|
26
|
+
(oldIndex) => {
|
|
27
|
+
const newIndex = mapper(oldIndex);
|
|
28
|
+
return newIndex === null ? [] : [[newIndex, oldIndex]];
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
return Array.from(
|
|
33
|
+
{ length: newLength },
|
|
34
|
+
(_, newIndex) => oldByNew.get(newIndex) ?? -1
|
|
35
|
+
);
|
|
36
|
+
};
|
|
23
37
|
var indexAndTail = (rest) => {
|
|
24
38
|
const dotIdx = rest.indexOf(".");
|
|
25
39
|
const idxStr = dotIdx === -1 ? rest : rest.slice(0, dotIdx);
|
|
@@ -43,6 +57,53 @@ var reKeyByArrayPath = (map, basePath, mapper) => {
|
|
|
43
57
|
);
|
|
44
58
|
};
|
|
45
59
|
|
|
60
|
+
// src/core/arrayOpLog.ts
|
|
61
|
+
var OP_LOG_LIMIT = 16;
|
|
62
|
+
var MAX_LOG_PATHS = 256;
|
|
63
|
+
var composeOps = (first, second) => ({
|
|
64
|
+
from: first.from,
|
|
65
|
+
to: second.to,
|
|
66
|
+
mapping: second.mapping.map(
|
|
67
|
+
(mid) => mid === -1 ? -1 : first.mapping[mid] ?? -1
|
|
68
|
+
)
|
|
69
|
+
});
|
|
70
|
+
var logs = /* @__PURE__ */ new WeakMap();
|
|
71
|
+
var boundOps = (ops) => {
|
|
72
|
+
const [head, second] = ops;
|
|
73
|
+
if (ops.length <= OP_LOG_LIMIT || head === void 0 || second === void 0) {
|
|
74
|
+
return ops;
|
|
75
|
+
}
|
|
76
|
+
return head.to === second.from ? [composeOps(head, second), ...ops.slice(2)] : ops.slice(1);
|
|
77
|
+
};
|
|
78
|
+
var recordArrayOp = (store, path, record) => {
|
|
79
|
+
const existing = logs.get(store);
|
|
80
|
+
const byPath = existing ?? /* @__PURE__ */ new Map();
|
|
81
|
+
if (existing === void 0) logs.set(store, byPath);
|
|
82
|
+
const bounded = boundOps([...byPath.get(path) ?? [], record]);
|
|
83
|
+
byPath.delete(path);
|
|
84
|
+
byPath.set(path, bounded);
|
|
85
|
+
if (byPath.size > MAX_LOG_PATHS) {
|
|
86
|
+
const oldest = byPath.keys().next().value;
|
|
87
|
+
if (oldest !== void 0) byPath.delete(oldest);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var arrayOpsFor = (store, path) => logs.get(store)?.get(path) ?? [];
|
|
91
|
+
var clearArrayOps = (store, path) => {
|
|
92
|
+
logs.get(store)?.delete(path);
|
|
93
|
+
};
|
|
94
|
+
var clearArrayOpsUnder = (store, path, keepSelf = false) => {
|
|
95
|
+
const byPath = logs.get(store);
|
|
96
|
+
if (byPath === void 0) return;
|
|
97
|
+
if (path === "") {
|
|
98
|
+
byPath.clear();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const related = [...byPath.keys()].filter(
|
|
102
|
+
(p) => p === path && !keepSelf || p.startsWith(`${path}.`)
|
|
103
|
+
);
|
|
104
|
+
related.forEach((p) => byPath.delete(p));
|
|
105
|
+
};
|
|
106
|
+
|
|
46
107
|
// src/core/equality.ts
|
|
47
108
|
var isPlainObject = (v) => {
|
|
48
109
|
if (typeof v !== "object" || v === null) return false;
|
|
@@ -178,7 +239,7 @@ var expandIssue = (issue, prefix) => {
|
|
|
178
239
|
return [{ path: issuePathToFormPath(at), message: issue.message }];
|
|
179
240
|
};
|
|
180
241
|
var flattenIssues = (issues) => issues.flatMap((issue) => expandIssue(issue, [])).reduce((acc, { path, message }) => {
|
|
181
|
-
const existing = acc[path] ?? [];
|
|
242
|
+
const existing = (Object.hasOwn(acc, path) ? acc[path] : void 0) ?? [];
|
|
182
243
|
return existing.includes(message) ? acc : { ...acc, [path]: [...existing, message] };
|
|
183
244
|
}, {});
|
|
184
245
|
var validateSync = (schema, values) => {
|
|
@@ -311,7 +372,9 @@ var reuseErrorRefs = (next, prev) => {
|
|
|
311
372
|
};
|
|
312
373
|
var scopeSettledResult = (result, path, viaSubschema) => result.kind !== "invalid" ? emptyErrors : viaSubschema ? prefixErrorKeys(result.errors, path) : path === "" ? result.errors : pickScope(result.errors, [path]);
|
|
313
374
|
var mergeErrorChannels = (schema, server) => {
|
|
314
|
-
const extra = Object.entries(server).filter(
|
|
375
|
+
const extra = Object.entries(server).filter(
|
|
376
|
+
([k]) => !Object.hasOwn(schema, k)
|
|
377
|
+
);
|
|
315
378
|
return extra.length === 0 ? schema : { ...schema, ...Object.fromEntries(extra) };
|
|
316
379
|
};
|
|
317
380
|
var errorChannels = (state, patch) => {
|
|
@@ -370,8 +433,11 @@ var createForm = (schema, options) => {
|
|
|
370
433
|
schemaErrors: reuseErrorRefs(next, state.schemaErrors)
|
|
371
434
|
});
|
|
372
435
|
const validate = () => {
|
|
436
|
+
if (asyncScopes.has("")) {
|
|
437
|
+
return { kind: "pending", promise: validateAsyncOnForm() };
|
|
438
|
+
}
|
|
373
439
|
try {
|
|
374
|
-
const result =
|
|
440
|
+
const result = validateSyncLatched("", schema, store.getState().values);
|
|
375
441
|
store.setState((state) => ({
|
|
376
442
|
...state,
|
|
377
443
|
...commitFullPass(
|
|
@@ -388,25 +454,32 @@ var createForm = (schema, options) => {
|
|
|
388
454
|
}
|
|
389
455
|
};
|
|
390
456
|
const validateFields = (paths) => {
|
|
391
|
-
if (paths.length === 0) return
|
|
457
|
+
if (paths.length === 0) return { kind: "valid" };
|
|
392
458
|
if (paths.some((p) => p === "")) {
|
|
393
459
|
const result = validate();
|
|
394
|
-
return result.kind === "pending" ? result.promise.then(
|
|
460
|
+
return result.kind === "pending" ? { kind: "pending", promise: result.promise.then(settledFormResult) } : settledFormResult(result);
|
|
461
|
+
}
|
|
462
|
+
const values = store.getState().values;
|
|
463
|
+
const scopes = paths.map((path) => ({
|
|
464
|
+
path,
|
|
465
|
+
scope: fieldScopeFor(path, values)
|
|
466
|
+
}));
|
|
467
|
+
const allViaSubschema = scopes.every(
|
|
468
|
+
({ scope }) => scope.kind === "skip" || scope.kind === "parse" && scope.viaSubschema
|
|
469
|
+
);
|
|
470
|
+
const anyLatched = allViaSubschema ? scopes.some(
|
|
471
|
+
({ path, scope }) => scope.kind === "parse" && asyncScopes.has(path)
|
|
472
|
+
) : asyncScopes.has("");
|
|
473
|
+
if (anyLatched) {
|
|
474
|
+
return { kind: "pending", promise: validateFieldsAsync(paths) };
|
|
395
475
|
}
|
|
396
476
|
try {
|
|
397
|
-
|
|
398
|
-
const scopes = paths.map((path) => ({
|
|
399
|
-
path,
|
|
400
|
-
scope: fieldScopeFor(path, values)
|
|
401
|
-
}));
|
|
402
|
-
if (scopes.every(
|
|
403
|
-
({ scope }) => scope.kind === "skip" || scope.kind === "parse" && scope.viaSubschema
|
|
404
|
-
)) {
|
|
477
|
+
if (allViaSubschema) {
|
|
405
478
|
const merged = scopes.reduce(
|
|
406
479
|
(acc, { path, scope }) => scope.kind === "skip" ? acc : {
|
|
407
480
|
...acc,
|
|
408
481
|
...scopeSettledResult(
|
|
409
|
-
|
|
482
|
+
validateSyncLatched(path, scope.schema, scope.value),
|
|
410
483
|
path,
|
|
411
484
|
true
|
|
412
485
|
)
|
|
@@ -415,37 +488,31 @@ var createForm = (schema, options) => {
|
|
|
415
488
|
);
|
|
416
489
|
store.setState((state) => ({
|
|
417
490
|
...state,
|
|
418
|
-
...
|
|
419
|
-
schemaErrors: reuseErrorRefs(
|
|
420
|
-
mergeScopedErrors(state.schemaErrors, merged, paths),
|
|
421
|
-
state.schemaErrors
|
|
422
|
-
),
|
|
423
|
-
serverErrors: omitScope(state.serverErrors, paths)
|
|
424
|
-
})
|
|
491
|
+
...commitScopedErrors(state, merged, paths)
|
|
425
492
|
}));
|
|
426
|
-
return
|
|
493
|
+
return settledFields(merged);
|
|
427
494
|
}
|
|
428
|
-
const result =
|
|
495
|
+
const result = validateSyncLatched("", schema, values);
|
|
429
496
|
const fullErrors = result.kind === "invalid" ? result.errors : emptyErrors;
|
|
497
|
+
const requestedErrors = pickScope(fullErrors, paths);
|
|
430
498
|
store.setState((state) => ({
|
|
431
499
|
...state,
|
|
432
|
-
...
|
|
433
|
-
schemaErrors: reuseErrorRefs(
|
|
434
|
-
mergeScopedErrors(
|
|
435
|
-
state.schemaErrors,
|
|
436
|
-
pickScope(fullErrors, paths),
|
|
437
|
-
paths
|
|
438
|
-
),
|
|
439
|
-
state.schemaErrors
|
|
440
|
-
),
|
|
441
|
-
// A validation explicitly targeting these paths supersedes any
|
|
442
|
-
// server verdict on them.
|
|
443
|
-
serverErrors: omitScope(state.serverErrors, paths)
|
|
444
|
-
})
|
|
500
|
+
...commitScopedErrors(state, requestedErrors, paths)
|
|
445
501
|
}));
|
|
446
|
-
return
|
|
502
|
+
return settledFields(requestedErrors);
|
|
503
|
+
} catch (e) {
|
|
504
|
+
if (isAsyncRequiredError(e)) {
|
|
505
|
+
return { kind: "pending", promise: validateFieldsAsync(paths) };
|
|
506
|
+
}
|
|
507
|
+
throw e;
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
const asyncScopes = /* @__PURE__ */ new Set();
|
|
511
|
+
const validateSyncLatched = (key, scopeSchema, value) => {
|
|
512
|
+
try {
|
|
513
|
+
return validateSync(scopeSchema, value);
|
|
447
514
|
} catch (e) {
|
|
448
|
-
if (isAsyncRequiredError(e))
|
|
515
|
+
if (isAsyncRequiredError(e)) asyncScopes.add(key);
|
|
449
516
|
throw e;
|
|
450
517
|
}
|
|
451
518
|
};
|
|
@@ -475,15 +542,6 @@ var createForm = (schema, options) => {
|
|
|
475
542
|
const slot = slotAtPath(values, path);
|
|
476
543
|
return slot.exists ? { kind: "parse", schema: sub, value: slot.value, viaSubschema: true } : { kind: "skip" };
|
|
477
544
|
};
|
|
478
|
-
const scopedFieldErrors = (path) => {
|
|
479
|
-
const values = store.getState().values;
|
|
480
|
-
const scope = fieldScopeFor(path, values);
|
|
481
|
-
return scope.kind === "skip" ? emptyErrors : scopeSettledResult(
|
|
482
|
-
validateSync(scope.schema, scope.value),
|
|
483
|
-
path,
|
|
484
|
-
scope.viaSubschema
|
|
485
|
-
);
|
|
486
|
-
};
|
|
487
545
|
const scopedFieldErrorsAsync = async (path, values) => {
|
|
488
546
|
const scope = fieldScopeFor(path, values);
|
|
489
547
|
return scope.kind === "skip" ? emptyErrors : scopeSettledResult(
|
|
@@ -496,17 +554,32 @@ var createForm = (schema, options) => {
|
|
|
496
554
|
const messages = Object.values(scoped).flat();
|
|
497
555
|
return messages.length === 0 ? { kind: "valid" } : { kind: "invalid", errors: messages };
|
|
498
556
|
};
|
|
499
|
-
const
|
|
557
|
+
const settledFields = (scoped) => Object.keys(scoped).length === 0 ? { kind: "valid" } : { kind: "invalid", errors: scoped };
|
|
558
|
+
const settledFormResult = (result) => settledFields(result.kind === "invalid" ? result.errors : emptyErrors);
|
|
559
|
+
const commitScopedErrors = (state, scoped, paths) => errorChannels(state, {
|
|
500
560
|
schemaErrors: reuseErrorRefs(
|
|
501
|
-
mergeScopedErrors(state.schemaErrors, scoped,
|
|
561
|
+
mergeScopedErrors(state.schemaErrors, scoped, paths),
|
|
502
562
|
state.schemaErrors
|
|
503
563
|
),
|
|
504
|
-
serverErrors: omitScope(state.serverErrors,
|
|
564
|
+
serverErrors: omitScope(state.serverErrors, paths)
|
|
505
565
|
});
|
|
566
|
+
const commitFieldErrors = (state, scoped, path) => path === "" ? commitFullPass(state, scoped) : commitScopedErrors(state, scoped, [path]);
|
|
506
567
|
const validateField = (path) => {
|
|
507
568
|
warnMissingSchemaPath("validateField", path);
|
|
569
|
+
const scope = fieldScopeFor(path, store.getState().values);
|
|
570
|
+
if (scope.kind === "parse" && asyncScopes.has(scope.viaSubschema ? path : "")) {
|
|
571
|
+
return { kind: "pending", promise: validateFieldAsync(path) };
|
|
572
|
+
}
|
|
508
573
|
try {
|
|
509
|
-
const scoped =
|
|
574
|
+
const scoped = scope.kind === "skip" ? emptyErrors : scopeSettledResult(
|
|
575
|
+
validateSyncLatched(
|
|
576
|
+
scope.viaSubschema ? path : "",
|
|
577
|
+
scope.schema,
|
|
578
|
+
scope.value
|
|
579
|
+
),
|
|
580
|
+
path,
|
|
581
|
+
scope.viaSubschema
|
|
582
|
+
);
|
|
510
583
|
store.setState((state) => ({
|
|
511
584
|
...state,
|
|
512
585
|
...commitFieldErrors(state, scoped, path)
|
|
@@ -559,10 +632,9 @@ var createForm = (schema, options) => {
|
|
|
559
632
|
return result;
|
|
560
633
|
};
|
|
561
634
|
const validateFieldsAsync = async (paths) => {
|
|
562
|
-
if (paths.length === 0) return
|
|
635
|
+
if (paths.length === 0) return { kind: "valid" };
|
|
563
636
|
if (paths.some((p) => p === "")) {
|
|
564
|
-
|
|
565
|
-
return result2.kind === "valid";
|
|
637
|
+
return settledFormResult(await validateAsyncOnForm());
|
|
566
638
|
}
|
|
567
639
|
const passes = new Map(paths.map((p) => [p, claimPass(p)]));
|
|
568
640
|
const valuesAtStart = store.getState().values;
|
|
@@ -575,9 +647,8 @@ var createForm = (schema, options) => {
|
|
|
575
647
|
}));
|
|
576
648
|
const result = await validateAsync(schema, valuesAtStart);
|
|
577
649
|
const fullErrors = result.kind === "invalid" ? result.errors : emptyErrors;
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
);
|
|
650
|
+
const requestedErrors = pickScope(fullErrors, paths);
|
|
651
|
+
const requested = settledFields(requestedErrors);
|
|
581
652
|
const live = store.getState();
|
|
582
653
|
const stillCurrent = paths.filter(
|
|
583
654
|
(p) => sequences.get(p) === passes.get(p)
|
|
@@ -592,27 +663,21 @@ var createForm = (schema, options) => {
|
|
|
592
663
|
)
|
|
593
664
|
}));
|
|
594
665
|
}
|
|
595
|
-
return
|
|
666
|
+
return requested;
|
|
596
667
|
}
|
|
597
668
|
store.setState((state) => ({
|
|
598
669
|
...state,
|
|
599
|
-
...
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
stillCurrent
|
|
605
|
-
),
|
|
606
|
-
state.schemaErrors
|
|
607
|
-
),
|
|
608
|
-
serverErrors: omitScope(state.serverErrors, stillCurrent)
|
|
609
|
-
}),
|
|
670
|
+
...commitScopedErrors(
|
|
671
|
+
state,
|
|
672
|
+
pickScope(fullErrors, stillCurrent),
|
|
673
|
+
stillCurrent
|
|
674
|
+
),
|
|
610
675
|
isValidating: stillCurrent.reduce(
|
|
611
676
|
(acc, p) => omitKey(acc, p),
|
|
612
677
|
state.isValidating
|
|
613
678
|
)
|
|
614
679
|
}));
|
|
615
|
-
return
|
|
680
|
+
return requested;
|
|
616
681
|
};
|
|
617
682
|
const validateFieldAsync = async (path) => {
|
|
618
683
|
warnMissingSchemaPath("validateFieldAsync", path);
|
|
@@ -662,12 +727,21 @@ var createForm = (schema, options) => {
|
|
|
662
727
|
);
|
|
663
728
|
return;
|
|
664
729
|
}
|
|
730
|
+
const arr = Array.isArray(current) ? current : [];
|
|
731
|
+
const next = nextArray(arr);
|
|
732
|
+
const written = setAtPath(store.getState().values, path, next);
|
|
733
|
+
clearArrayOpsUnder(store, path, true);
|
|
734
|
+
if (Array.isArray(current) && getAtPath(written, path) === next) {
|
|
735
|
+
recordArrayOp(store, path, {
|
|
736
|
+
from: current,
|
|
737
|
+
to: next,
|
|
738
|
+
mapping: invertMapper(mapper, current.length, next.length)
|
|
739
|
+
});
|
|
740
|
+
}
|
|
665
741
|
store.setState((state) => {
|
|
666
|
-
const live = getAtPath(state.values, path);
|
|
667
|
-
const arr = Array.isArray(live) ? live : [];
|
|
668
742
|
return {
|
|
669
743
|
...state,
|
|
670
|
-
values:
|
|
744
|
+
values: written,
|
|
671
745
|
...errorChannels(state, {
|
|
672
746
|
schemaErrors: reKeyByArrayPath(state.schemaErrors, path, mapper),
|
|
673
747
|
// Server entries on rows follow their rows through the index
|
|
@@ -808,24 +882,30 @@ var createForm = (schema, options) => {
|
|
|
808
882
|
}
|
|
809
883
|
});
|
|
810
884
|
},
|
|
811
|
-
setValue: (path, value) =>
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
885
|
+
setValue: (path, value) => {
|
|
886
|
+
clearArrayOpsUnder(store, path);
|
|
887
|
+
store.setState((state) => ({
|
|
888
|
+
...state,
|
|
889
|
+
values: setAtPath(state.values, path, value),
|
|
890
|
+
...errorChannels(state, {
|
|
891
|
+
serverErrors: releaseSpine(state.serverErrors, path)
|
|
892
|
+
})
|
|
893
|
+
}));
|
|
894
|
+
},
|
|
895
|
+
setValues: (next) => {
|
|
896
|
+
clearArrayOpsUnder(store, "");
|
|
897
|
+
store.setState((state) => ({
|
|
898
|
+
...state,
|
|
899
|
+
values: next,
|
|
900
|
+
...errorChannels(state, {
|
|
901
|
+
serverErrors: releaseChangedSlices(
|
|
902
|
+
state.serverErrors,
|
|
903
|
+
state.values,
|
|
904
|
+
next
|
|
905
|
+
)
|
|
906
|
+
})
|
|
907
|
+
}));
|
|
908
|
+
},
|
|
829
909
|
setTouched: (path, touched = true) => store.setState((state) => ({
|
|
830
910
|
...state,
|
|
831
911
|
touched: { ...state.touched, [path]: touched }
|
|
@@ -856,25 +936,32 @@ var createForm = (schema, options) => {
|
|
|
856
936
|
})
|
|
857
937
|
};
|
|
858
938
|
}),
|
|
859
|
-
updateState: (updater) =>
|
|
860
|
-
const
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
939
|
+
updateState: (updater) => {
|
|
940
|
+
const valuesBefore = store.getState().values;
|
|
941
|
+
store.setState((state) => {
|
|
942
|
+
const patch = updater(state);
|
|
943
|
+
if (Object.keys(patch).length === 0) return state;
|
|
944
|
+
if (patch.errors !== void 0 && patch.errors !== state.errors) {
|
|
945
|
+
console.warn(
|
|
946
|
+
"[formstand] `errors` is derived from schemaErrors/serverErrors \u2014 patch those channels instead; the direct `errors` patch is ignored."
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
const next = { ...state, ...patch };
|
|
950
|
+
return {
|
|
951
|
+
...next,
|
|
952
|
+
...errorChannels(state, {
|
|
953
|
+
schemaErrors: next.schemaErrors,
|
|
954
|
+
serverErrors: next.serverErrors
|
|
955
|
+
})
|
|
956
|
+
};
|
|
957
|
+
});
|
|
958
|
+
if (store.getState().values !== valuesBefore) {
|
|
959
|
+
clearArrayOpsUnder(store, "");
|
|
866
960
|
}
|
|
867
|
-
|
|
868
|
-
return {
|
|
869
|
-
...next,
|
|
870
|
-
...errorChannels(state, {
|
|
871
|
-
schemaErrors: next.schemaErrors,
|
|
872
|
-
serverErrors: next.serverErrors
|
|
873
|
-
})
|
|
874
|
-
};
|
|
875
|
-
}),
|
|
961
|
+
},
|
|
876
962
|
adoptValues: (values) => {
|
|
877
963
|
sequences.clear();
|
|
964
|
+
clearArrayOpsUnder(store, "");
|
|
878
965
|
store.setState((state) => ({
|
|
879
966
|
...state,
|
|
880
967
|
values,
|
|
@@ -892,6 +979,7 @@ var createForm = (schema, options) => {
|
|
|
892
979
|
},
|
|
893
980
|
reset: (nextInitial, resetOptions) => {
|
|
894
981
|
sequences.clear();
|
|
982
|
+
clearArrayOpsUnder(store, "");
|
|
895
983
|
store.setState((state) => {
|
|
896
984
|
const init = nextInitial === void 0 ? state.initialValues : isPlainObject(state.initialValues) && isPlainObject(nextInitial) ? {
|
|
897
985
|
...state.initialValues,
|
|
@@ -913,23 +1001,33 @@ var createForm = (schema, options) => {
|
|
|
913
1001
|
};
|
|
914
1002
|
});
|
|
915
1003
|
},
|
|
916
|
-
resetField: (path) =>
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1004
|
+
resetField: (path) => {
|
|
1005
|
+
const initialSlot = slotAtPath(
|
|
1006
|
+
store.getState().initialValues,
|
|
1007
|
+
path
|
|
1008
|
+
);
|
|
1009
|
+
if (!initialSlot.exists) {
|
|
1010
|
+
console.warn(
|
|
1011
|
+
`[formstand] resetField("${path}"): path has no initial value (e.g. an appended array row); value left unchanged, field state cleared.`
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
if (initialSlot.exists) {
|
|
1015
|
+
clearArrayOpsUnder(store, path);
|
|
1016
|
+
}
|
|
1017
|
+
store.setState((state) => ({
|
|
1018
|
+
...state,
|
|
1019
|
+
values: initialSlot.exists ? setAtPath(state.values, path, initialSlot.value) : state.values,
|
|
1020
|
+
// A runtime "" path scopes the whole form (the imperative surface is
|
|
1021
|
+
// string-typed even though FieldPath excludes ""). Server entries
|
|
1022
|
+
// follow the setValue release contract (releaseSpine).
|
|
1023
|
+
...errorChannels(state, {
|
|
1024
|
+
schemaErrors: path === "" ? emptyErrors : omitScope(state.schemaErrors, [path]),
|
|
1025
|
+
serverErrors: releaseSpine(state.serverErrors, path)
|
|
1026
|
+
}),
|
|
1027
|
+
touched: path === "" ? emptyBools : omitScope(state.touched, [path]),
|
|
1028
|
+
isValidating: path === "" ? emptyBools : omitScope(state.isValidating, [path])
|
|
1029
|
+
}));
|
|
1030
|
+
},
|
|
933
1031
|
validate,
|
|
934
1032
|
validateField,
|
|
935
1033
|
validateFields,
|
|
@@ -983,29 +1081,32 @@ var createForm = (schema, options) => {
|
|
|
983
1081
|
return dirtyPathsOf(state.values, state.initialValues, "");
|
|
984
1082
|
},
|
|
985
1083
|
snapshot: () => store.getState(),
|
|
986
|
-
restore: (snap) =>
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1084
|
+
restore: (snap) => {
|
|
1085
|
+
clearArrayOpsUnder(store, "");
|
|
1086
|
+
store.setState(() => {
|
|
1087
|
+
const schemaErrors = snap.schemaErrors ?? emptyErrors;
|
|
1088
|
+
const serverErrors = snap.serverErrors ?? emptyErrors;
|
|
1089
|
+
return {
|
|
1090
|
+
...snap,
|
|
1091
|
+
schemaErrors,
|
|
1092
|
+
serverErrors,
|
|
1093
|
+
errors: reuseErrorRefs(
|
|
1094
|
+
mergeErrorChannels(schemaErrors, serverErrors),
|
|
1095
|
+
snap.errors ?? emptyErrors
|
|
1096
|
+
),
|
|
1097
|
+
// In-flight state is owned by live passes, never by snapshots: a
|
|
1098
|
+
// snapshotted flag has no pass left to clear it (the pass that set
|
|
1099
|
+
// it cleared its ORIGINAL entry when it settled), so restoring one
|
|
1100
|
+
// would stick it forever. isSubmitting follows the same rule — a
|
|
1101
|
+
// mid-submit snapshot restored later must not disable submit
|
|
1102
|
+
// buttons forever, so it reflects the LIVE submit count instead of
|
|
1103
|
+
// the snapshotted flag (a live pass's finally still resets it).
|
|
1104
|
+
isValidating: emptyBools,
|
|
1105
|
+
isValidatingForm: false,
|
|
1106
|
+
isSubmitting: inFlight.count > 0
|
|
1107
|
+
};
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1009
1110
|
});
|
|
1010
1111
|
};
|
|
1011
1112
|
|
|
@@ -1175,15 +1276,7 @@ function useField(form, pathArg, options) {
|
|
|
1175
1276
|
}, debounceMs);
|
|
1176
1277
|
return;
|
|
1177
1278
|
}
|
|
1178
|
-
|
|
1179
|
-
form.validateField(path);
|
|
1180
|
-
} catch (e) {
|
|
1181
|
-
if (isAsyncRequiredError(e)) {
|
|
1182
|
-
void form.validateFieldAsync(path);
|
|
1183
|
-
return;
|
|
1184
|
-
}
|
|
1185
|
-
throw e;
|
|
1186
|
-
}
|
|
1279
|
+
form.validateField(path);
|
|
1187
1280
|
}, [form, path, debounceMs]);
|
|
1188
1281
|
const setValue = react.useCallback(
|
|
1189
1282
|
(value) => {
|
|
@@ -1263,12 +1356,14 @@ function useVariantField(form, unionPath, field) {
|
|
|
1263
1356
|
`${unionPath}.${field}`
|
|
1264
1357
|
);
|
|
1265
1358
|
}
|
|
1266
|
-
var EMPTY_ID_STATE = { items: [], ids: [], counter: 0 };
|
|
1267
1359
|
var EMPTY_ITEMS = [];
|
|
1360
|
+
var mintNumbers = (needsMint) => new Map(
|
|
1361
|
+
needsMint.flatMap((mint, i) => mint ? [i] : []).map((position, k) => [position, k + 1])
|
|
1362
|
+
);
|
|
1268
1363
|
var reconcileIds = (prev, nextItems) => {
|
|
1269
1364
|
if (prev.items === nextItems) return prev;
|
|
1270
1365
|
if (prev.items.length === nextItems.length && prev.items.every((item, i) => Object.is(item, nextItems[i]))) {
|
|
1271
|
-
return prev;
|
|
1366
|
+
return { ...prev, items: nextItems };
|
|
1272
1367
|
}
|
|
1273
1368
|
const buckets = prev.items.reduce((acc, item, i) => {
|
|
1274
1369
|
const bucket = acc.get(item);
|
|
@@ -1290,15 +1385,77 @@ var reconcileIds = (prev, nextItems) => {
|
|
|
1290
1385
|
})
|
|
1291
1386
|
);
|
|
1292
1387
|
const reused = matchedIds.map((id, i) => id ?? leftoverAt.get(i) ?? null);
|
|
1293
|
-
const
|
|
1294
|
-
const count = acc[acc.length - 1] ?? 0;
|
|
1295
|
-
return [...acc, id === null ? count + 1 : count];
|
|
1296
|
-
}, []);
|
|
1297
|
-
const minted = mintCounts[mintCounts.length - 1] ?? 0;
|
|
1388
|
+
const mints = mintNumbers(reused.map((id) => id === null));
|
|
1298
1389
|
const ids = reused.map(
|
|
1299
|
-
(id, i) => id ??
|
|
1390
|
+
(id, i) => id ?? `${prev.prefix}${prev.counter + (mints.get(i) ?? 0)}`
|
|
1391
|
+
);
|
|
1392
|
+
return {
|
|
1393
|
+
items: nextItems,
|
|
1394
|
+
ids,
|
|
1395
|
+
counter: prev.counter + mints.size,
|
|
1396
|
+
prefix: prev.prefix
|
|
1397
|
+
};
|
|
1398
|
+
};
|
|
1399
|
+
var mappedIdState = (prev, mapping, liveItems) => {
|
|
1400
|
+
if (liveItems.length !== mapping.length) return null;
|
|
1401
|
+
const aligned = mapping.every(
|
|
1402
|
+
(src, i) => src === -1 ? true : prev.ids[src] !== void 0 && Object.is(liveItems[i], prev.items[src])
|
|
1403
|
+
);
|
|
1404
|
+
if (!aligned) return null;
|
|
1405
|
+
const mints = mintNumbers(mapping.map((src) => src === -1));
|
|
1406
|
+
const ids = mapping.map(
|
|
1407
|
+
(src, i) => src === -1 ? `${prev.prefix}${prev.counter + (mints.get(i) ?? 0)}` : prev.ids[src]
|
|
1300
1408
|
);
|
|
1301
|
-
return {
|
|
1409
|
+
return {
|
|
1410
|
+
items: liveItems,
|
|
1411
|
+
ids,
|
|
1412
|
+
counter: prev.counter + mints.size,
|
|
1413
|
+
prefix: prev.prefix
|
|
1414
|
+
};
|
|
1415
|
+
};
|
|
1416
|
+
var MAX_ID_PATHS = 4096;
|
|
1417
|
+
var sharedIdEntries = /* @__PURE__ */ new WeakMap();
|
|
1418
|
+
var sharedIdEntry = (store, path) => {
|
|
1419
|
+
const existingStore = sharedIdEntries.get(store);
|
|
1420
|
+
const forStore = existingStore ?? { byPath: /* @__PURE__ */ new Map(), seq: 1 };
|
|
1421
|
+
if (existingStore === void 0) sharedIdEntries.set(store, forStore);
|
|
1422
|
+
const existing = forStore.byPath.get(path);
|
|
1423
|
+
if (existing !== void 0) {
|
|
1424
|
+
forStore.byPath.delete(path);
|
|
1425
|
+
forStore.byPath.set(path, existing);
|
|
1426
|
+
return existing;
|
|
1427
|
+
}
|
|
1428
|
+
const created = {
|
|
1429
|
+
state: {
|
|
1430
|
+
items: EMPTY_ITEMS,
|
|
1431
|
+
ids: [],
|
|
1432
|
+
counter: 0,
|
|
1433
|
+
prefix: `__zfa_${forStore.seq}_`
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
forStore.seq += 1;
|
|
1437
|
+
forStore.byPath.set(path, created);
|
|
1438
|
+
if (forStore.byPath.size > MAX_ID_PATHS) {
|
|
1439
|
+
const oldest = forStore.byPath.keys().next().value;
|
|
1440
|
+
if (oldest !== void 0) forStore.byPath.delete(oldest);
|
|
1441
|
+
}
|
|
1442
|
+
return created;
|
|
1443
|
+
};
|
|
1444
|
+
var walkOps = (start, items, ops) => {
|
|
1445
|
+
const walked = ops.reduce((acc, op) => {
|
|
1446
|
+
const base = acc.items === op.from ? acc : reconcileIds(acc, op.from);
|
|
1447
|
+
return mappedIdState(base, op.mapping, op.to) ?? base;
|
|
1448
|
+
}, start);
|
|
1449
|
+
return walked.items === items ? walked : reconcileIds(walked, items);
|
|
1450
|
+
};
|
|
1451
|
+
var deriveIds = (store, path, items) => {
|
|
1452
|
+
const entry = sharedIdEntry(store, path);
|
|
1453
|
+
if (entry.state.items === items) return entry.state;
|
|
1454
|
+
const ops = arrayOpsFor(store, path);
|
|
1455
|
+
if (ops.length > 0) clearArrayOps(store, path);
|
|
1456
|
+
const next = walkOps(entry.state, items, ops);
|
|
1457
|
+
entry.state = next;
|
|
1458
|
+
return next;
|
|
1302
1459
|
};
|
|
1303
1460
|
function useFieldArray(form, pathArg) {
|
|
1304
1461
|
const path = react$1.useStore(
|
|
@@ -1317,13 +1474,7 @@ function useFieldArray(form, pathArg) {
|
|
|
1317
1474
|
);
|
|
1318
1475
|
const items = slice.items;
|
|
1319
1476
|
const error = slice.error;
|
|
1320
|
-
const
|
|
1321
|
-
const base = idEntry.form === form && idEntry.path === path ? idEntry.state : { ...EMPTY_ID_STATE, counter: idEntry.state.counter };
|
|
1322
|
-
const nextIdState = reconcileIds(base, items);
|
|
1323
|
-
if (idEntry.form !== form || idEntry.path !== path || idEntry.state !== nextIdState) {
|
|
1324
|
-
setIdEntry({ form, path, state: nextIdState });
|
|
1325
|
-
}
|
|
1326
|
-
const ids = nextIdState.ids;
|
|
1477
|
+
const ids = deriveIds(form.store, path, items).ids;
|
|
1327
1478
|
const push = react.useCallback(
|
|
1328
1479
|
(item) => form.arrayPush(path, item),
|
|
1329
1480
|
[form, path]
|
|
@@ -1433,7 +1584,8 @@ var createFormHooks = (form, name) => {
|
|
|
1433
1584
|
};
|
|
1434
1585
|
|
|
1435
1586
|
// src/react/inputProps.ts
|
|
1436
|
-
var
|
|
1587
|
+
var hasFieldError = (error) => error !== void 0 && error.length > 0;
|
|
1588
|
+
var ariaInvalid = (field) => hasFieldError(field.error) ? true : void 0;
|
|
1437
1589
|
var numberToInputText = (value) => value === void 0 || value === null || Number.isNaN(value) ? "" : String(value);
|
|
1438
1590
|
var parseNumberText = (text) => {
|
|
1439
1591
|
if (text.trim() === "") return { kind: "empty" };
|
|
@@ -1527,11 +1679,37 @@ var selectProps = (field) => ({
|
|
|
1527
1679
|
},
|
|
1528
1680
|
onBlur: field.onBlur
|
|
1529
1681
|
});
|
|
1530
|
-
var
|
|
1682
|
+
var useFieldA11y = (error) => {
|
|
1683
|
+
const id = react.useId();
|
|
1684
|
+
const errorId = `${id}-error`;
|
|
1685
|
+
return {
|
|
1686
|
+
id,
|
|
1687
|
+
errorId,
|
|
1688
|
+
describedBy: hasFieldError(error) ? errorId : void 0
|
|
1689
|
+
};
|
|
1690
|
+
};
|
|
1531
1691
|
var ErrorText = ({
|
|
1532
1692
|
id,
|
|
1533
1693
|
error
|
|
1534
|
-
}) =>
|
|
1694
|
+
}) => hasFieldError(error) ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "zf-error", id, role: "alert", children: error?.[0] }) : null;
|
|
1695
|
+
var FieldShell = ({
|
|
1696
|
+
id,
|
|
1697
|
+
errorId,
|
|
1698
|
+
label,
|
|
1699
|
+
error,
|
|
1700
|
+
wrapLabel,
|
|
1701
|
+
children
|
|
1702
|
+
}) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "zf-field", children: [
|
|
1703
|
+
wrapLabel === true ? /* @__PURE__ */ jsxRuntime.jsxs("label", { htmlFor: id, className: "zf-label", children: [
|
|
1704
|
+
children,
|
|
1705
|
+
" ",
|
|
1706
|
+
label
|
|
1707
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1708
|
+
label !== void 0 ? /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: id, className: "zf-label", children: label }) : null,
|
|
1709
|
+
children
|
|
1710
|
+
] }),
|
|
1711
|
+
/* @__PURE__ */ jsxRuntime.jsx(ErrorText, { id: errorId, error })
|
|
1712
|
+
] });
|
|
1535
1713
|
var TextField = ({
|
|
1536
1714
|
form,
|
|
1537
1715
|
path,
|
|
@@ -1541,25 +1719,29 @@ var TextField = ({
|
|
|
1541
1719
|
autoComplete,
|
|
1542
1720
|
ref
|
|
1543
1721
|
}) => {
|
|
1544
|
-
const id = react.useId();
|
|
1545
|
-
const errorId = `${id}-error`;
|
|
1546
1722
|
const field = useField(form, path);
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1723
|
+
const a11y = useFieldA11y(field.error);
|
|
1724
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1725
|
+
FieldShell,
|
|
1726
|
+
{
|
|
1727
|
+
id: a11y.id,
|
|
1728
|
+
errorId: a11y.errorId,
|
|
1729
|
+
label,
|
|
1730
|
+
error: field.error,
|
|
1731
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1732
|
+
"input",
|
|
1733
|
+
{
|
|
1734
|
+
id: a11y.id,
|
|
1735
|
+
ref,
|
|
1736
|
+
...textInputProps(field),
|
|
1737
|
+
type,
|
|
1738
|
+
placeholder,
|
|
1739
|
+
autoComplete,
|
|
1740
|
+
"aria-describedby": a11y.describedBy
|
|
1741
|
+
}
|
|
1742
|
+
)
|
|
1743
|
+
}
|
|
1744
|
+
);
|
|
1563
1745
|
};
|
|
1564
1746
|
var DateField = ({
|
|
1565
1747
|
form,
|
|
@@ -1569,24 +1751,28 @@ var DateField = ({
|
|
|
1569
1751
|
max,
|
|
1570
1752
|
ref
|
|
1571
1753
|
}) => {
|
|
1572
|
-
const id = react.useId();
|
|
1573
|
-
const errorId = `${id}-error`;
|
|
1574
1754
|
const field = useField(form, path);
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1755
|
+
const a11y = useFieldA11y(field.error);
|
|
1756
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1757
|
+
FieldShell,
|
|
1758
|
+
{
|
|
1759
|
+
id: a11y.id,
|
|
1760
|
+
errorId: a11y.errorId,
|
|
1761
|
+
label,
|
|
1762
|
+
error: field.error,
|
|
1763
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1764
|
+
"input",
|
|
1765
|
+
{
|
|
1766
|
+
id: a11y.id,
|
|
1767
|
+
ref,
|
|
1768
|
+
...dateInputProps(field),
|
|
1769
|
+
min,
|
|
1770
|
+
max,
|
|
1771
|
+
"aria-describedby": a11y.describedBy
|
|
1772
|
+
}
|
|
1773
|
+
)
|
|
1774
|
+
}
|
|
1775
|
+
);
|
|
1590
1776
|
};
|
|
1591
1777
|
var IDLE_EDIT = { raw: null, pushed: void 0 };
|
|
1592
1778
|
var useNumberInput = (field) => {
|
|
@@ -1600,7 +1786,7 @@ var useNumberInput = (field) => {
|
|
|
1600
1786
|
name: field.path,
|
|
1601
1787
|
value: raw ?? numberToInputText(field.value),
|
|
1602
1788
|
inputMode: "decimal",
|
|
1603
|
-
"aria-invalid":
|
|
1789
|
+
"aria-invalid": hasFieldError(field.error) ? true : void 0,
|
|
1604
1790
|
onChange: (e) => {
|
|
1605
1791
|
const text = e.target.value;
|
|
1606
1792
|
const parsed = parseNumberText(text);
|
|
@@ -1633,25 +1819,29 @@ var NumberField = ({
|
|
|
1633
1819
|
placeholder,
|
|
1634
1820
|
ref
|
|
1635
1821
|
}) => {
|
|
1636
|
-
const id = react.useId();
|
|
1637
|
-
const errorId = `${id}-error`;
|
|
1638
1822
|
const field = useField(form, path);
|
|
1639
1823
|
const binding = useNumberInput(field);
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1824
|
+
const a11y = useFieldA11y(field.error);
|
|
1825
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1826
|
+
FieldShell,
|
|
1827
|
+
{
|
|
1828
|
+
id: a11y.id,
|
|
1829
|
+
errorId: a11y.errorId,
|
|
1830
|
+
label,
|
|
1831
|
+
error: field.error,
|
|
1832
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1833
|
+
"input",
|
|
1834
|
+
{
|
|
1835
|
+
id: a11y.id,
|
|
1836
|
+
ref,
|
|
1837
|
+
type: "text",
|
|
1838
|
+
...binding,
|
|
1839
|
+
placeholder,
|
|
1840
|
+
"aria-describedby": a11y.describedBy
|
|
1841
|
+
}
|
|
1842
|
+
)
|
|
1843
|
+
}
|
|
1844
|
+
);
|
|
1655
1845
|
};
|
|
1656
1846
|
var CheckboxField = ({
|
|
1657
1847
|
form,
|
|
@@ -1659,25 +1849,27 @@ var CheckboxField = ({
|
|
|
1659
1849
|
label,
|
|
1660
1850
|
ref
|
|
1661
1851
|
}) => {
|
|
1662
|
-
const id = react.useId();
|
|
1663
|
-
const errorId = `${id}-error`;
|
|
1664
1852
|
const field = useField(form, path);
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1853
|
+
const a11y = useFieldA11y(field.error);
|
|
1854
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1855
|
+
FieldShell,
|
|
1856
|
+
{
|
|
1857
|
+
id: a11y.id,
|
|
1858
|
+
errorId: a11y.errorId,
|
|
1859
|
+
label,
|
|
1860
|
+
error: field.error,
|
|
1861
|
+
wrapLabel: true,
|
|
1862
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1668
1863
|
"input",
|
|
1669
1864
|
{
|
|
1670
|
-
id,
|
|
1865
|
+
id: a11y.id,
|
|
1671
1866
|
ref,
|
|
1672
1867
|
...checkboxProps(field),
|
|
1673
|
-
"aria-describedby":
|
|
1868
|
+
"aria-describedby": a11y.describedBy
|
|
1674
1869
|
}
|
|
1675
|
-
)
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
] }),
|
|
1679
|
-
/* @__PURE__ */ jsxRuntime.jsx(ErrorText, { id: errorId, error: field.error })
|
|
1680
|
-
] });
|
|
1870
|
+
)
|
|
1871
|
+
}
|
|
1872
|
+
);
|
|
1681
1873
|
};
|
|
1682
1874
|
var SelectField = ({
|
|
1683
1875
|
form,
|
|
@@ -1687,33 +1879,39 @@ var SelectField = ({
|
|
|
1687
1879
|
placeholder,
|
|
1688
1880
|
ref
|
|
1689
1881
|
}) => {
|
|
1690
|
-
const id = react.useId();
|
|
1691
|
-
const errorId = `${id}-error`;
|
|
1692
1882
|
const field = useField(form, path);
|
|
1883
|
+
const a11y = useFieldA11y(field.error);
|
|
1884
|
+
const select = selectProps(field);
|
|
1693
1885
|
const clearable = field.emptyValue === null;
|
|
1694
|
-
const
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1886
|
+
const hasExplicitEmptyOption = options.some((opt) => opt.value === "");
|
|
1887
|
+
const showEmptyOption = !hasExplicitEmptyOption && (clearable || select.value === "" || placeholder !== void 0);
|
|
1888
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1889
|
+
FieldShell,
|
|
1890
|
+
{
|
|
1891
|
+
id: a11y.id,
|
|
1892
|
+
errorId: a11y.errorId,
|
|
1893
|
+
label,
|
|
1894
|
+
error: field.error,
|
|
1895
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1896
|
+
"select",
|
|
1897
|
+
{
|
|
1898
|
+
id: a11y.id,
|
|
1899
|
+
ref,
|
|
1900
|
+
...select,
|
|
1901
|
+
"aria-describedby": a11y.describedBy,
|
|
1902
|
+
children: [
|
|
1903
|
+
showEmptyOption ? /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", disabled: !clearable, children: placeholder ?? "" }) : null,
|
|
1904
|
+
options.map((opt, index) => (
|
|
1905
|
+
// Index-qualified: option lists may legitimately repeat a value
|
|
1906
|
+
// (e.g. differently-labelled aliases), and bare value keys would
|
|
1907
|
+
// collide.
|
|
1908
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: opt.value, children: opt.label }, `${index}-${opt.value}`)
|
|
1909
|
+
))
|
|
1910
|
+
]
|
|
1911
|
+
}
|
|
1912
|
+
)
|
|
1913
|
+
}
|
|
1914
|
+
);
|
|
1717
1915
|
};
|
|
1718
1916
|
|
|
1719
1917
|
// src/react/focusError.ts
|
|
@@ -1736,6 +1934,20 @@ var nameMatchesAny = (el, paths) => {
|
|
|
1736
1934
|
const name = el.getAttribute("name");
|
|
1737
1935
|
return name !== null && paths.some((p) => isPathOrChild(name, p));
|
|
1738
1936
|
};
|
|
1937
|
+
var cssQuoted = (value) => `"${value.replace(/[\\"]/g, (ch) => `\\${ch}`)}"`;
|
|
1938
|
+
var idMatches = (scope, path) => path === "" ? [] : [...scope.querySelectorAll(`[id=${cssQuoted(path)}]`)];
|
|
1939
|
+
var inDomOrder = (els) => [...els].sort(
|
|
1940
|
+
(a, b) => a === b ? 0 : (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0 ? -1 : 1
|
|
1941
|
+
);
|
|
1942
|
+
var pathCandidates = (scope, paths) => {
|
|
1943
|
+
const controls = namedControls(scope);
|
|
1944
|
+
const named = controls.filter((el) => nameMatchesAny(el, paths));
|
|
1945
|
+
const unnamedPaths = paths.filter(
|
|
1946
|
+
(p) => !controls.some((el) => nameMatchesAny(el, [p]))
|
|
1947
|
+
);
|
|
1948
|
+
const byId = unnamedPaths.flatMap((p) => idMatches(scope, p));
|
|
1949
|
+
return inDomOrder([.../* @__PURE__ */ new Set([...named, ...byId])]);
|
|
1950
|
+
};
|
|
1739
1951
|
var focusFirstError = (errors, root) => {
|
|
1740
1952
|
const scope = root ?? document;
|
|
1741
1953
|
const erroredPaths = Object.keys(errors).filter(
|
|
@@ -1743,16 +1955,16 @@ var focusFirstError = (errors, root) => {
|
|
|
1743
1955
|
);
|
|
1744
1956
|
const hasRootError = (errors[""]?.length ?? 0) > 0;
|
|
1745
1957
|
const controls = namedControls(scope);
|
|
1746
|
-
const fieldMatches =
|
|
1958
|
+
const fieldMatches = pathCandidates(scope, erroredPaths).filter(
|
|
1959
|
+
isFocusCandidate
|
|
1960
|
+
);
|
|
1747
1961
|
const rootFallbackApplies = hasRootError && (root !== void 0 || document.querySelectorAll("form").length <= 1);
|
|
1748
1962
|
const candidates = fieldMatches.length > 0 ? fieldMatches : rootFallbackApplies ? controls.filter(isFocusCandidate) : [];
|
|
1749
1963
|
return focusFirstOf(candidates);
|
|
1750
1964
|
};
|
|
1751
1965
|
var focusField = (path, root) => {
|
|
1752
|
-
const
|
|
1753
|
-
return path === "" ? (root !== void 0 || document.querySelectorAll("form").length <= 1) && focusFirstOf(
|
|
1754
|
-
controls.filter((el) => nameMatchesAny(el, [path])).filter(isFocusCandidate)
|
|
1755
|
-
);
|
|
1966
|
+
const scope = root ?? document;
|
|
1967
|
+
return path === "" ? (root !== void 0 || document.querySelectorAll("form").length <= 1) && focusFirstOf(namedControls(scope).filter(isFocusCandidate)) : focusFirstOf(pathCandidates(scope, [path]).filter(isFocusCandidate));
|
|
1756
1968
|
};
|
|
1757
1969
|
|
|
1758
1970
|
exports.CheckboxField = CheckboxField;
|
|
@@ -1771,6 +1983,7 @@ exports.flattenIssues = flattenIssues;
|
|
|
1771
1983
|
exports.focusField = focusField;
|
|
1772
1984
|
exports.focusFirstError = focusFirstError;
|
|
1773
1985
|
exports.getAtPath = getAtPath;
|
|
1986
|
+
exports.hasFieldError = hasFieldError;
|
|
1774
1987
|
exports.isAsyncRequiredError = isAsyncRequiredError;
|
|
1775
1988
|
exports.numberInputProps = numberInputProps;
|
|
1776
1989
|
exports.numberToInputText = numberToInputText;
|
|
@@ -1791,6 +2004,7 @@ exports.useFormSelectorShallow = useFormSelectorShallow;
|
|
|
1791
2004
|
exports.useIsDirty = useIsDirty;
|
|
1792
2005
|
exports.useIsSubmitting = useIsSubmitting;
|
|
1793
2006
|
exports.useIsValid = useIsValid;
|
|
2007
|
+
exports.useNumberInput = useNumberInput;
|
|
1794
2008
|
exports.useSubmitCount = useSubmitCount;
|
|
1795
2009
|
exports.useVariantField = useVariantField;
|
|
1796
2010
|
exports.validateAsync = validateAsync;
|