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