formstand 0.7.0 → 0.9.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 +363 -0
- package/README.md +2 -1
- package/dist/index.cjs +274 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -1
- package/dist/index.d.ts +61 -1
- package/dist/index.js +269 -46
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createStore } from 'zustand/vanilla';
|
|
2
|
+
import { devtools } from 'zustand/middleware';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { useRef, useEffect, createContext, useMemo, useCallback, useState, useId, useContext } from 'react';
|
|
4
5
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
@@ -57,7 +58,7 @@ var valuesEqual = (a, b) => {
|
|
|
57
58
|
if (isPlainObject(a) && isPlainObject(b)) {
|
|
58
59
|
const aKeys = Object.keys(a);
|
|
59
60
|
const bKeys = Object.keys(b);
|
|
60
|
-
return aKeys.length === bKeys.length && aKeys.every((k) => valuesEqual(a[k], b[k]));
|
|
61
|
+
return aKeys.length === bKeys.length && aKeys.every((k) => Object.hasOwn(b, k) && valuesEqual(a[k], b[k]));
|
|
61
62
|
}
|
|
62
63
|
return false;
|
|
63
64
|
};
|
|
@@ -85,21 +86,33 @@ var isIndexSegment = (s) => {
|
|
|
85
86
|
const n = Number(s);
|
|
86
87
|
return Number.isInteger(n) && n >= 0 && String(n) === s;
|
|
87
88
|
};
|
|
88
|
-
var
|
|
89
|
+
var PARSE_CACHE_MAX = 4096;
|
|
90
|
+
var parseCache = /* @__PURE__ */ new Map();
|
|
91
|
+
var parsePath = (path) => {
|
|
92
|
+
const cached = parseCache.get(path);
|
|
93
|
+
if (cached !== void 0) return cached;
|
|
94
|
+
const segments = Object.freeze(
|
|
95
|
+
path === "" ? [] : path.split(".").map((s) => isIndexSegment(s) ? Number(s) : s)
|
|
96
|
+
);
|
|
97
|
+
if (parseCache.size >= PARSE_CACHE_MAX) parseCache.clear();
|
|
98
|
+
parseCache.set(path, segments);
|
|
99
|
+
return segments;
|
|
100
|
+
};
|
|
89
101
|
var isPlainRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
90
102
|
var readSegment = (obj, segment) => {
|
|
91
103
|
if (obj == null) return void 0;
|
|
92
104
|
if (Array.isArray(obj)) {
|
|
93
105
|
return typeof segment === "number" ? obj[segment] : void 0;
|
|
94
106
|
}
|
|
95
|
-
return isPlainRecord(obj) ? obj[String(segment)] : void 0;
|
|
107
|
+
return isPlainRecord(obj) && Object.hasOwn(obj, String(segment)) ? obj[String(segment)] : void 0;
|
|
96
108
|
};
|
|
97
109
|
var getAtPath = (obj, path) => parsePath(path).reduce(readSegment, obj);
|
|
98
110
|
var slotAtPath = (obj, path) => {
|
|
99
111
|
const walked = parsePath(path).reduce(
|
|
100
112
|
(acc, segment) => {
|
|
101
|
-
const
|
|
102
|
-
|
|
113
|
+
const container = acc.current;
|
|
114
|
+
const addressable = typeof segment === "number" ? Array.isArray(container) ? segment < container.length : isPlainRecord(container) : isPlainRecord(container);
|
|
115
|
+
return !acc.ok || !addressable ? { ok: false, current: void 0 } : { ok: true, current: readSegment(container, segment) };
|
|
103
116
|
},
|
|
104
117
|
{ ok: true, current: obj }
|
|
105
118
|
);
|
|
@@ -135,6 +148,12 @@ var writeSegments = (obj, segments, value) => {
|
|
|
135
148
|
return writeIntoArray(obj, head, rest, value);
|
|
136
149
|
}
|
|
137
150
|
if (isPlainRecord(obj)) {
|
|
151
|
+
if (!isPlainObject(obj)) {
|
|
152
|
+
console.warn(
|
|
153
|
+
`[formstand] cannot write key "${String(head)}" through a non-plain object (Date/Map/Set/class instance); value left unchanged.`
|
|
154
|
+
);
|
|
155
|
+
return obj;
|
|
156
|
+
}
|
|
138
157
|
const key = String(head);
|
|
139
158
|
return { ...obj, [key]: writeSegments(obj[key], rest, value) };
|
|
140
159
|
}
|
|
@@ -257,7 +276,7 @@ var releaseAncestorEntries = (map, path) => {
|
|
|
257
276
|
return kept.length === entries.length ? map : Object.fromEntries(kept);
|
|
258
277
|
};
|
|
259
278
|
var dirtyPathsOf = (values, initial, prefix) => {
|
|
260
|
-
if (
|
|
279
|
+
if (Object.is(values, initial)) return [];
|
|
261
280
|
if (isPlainObject(values) && isPlainObject(initial)) {
|
|
262
281
|
const keys = /* @__PURE__ */ new Set([...Object.keys(values), ...Object.keys(initial)]);
|
|
263
282
|
const childPaths = [...keys].flatMap(
|
|
@@ -267,9 +286,11 @@ var dirtyPathsOf = (values, initial, prefix) => {
|
|
|
267
286
|
prefix === "" ? k : `${prefix}.${k}`
|
|
268
287
|
)
|
|
269
288
|
);
|
|
270
|
-
|
|
289
|
+
if (childPaths.length > 0) return childPaths;
|
|
290
|
+
const sameKeySet = Object.keys(values).length === Object.keys(initial).length && Object.keys(values).every((k) => Object.hasOwn(initial, k));
|
|
291
|
+
return sameKeySet ? [] : [prefix];
|
|
271
292
|
}
|
|
272
|
-
return [prefix];
|
|
293
|
+
return valuesEqual(values, initial) ? [] : [prefix];
|
|
273
294
|
};
|
|
274
295
|
var FORM_SEQ_KEY = "__form__";
|
|
275
296
|
var SUBMIT_SEQ_KEY = "__submit__";
|
|
@@ -331,7 +352,18 @@ var createForm = (schema, options) => {
|
|
|
331
352
|
mode: initialMode,
|
|
332
353
|
reValidateMode: initialReValidateMode
|
|
333
354
|
};
|
|
334
|
-
const store = createStore(() => initial)
|
|
355
|
+
const store = options.devtools === void 0 || options.devtools === false ? createStore(() => initial) : createStore()(
|
|
356
|
+
devtools(() => initial, {
|
|
357
|
+
name: typeof options.devtools === "string" ? options.devtools : "formstand",
|
|
358
|
+
// Non-production only, matching zustand's own devtools default:
|
|
359
|
+
// opting in during development must NOT stream every keystroke to
|
|
360
|
+
// an end user who happens to have the extension installed, where
|
|
361
|
+
// a module-singleton form's unbounded time-travel history is a
|
|
362
|
+
// real memory + serialization cost. Guarded for environments
|
|
363
|
+
// without process (a bundler defines NODE_ENV in real dev).
|
|
364
|
+
enabled: typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production"
|
|
365
|
+
})
|
|
366
|
+
);
|
|
335
367
|
const commitFullPass = (state, next) => errorChannels(state, {
|
|
336
368
|
schemaErrors: reuseErrorRefs(next, state.schemaErrors)
|
|
337
369
|
});
|
|
@@ -360,7 +392,38 @@ var createForm = (schema, options) => {
|
|
|
360
392
|
return result.kind === "pending" ? result.promise.then((r) => r.kind === "valid") : result.kind === "valid";
|
|
361
393
|
}
|
|
362
394
|
try {
|
|
363
|
-
const
|
|
395
|
+
const values = store.getState().values;
|
|
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
|
+
)) {
|
|
403
|
+
const merged = scopes.reduce(
|
|
404
|
+
(acc, { path, scope }) => scope.kind === "skip" ? acc : {
|
|
405
|
+
...acc,
|
|
406
|
+
...scopeSettledResult(
|
|
407
|
+
validateSync(scope.schema, scope.value),
|
|
408
|
+
path,
|
|
409
|
+
true
|
|
410
|
+
)
|
|
411
|
+
},
|
|
412
|
+
{}
|
|
413
|
+
);
|
|
414
|
+
store.setState((state) => ({
|
|
415
|
+
...state,
|
|
416
|
+
...errorChannels(state, {
|
|
417
|
+
schemaErrors: reuseErrorRefs(
|
|
418
|
+
mergeScopedErrors(state.schemaErrors, merged, paths),
|
|
419
|
+
state.schemaErrors
|
|
420
|
+
),
|
|
421
|
+
serverErrors: omitScope(state.serverErrors, paths)
|
|
422
|
+
})
|
|
423
|
+
}));
|
|
424
|
+
return Object.values(merged).flat().length === 0;
|
|
425
|
+
}
|
|
426
|
+
const result = validateSync(schema, values);
|
|
364
427
|
const fullErrors = result.kind === "invalid" ? result.errors : emptyErrors;
|
|
365
428
|
store.setState((state) => ({
|
|
366
429
|
...state,
|
|
@@ -668,7 +731,11 @@ var createForm = (schema, options) => {
|
|
|
668
731
|
});
|
|
669
732
|
}
|
|
670
733
|
const merged = stale ? result.errors : store.getState().errors;
|
|
671
|
-
|
|
734
|
+
try {
|
|
735
|
+
onInvalid?.(merged);
|
|
736
|
+
} catch (error) {
|
|
737
|
+
return { kind: "error", error };
|
|
738
|
+
}
|
|
672
739
|
return { kind: "invalid", errors: merged };
|
|
673
740
|
}
|
|
674
741
|
if (!stale) {
|
|
@@ -928,9 +995,13 @@ var createForm = (schema, options) => {
|
|
|
928
995
|
// In-flight state is owned by live passes, never by snapshots: a
|
|
929
996
|
// snapshotted flag has no pass left to clear it (the pass that set
|
|
930
997
|
// it cleared its ORIGINAL entry when it settled), so restoring one
|
|
931
|
-
// would stick it forever.
|
|
998
|
+
// would stick it forever. isSubmitting follows the same rule — a
|
|
999
|
+
// mid-submit snapshot restored later must not disable submit
|
|
1000
|
+
// buttons forever, so it reflects the LIVE submit count instead of
|
|
1001
|
+
// the snapshotted flag (a live pass's finally still resets it).
|
|
932
1002
|
isValidating: emptyBools,
|
|
933
|
-
isValidatingForm: false
|
|
1003
|
+
isValidatingForm: false,
|
|
1004
|
+
isSubmitting: inFlight.count > 0
|
|
934
1005
|
};
|
|
935
1006
|
})
|
|
936
1007
|
});
|
|
@@ -952,21 +1023,91 @@ var shouldValidateOn = (trigger, mode, reValidateMode, submitAttempted, touched
|
|
|
952
1023
|
return true;
|
|
953
1024
|
}
|
|
954
1025
|
};
|
|
1026
|
+
|
|
1027
|
+
// src/core/persist.ts
|
|
1028
|
+
var defaultStorage = () => {
|
|
1029
|
+
try {
|
|
1030
|
+
return typeof globalThis.localStorage === "undefined" ? null : globalThis.localStorage;
|
|
1031
|
+
} catch {
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
var persistForm = (form, options) => {
|
|
1036
|
+
const storage = options.storage ?? defaultStorage();
|
|
1037
|
+
const debounceMs = options.debounceMs ?? 300;
|
|
1038
|
+
const apply = options.apply ?? "adopt";
|
|
1039
|
+
const applyDraft = (values) => apply === "restore" ? form.setValues(values) : form.adoptValues(values);
|
|
1040
|
+
const restore = () => {
|
|
1041
|
+
try {
|
|
1042
|
+
const raw = storage === null ? null : storage.getItem(options.key);
|
|
1043
|
+
if (raw === null) return false;
|
|
1044
|
+
applyDraft(JSON.parse(raw));
|
|
1045
|
+
return true;
|
|
1046
|
+
} catch {
|
|
1047
|
+
return false;
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
const writeDraft = (values) => {
|
|
1051
|
+
try {
|
|
1052
|
+
storage?.setItem(options.key, JSON.stringify(values));
|
|
1053
|
+
} catch {
|
|
1054
|
+
}
|
|
1055
|
+
};
|
|
1056
|
+
const timerRef = {
|
|
1057
|
+
current: null
|
|
1058
|
+
};
|
|
1059
|
+
const cancelPending = () => {
|
|
1060
|
+
if (timerRef.current !== null) {
|
|
1061
|
+
clearTimeout(timerRef.current);
|
|
1062
|
+
timerRef.current = null;
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
if (apply !== "manual") {
|
|
1066
|
+
restore();
|
|
1067
|
+
}
|
|
1068
|
+
const unsubscribe = form.watchValues((values) => {
|
|
1069
|
+
cancelPending();
|
|
1070
|
+
if (debounceMs === 0) {
|
|
1071
|
+
writeDraft(values);
|
|
1072
|
+
} else {
|
|
1073
|
+
timerRef.current = setTimeout(() => {
|
|
1074
|
+
timerRef.current = null;
|
|
1075
|
+
writeDraft(values);
|
|
1076
|
+
}, debounceMs);
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
return Object.freeze({
|
|
1080
|
+
restore,
|
|
1081
|
+
clear: () => {
|
|
1082
|
+
cancelPending();
|
|
1083
|
+
try {
|
|
1084
|
+
storage?.removeItem(options.key);
|
|
1085
|
+
} catch {
|
|
1086
|
+
}
|
|
1087
|
+
},
|
|
1088
|
+
dispose: () => {
|
|
1089
|
+
cancelPending();
|
|
1090
|
+
unsubscribe();
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
};
|
|
955
1094
|
var useForm = (schema, options) => {
|
|
956
1095
|
const formRef = useRef(null);
|
|
957
1096
|
if (formRef.current === null) {
|
|
958
1097
|
formRef.current = createForm(schema, options);
|
|
959
1098
|
}
|
|
960
1099
|
const form = formRef.current;
|
|
1100
|
+
const mountInitialsRef = useRef(options.initialValues);
|
|
961
1101
|
const warnedRef = useRef(false);
|
|
962
1102
|
useEffect(() => {
|
|
963
|
-
|
|
1103
|
+
const changedInitials = !Object.is(options.initialValues, mountInitialsRef.current) && !valuesEqual(options.initialValues, mountInitialsRef.current);
|
|
1104
|
+
if (!warnedRef.current && (form.schema !== schema || changedInitials)) {
|
|
964
1105
|
warnedRef.current = true;
|
|
965
1106
|
console.warn(
|
|
966
|
-
"[formstand] useForm received a different schema
|
|
1107
|
+
"[formstand] useForm received a different schema or initial values after the first render; schema and options changes are ignored for the life of the component. If the schema is defined inline this is harmless (but consider hoisting it to module scope). For initial values that arrive later (e.g. from a fetch), call form.adoptValues(values) or form.reset(values) instead."
|
|
967
1108
|
);
|
|
968
1109
|
}
|
|
969
|
-
}, [form, schema]);
|
|
1110
|
+
}, [form, schema, options.initialValues]);
|
|
970
1111
|
return form;
|
|
971
1112
|
};
|
|
972
1113
|
var createFormContext = () => {
|
|
@@ -1112,6 +1253,14 @@ function useField(form, pathArg, options) {
|
|
|
1112
1253
|
[slice, emptyValue, setValue, setTouched, setError, clearError, validate, validateAsync2, onBlur]
|
|
1113
1254
|
);
|
|
1114
1255
|
}
|
|
1256
|
+
|
|
1257
|
+
// src/react/useVariantField.ts
|
|
1258
|
+
function useVariantField(form, unionPath, field) {
|
|
1259
|
+
return useField(
|
|
1260
|
+
form,
|
|
1261
|
+
`${unionPath}.${field}`
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1115
1264
|
var EMPTY_ID_STATE = { items: [], ids: [], counter: 0 };
|
|
1116
1265
|
var EMPTY_ITEMS = [];
|
|
1117
1266
|
var reconcileIds = (prev, nextItems) => {
|
|
@@ -1132,23 +1281,22 @@ var reconcileIds = (prev, nextItems) => {
|
|
|
1132
1281
|
const prevIndex = buckets.get(item)?.shift();
|
|
1133
1282
|
return prevIndex === void 0 ? null : prev.ids[prevIndex] ?? null;
|
|
1134
1283
|
});
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1284
|
+
const leftoverAt = new Map(
|
|
1285
|
+
[...buckets.values()].flat().flatMap((i) => {
|
|
1286
|
+
const id = prev.ids[i];
|
|
1287
|
+
return id === void 0 ? [] : [[i, id]];
|
|
1288
|
+
})
|
|
1289
|
+
);
|
|
1290
|
+
const reused = matchedIds.map((id, i) => id ?? leftoverAt.get(i) ?? null);
|
|
1291
|
+
const mintCounts = reused.reduce((acc, id) => {
|
|
1292
|
+
const count = acc[acc.length - 1] ?? 0;
|
|
1293
|
+
return [...acc, id === null ? count + 1 : count];
|
|
1140
1294
|
}, []);
|
|
1141
|
-
const
|
|
1142
|
-
const ids =
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
});
|
|
1147
|
-
return {
|
|
1148
|
-
items: nextItems,
|
|
1149
|
-
ids,
|
|
1150
|
-
counter: prev.counter + Math.max(0, unmatched - leftover.length)
|
|
1151
|
-
};
|
|
1295
|
+
const minted = mintCounts[mintCounts.length - 1] ?? 0;
|
|
1296
|
+
const ids = reused.map(
|
|
1297
|
+
(id, i) => id ?? `__zfa_${prev.counter + (mintCounts[i] ?? 0)}`
|
|
1298
|
+
);
|
|
1299
|
+
return { items: nextItems, ids, counter: prev.counter + minted };
|
|
1152
1300
|
};
|
|
1153
1301
|
function useFieldArray(form, pathArg) {
|
|
1154
1302
|
const path = useStore(
|
|
@@ -1254,6 +1402,11 @@ var capitalize = (name) => name.length === 0 ? name : name.charAt(0).toUpperCase
|
|
|
1254
1402
|
var createFormHooks = (form, name) => {
|
|
1255
1403
|
const structural = form;
|
|
1256
1404
|
const useBoundField = (path, options) => useField(structural, path, options);
|
|
1405
|
+
const useBoundVariantField = (unionPath, field) => useVariantField(
|
|
1406
|
+
structural,
|
|
1407
|
+
unionPath,
|
|
1408
|
+
field
|
|
1409
|
+
);
|
|
1257
1410
|
const useBoundFieldArray = (path) => useFieldArray(structural, path);
|
|
1258
1411
|
const useBoundSelector = (selector) => useFormSelector(structural, selector);
|
|
1259
1412
|
const useBoundSelectorShallow = (selector) => useFormSelectorShallow(structural, selector);
|
|
@@ -1265,6 +1418,7 @@ var createFormHooks = (form, name) => {
|
|
|
1265
1418
|
const prefix = capitalize(name ?? "");
|
|
1266
1419
|
return {
|
|
1267
1420
|
[`use${prefix}Field`]: useBoundField,
|
|
1421
|
+
[`use${prefix}VariantField`]: useBoundVariantField,
|
|
1268
1422
|
[`use${prefix}FieldArray`]: useBoundFieldArray,
|
|
1269
1423
|
[`use${prefix}Selector`]: useBoundSelector,
|
|
1270
1424
|
[`use${prefix}SelectorShallow`]: useBoundSelectorShallow,
|
|
@@ -1309,6 +1463,48 @@ var numberInputProps = (field) => ({
|
|
|
1309
1463
|
},
|
|
1310
1464
|
onBlur: field.onBlur
|
|
1311
1465
|
});
|
|
1466
|
+
var dateToInputText = (value) => {
|
|
1467
|
+
if (value === void 0 || value === null || Number.isNaN(value.getTime())) {
|
|
1468
|
+
return "";
|
|
1469
|
+
}
|
|
1470
|
+
const pad = (part) => String(part).padStart(2, "0");
|
|
1471
|
+
return `${String(value.getFullYear()).padStart(4, "0")}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
|
|
1472
|
+
};
|
|
1473
|
+
var parseDateText = (text) => {
|
|
1474
|
+
if (text.trim() === "") return { kind: "empty" };
|
|
1475
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
|
|
1476
|
+
if (match === null) return { kind: "invalid" };
|
|
1477
|
+
const [, year, month, day] = match;
|
|
1478
|
+
const y = Number(year);
|
|
1479
|
+
const date = new Date(y, Number(month) - 1, Number(day));
|
|
1480
|
+
if (y < 100) date.setFullYear(y);
|
|
1481
|
+
return date.getFullYear() === y && date.getMonth() === Number(month) - 1 && date.getDate() === Number(day) ? { kind: "date", value: date } : { kind: "invalid" };
|
|
1482
|
+
};
|
|
1483
|
+
var dateInputProps = (field) => ({
|
|
1484
|
+
type: "date",
|
|
1485
|
+
name: field.path,
|
|
1486
|
+
value: dateToInputText(field.value),
|
|
1487
|
+
"aria-invalid": ariaInvalid(field),
|
|
1488
|
+
onChange: (e) => {
|
|
1489
|
+
const parsed = parseDateText(e.target.value);
|
|
1490
|
+
if (parsed.kind !== "date") {
|
|
1491
|
+
field.setValue(field.emptyValue);
|
|
1492
|
+
return;
|
|
1493
|
+
}
|
|
1494
|
+
const current = field.value;
|
|
1495
|
+
const next = current instanceof Date && !Number.isNaN(current.getTime()) ? new Date(
|
|
1496
|
+
parsed.value.getFullYear(),
|
|
1497
|
+
parsed.value.getMonth(),
|
|
1498
|
+
parsed.value.getDate(),
|
|
1499
|
+
current.getHours(),
|
|
1500
|
+
current.getMinutes(),
|
|
1501
|
+
current.getSeconds(),
|
|
1502
|
+
current.getMilliseconds()
|
|
1503
|
+
) : parsed.value;
|
|
1504
|
+
field.setValue(next);
|
|
1505
|
+
},
|
|
1506
|
+
onBlur: field.onBlur
|
|
1507
|
+
});
|
|
1312
1508
|
var checkboxProps = (field) => ({
|
|
1313
1509
|
type: "checkbox",
|
|
1314
1510
|
name: field.path,
|
|
@@ -1363,6 +1559,33 @@ var TextField = ({
|
|
|
1363
1559
|
/* @__PURE__ */ jsx(ErrorText, { id: errorId, error: field.error })
|
|
1364
1560
|
] });
|
|
1365
1561
|
};
|
|
1562
|
+
var DateField = ({
|
|
1563
|
+
form,
|
|
1564
|
+
path,
|
|
1565
|
+
label,
|
|
1566
|
+
min,
|
|
1567
|
+
max,
|
|
1568
|
+
ref
|
|
1569
|
+
}) => {
|
|
1570
|
+
const id = useId();
|
|
1571
|
+
const errorId = `${id}-error`;
|
|
1572
|
+
const field = useField(form, path);
|
|
1573
|
+
return /* @__PURE__ */ jsxs("div", { className: "zf-field", children: [
|
|
1574
|
+
label !== void 0 ? /* @__PURE__ */ jsx("label", { htmlFor: id, className: "zf-label", children: label }) : null,
|
|
1575
|
+
/* @__PURE__ */ jsx(
|
|
1576
|
+
"input",
|
|
1577
|
+
{
|
|
1578
|
+
id,
|
|
1579
|
+
ref,
|
|
1580
|
+
...dateInputProps(field),
|
|
1581
|
+
min,
|
|
1582
|
+
max,
|
|
1583
|
+
"aria-describedby": hasError(field.error) ? errorId : void 0
|
|
1584
|
+
}
|
|
1585
|
+
),
|
|
1586
|
+
/* @__PURE__ */ jsx(ErrorText, { id: errorId, error: field.error })
|
|
1587
|
+
] });
|
|
1588
|
+
};
|
|
1366
1589
|
var IDLE_EDIT = { raw: null, pushed: void 0 };
|
|
1367
1590
|
var useNumberInput = (field) => {
|
|
1368
1591
|
const [edit, setEdit] = useState(IDLE_EDIT);
|
|
@@ -1465,7 +1688,8 @@ var SelectField = ({
|
|
|
1465
1688
|
const id = useId();
|
|
1466
1689
|
const errorId = `${id}-error`;
|
|
1467
1690
|
const field = useField(form, path);
|
|
1468
|
-
const
|
|
1691
|
+
const clearable = field.emptyValue === null;
|
|
1692
|
+
const showEmptyOption = clearable || field.value === void 0 || field.value === null || placeholder !== void 0;
|
|
1469
1693
|
return /* @__PURE__ */ jsxs("div", { className: "zf-field", children: [
|
|
1470
1694
|
label !== void 0 ? /* @__PURE__ */ jsx("label", { htmlFor: id, className: "zf-label", children: label }) : null,
|
|
1471
1695
|
/* @__PURE__ */ jsxs(
|
|
@@ -1476,7 +1700,7 @@ var SelectField = ({
|
|
|
1476
1700
|
...selectProps(field),
|
|
1477
1701
|
"aria-describedby": hasError(field.error) ? errorId : void 0,
|
|
1478
1702
|
children: [
|
|
1479
|
-
showEmptyOption ? /* @__PURE__ */ jsx("option", { value: "", disabled:
|
|
1703
|
+
showEmptyOption ? /* @__PURE__ */ jsx("option", { value: "", disabled: !clearable, children: placeholder ?? "" }) : null,
|
|
1480
1704
|
options.map((opt, index) => (
|
|
1481
1705
|
// Index-qualified: option lists may legitimately repeat a value
|
|
1482
1706
|
// (e.g. differently-labelled aliases), and bare value keys would
|
|
@@ -1491,13 +1715,12 @@ var SelectField = ({
|
|
|
1491
1715
|
};
|
|
1492
1716
|
|
|
1493
1717
|
// src/react/focusError.ts
|
|
1494
|
-
var
|
|
1718
|
+
var namedControls = (scope) => [
|
|
1495
1719
|
...scope.querySelectorAll(
|
|
1496
1720
|
"input[name], select[name], textarea[name]"
|
|
1497
1721
|
)
|
|
1498
|
-
]
|
|
1499
|
-
|
|
1500
|
-
);
|
|
1722
|
+
];
|
|
1723
|
+
var isFocusCandidate = (el) => !el.matches(':disabled, [type="hidden"]') && el.closest("dialog:not([open])") === null;
|
|
1501
1724
|
var holdsFocus = (el) => {
|
|
1502
1725
|
const rootNode = el.getRootNode();
|
|
1503
1726
|
const active = rootNode instanceof Document || rootNode instanceof ShadowRoot ? rootNode.activeElement : document.activeElement;
|
|
@@ -1517,19 +1740,19 @@ var focusFirstError = (errors, root) => {
|
|
|
1517
1740
|
(k) => k !== "" && (errors[k]?.length ?? 0) > 0
|
|
1518
1741
|
);
|
|
1519
1742
|
const hasRootError = (errors[""]?.length ?? 0) > 0;
|
|
1520
|
-
const controls =
|
|
1521
|
-
const fieldMatches = controls.filter(
|
|
1522
|
-
(el) => nameMatchesAny(el, erroredPaths)
|
|
1523
|
-
);
|
|
1743
|
+
const controls = namedControls(scope);
|
|
1744
|
+
const fieldMatches = controls.filter((el) => nameMatchesAny(el, erroredPaths)).filter(isFocusCandidate);
|
|
1524
1745
|
const rootFallbackApplies = hasRootError && (root !== void 0 || document.querySelectorAll("form").length <= 1);
|
|
1525
|
-
const candidates = fieldMatches.length > 0 ? fieldMatches : rootFallbackApplies ? controls : [];
|
|
1746
|
+
const candidates = fieldMatches.length > 0 ? fieldMatches : rootFallbackApplies ? controls.filter(isFocusCandidate) : [];
|
|
1526
1747
|
return focusFirstOf(candidates);
|
|
1527
1748
|
};
|
|
1528
1749
|
var focusField = (path, root) => {
|
|
1529
|
-
const controls =
|
|
1530
|
-
return path === "" ? (root !== void 0 || document.querySelectorAll("form").length <= 1) && focusFirstOf(controls
|
|
1750
|
+
const controls = namedControls(root ?? document);
|
|
1751
|
+
return path === "" ? (root !== void 0 || document.querySelectorAll("form").length <= 1) && focusFirstOf(controls.filter(isFocusCandidate)) : focusFirstOf(
|
|
1752
|
+
controls.filter((el) => nameMatchesAny(el, [path])).filter(isFocusCandidate)
|
|
1753
|
+
);
|
|
1531
1754
|
};
|
|
1532
1755
|
|
|
1533
|
-
export { CheckboxField, NumberField, SelectField, TextField, checkboxProps, createForm, createFormContext, createFormHooks, emptyValueForSchema, flattenIssues, focusField, focusFirstError, getAtPath, isAsyncRequiredError, numberInputProps, numberToInputText, parseNumberText, parsePath, selectProps, setAtPath, shouldValidateOn, textInputProps, useField, useFieldArray, useForm, useFormError, useFormSelector, useFormSelectorShallow, useIsDirty, useIsSubmitting, useIsValid, useSubmitCount, validateAsync, validateSync };
|
|
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 };
|
|
1534
1757
|
//# sourceMappingURL=index.js.map
|
|
1535
1758
|
//# sourceMappingURL=index.js.map
|