formstand 0.7.0 → 0.8.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 +209 -0
- package/dist/index.cjs +42 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +42 -26
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ var valuesEqual = (a, b) => {
|
|
|
57
57
|
if (isPlainObject(a) && isPlainObject(b)) {
|
|
58
58
|
const aKeys = Object.keys(a);
|
|
59
59
|
const bKeys = Object.keys(b);
|
|
60
|
-
return aKeys.length === bKeys.length && aKeys.every((k) => valuesEqual(a[k], b[k]));
|
|
60
|
+
return aKeys.length === bKeys.length && aKeys.every((k) => Object.hasOwn(b, k) && valuesEqual(a[k], b[k]));
|
|
61
61
|
}
|
|
62
62
|
return false;
|
|
63
63
|
};
|
|
@@ -92,7 +92,7 @@ var readSegment = (obj, segment) => {
|
|
|
92
92
|
if (Array.isArray(obj)) {
|
|
93
93
|
return typeof segment === "number" ? obj[segment] : void 0;
|
|
94
94
|
}
|
|
95
|
-
return isPlainRecord(obj) ? obj[String(segment)] : void 0;
|
|
95
|
+
return isPlainRecord(obj) && Object.hasOwn(obj, String(segment)) ? obj[String(segment)] : void 0;
|
|
96
96
|
};
|
|
97
97
|
var getAtPath = (obj, path) => parsePath(path).reduce(readSegment, obj);
|
|
98
98
|
var slotAtPath = (obj, path) => {
|
|
@@ -135,6 +135,12 @@ var writeSegments = (obj, segments, value) => {
|
|
|
135
135
|
return writeIntoArray(obj, head, rest, value);
|
|
136
136
|
}
|
|
137
137
|
if (isPlainRecord(obj)) {
|
|
138
|
+
if (!isPlainObject(obj)) {
|
|
139
|
+
console.warn(
|
|
140
|
+
`[formstand] cannot write key "${String(head)}" through a non-plain object (Date/Map/Set/class instance); value left unchanged.`
|
|
141
|
+
);
|
|
142
|
+
return obj;
|
|
143
|
+
}
|
|
138
144
|
const key = String(head);
|
|
139
145
|
return { ...obj, [key]: writeSegments(obj[key], rest, value) };
|
|
140
146
|
}
|
|
@@ -668,7 +674,11 @@ var createForm = (schema, options) => {
|
|
|
668
674
|
});
|
|
669
675
|
}
|
|
670
676
|
const merged = stale ? result.errors : store.getState().errors;
|
|
671
|
-
|
|
677
|
+
try {
|
|
678
|
+
onInvalid?.(merged);
|
|
679
|
+
} catch (error) {
|
|
680
|
+
return { kind: "error", error };
|
|
681
|
+
}
|
|
672
682
|
return { kind: "invalid", errors: merged };
|
|
673
683
|
}
|
|
674
684
|
if (!stale) {
|
|
@@ -928,9 +938,13 @@ var createForm = (schema, options) => {
|
|
|
928
938
|
// In-flight state is owned by live passes, never by snapshots: a
|
|
929
939
|
// snapshotted flag has no pass left to clear it (the pass that set
|
|
930
940
|
// it cleared its ORIGINAL entry when it settled), so restoring one
|
|
931
|
-
// would stick it forever.
|
|
941
|
+
// would stick it forever. isSubmitting follows the same rule — a
|
|
942
|
+
// mid-submit snapshot restored later must not disable submit
|
|
943
|
+
// buttons forever, so it reflects the LIVE submit count instead of
|
|
944
|
+
// the snapshotted flag (a live pass's finally still resets it).
|
|
932
945
|
isValidating: emptyBools,
|
|
933
|
-
isValidatingForm: false
|
|
946
|
+
isValidatingForm: false,
|
|
947
|
+
isSubmitting: inFlight.count > 0
|
|
934
948
|
};
|
|
935
949
|
})
|
|
936
950
|
});
|
|
@@ -958,15 +972,17 @@ var useForm = (schema, options) => {
|
|
|
958
972
|
formRef.current = createForm(schema, options);
|
|
959
973
|
}
|
|
960
974
|
const form = formRef.current;
|
|
975
|
+
const mountInitialsRef = useRef(options.initialValues);
|
|
961
976
|
const warnedRef = useRef(false);
|
|
962
977
|
useEffect(() => {
|
|
963
|
-
|
|
978
|
+
const changedInitials = !Object.is(options.initialValues, mountInitialsRef.current) && !valuesEqual(options.initialValues, mountInitialsRef.current);
|
|
979
|
+
if (!warnedRef.current && (form.schema !== schema || changedInitials)) {
|
|
964
980
|
warnedRef.current = true;
|
|
965
981
|
console.warn(
|
|
966
|
-
"[formstand] useForm received a different schema
|
|
982
|
+
"[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
983
|
);
|
|
968
984
|
}
|
|
969
|
-
}, [form, schema]);
|
|
985
|
+
}, [form, schema, options.initialValues]);
|
|
970
986
|
return form;
|
|
971
987
|
};
|
|
972
988
|
var createFormContext = () => {
|
|
@@ -1132,23 +1148,22 @@ var reconcileIds = (prev, nextItems) => {
|
|
|
1132
1148
|
const prevIndex = buckets.get(item)?.shift();
|
|
1133
1149
|
return prevIndex === void 0 ? null : prev.ids[prevIndex] ?? null;
|
|
1134
1150
|
});
|
|
1135
|
-
const
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1151
|
+
const leftoverAt = new Map(
|
|
1152
|
+
[...buckets.values()].flat().flatMap((i) => {
|
|
1153
|
+
const id = prev.ids[i];
|
|
1154
|
+
return id === void 0 ? [] : [[i, id]];
|
|
1155
|
+
})
|
|
1156
|
+
);
|
|
1157
|
+
const reused = matchedIds.map((id, i) => id ?? leftoverAt.get(i) ?? null);
|
|
1158
|
+
const mintCounts = reused.reduce((acc, id) => {
|
|
1159
|
+
const count = acc[acc.length - 1] ?? 0;
|
|
1160
|
+
return [...acc, id === null ? count + 1 : count];
|
|
1140
1161
|
}, []);
|
|
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
|
-
};
|
|
1162
|
+
const minted = mintCounts[mintCounts.length - 1] ?? 0;
|
|
1163
|
+
const ids = reused.map(
|
|
1164
|
+
(id, i) => id ?? `__zfa_${prev.counter + (mintCounts[i] ?? 0)}`
|
|
1165
|
+
);
|
|
1166
|
+
return { items: nextItems, ids, counter: prev.counter + minted };
|
|
1152
1167
|
};
|
|
1153
1168
|
function useFieldArray(form, pathArg) {
|
|
1154
1169
|
const path = useStore(
|
|
@@ -1465,7 +1480,8 @@ var SelectField = ({
|
|
|
1465
1480
|
const id = useId();
|
|
1466
1481
|
const errorId = `${id}-error`;
|
|
1467
1482
|
const field = useField(form, path);
|
|
1468
|
-
const
|
|
1483
|
+
const clearable = field.emptyValue === null;
|
|
1484
|
+
const showEmptyOption = clearable || field.value === void 0 || field.value === null || placeholder !== void 0;
|
|
1469
1485
|
return /* @__PURE__ */ jsxs("div", { className: "zf-field", children: [
|
|
1470
1486
|
label !== void 0 ? /* @__PURE__ */ jsx("label", { htmlFor: id, className: "zf-label", children: label }) : null,
|
|
1471
1487
|
/* @__PURE__ */ jsxs(
|
|
@@ -1476,7 +1492,7 @@ var SelectField = ({
|
|
|
1476
1492
|
...selectProps(field),
|
|
1477
1493
|
"aria-describedby": hasError(field.error) ? errorId : void 0,
|
|
1478
1494
|
children: [
|
|
1479
|
-
showEmptyOption ? /* @__PURE__ */ jsx("option", { value: "", disabled:
|
|
1495
|
+
showEmptyOption ? /* @__PURE__ */ jsx("option", { value: "", disabled: !clearable, children: placeholder ?? "" }) : null,
|
|
1480
1496
|
options.map((opt, index) => (
|
|
1481
1497
|
// Index-qualified: option lists may legitimately repeat a value
|
|
1482
1498
|
// (e.g. differently-labelled aliases), and bare value keys would
|