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