@homebound/form-state 2.22.2 → 2.22.4
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/dist/fields/listField.js
CHANGED
|
@@ -31,7 +31,9 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
31
31
|
if (!childState) {
|
|
32
32
|
for (const [, otherState] of rowMap.entries()) {
|
|
33
33
|
if (otherState.isSameEntity(child)) {
|
|
34
|
-
|
|
34
|
+
// If we're being called from `list.rows`, we should be careful to not trigger mutations
|
|
35
|
+
if (!(opts === null || opts === void 0 ? void 0 : opts.skipSet))
|
|
36
|
+
otherState.set(child, opts);
|
|
35
37
|
rowMap.set(child, otherState);
|
|
36
38
|
return otherState;
|
|
37
39
|
}
|
|
@@ -100,7 +102,7 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
100
102
|
(0, utils_1.fail)();
|
|
101
103
|
// Avoid using `this.value` to avoid registering `_childTick` as a dependency
|
|
102
104
|
const value = parentInstance[key];
|
|
103
|
-
return (value || []).map((child) => getOrCreateChildState(child));
|
|
105
|
+
return (value || []).map((child) => getOrCreateChildState(child, { skipSet: true }));
|
|
104
106
|
},
|
|
105
107
|
// TODO Should this be true when all rows are touched?
|
|
106
108
|
get touched() {
|
|
@@ -163,6 +163,8 @@ function newObjectState(config, parentState, parentListState, instance, key, may
|
|
|
163
163
|
get changedValue() {
|
|
164
164
|
const result = {};
|
|
165
165
|
getFields(this).forEach((f) => {
|
|
166
|
+
if (f._computed)
|
|
167
|
+
return;
|
|
166
168
|
if (f.dirty ||
|
|
167
169
|
// If the caller used useFormState.ifUndefined to provide some default values, then those keys may not
|
|
168
170
|
// look dirty, but if we're new we should include them anyway.
|
|
@@ -18,6 +18,8 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
18
18
|
_readOnly: readOnly || false,
|
|
19
19
|
_loading: false,
|
|
20
20
|
_focused: false,
|
|
21
|
+
// Expose so computed can be skipped in changedValue
|
|
22
|
+
_computed: computed,
|
|
21
23
|
_isIdKey: isIdKey,
|
|
22
24
|
_isDeleteKey: isDeleteKey,
|
|
23
25
|
_isReadOnlyKey: isReadOnlyKey,
|
|
@@ -54,6 +56,12 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
54
56
|
},
|
|
55
57
|
// For primitive fields, the changed value is just the value.
|
|
56
58
|
get changedValue() {
|
|
59
|
+
// Usually if we see a field being unset, we set `parent[key] = null`, but if we're wrapping
|
|
60
|
+
// a mobx observable object, it might have changed to `undefined` without us being able to
|
|
61
|
+
// tell it to be `null` (and potentially break the type contract of `string | undefined`).
|
|
62
|
+
// So detect un-set-ness here and return `null`.
|
|
63
|
+
if (this.value === undefined && this.originalValue !== undefined)
|
|
64
|
+
return null;
|
|
57
65
|
return this.value;
|
|
58
66
|
},
|
|
59
67
|
get valid() {
|
package/dist/formState.test.js
CHANGED
|
@@ -442,6 +442,20 @@ describe("formState", () => {
|
|
|
442
442
|
a1.books.remove(0);
|
|
443
443
|
expect(a1.dirty).toBeFalsy();
|
|
444
444
|
});
|
|
445
|
+
it("handles list fields with duplicate ids", () => {
|
|
446
|
+
// Given an author that incorrectly has duplicate books
|
|
447
|
+
const a1 = createAuthorInputState({
|
|
448
|
+
books: [
|
|
449
|
+
{ id: "b:1", title: "b1" },
|
|
450
|
+
{ id: "b:1", title: "b2" },
|
|
451
|
+
],
|
|
452
|
+
});
|
|
453
|
+
// Then it didn't blow up
|
|
454
|
+
expect(a1.books.dirty).toBeFalsy();
|
|
455
|
+
// And the 1st book won
|
|
456
|
+
expect(a1.books.rows[0].title.value).toBe("b1");
|
|
457
|
+
expect(a1.books.rows[1].title.value).toBe("b1");
|
|
458
|
+
});
|
|
445
459
|
it("calls maybeAutoSave when adding or removing to a list", () => {
|
|
446
460
|
const maybeAutoSave = jest.fn();
|
|
447
461
|
const a1 = createAuthorInputState({ books: [] }, maybeAutoSave);
|
|
@@ -979,11 +993,7 @@ describe("formState", () => {
|
|
|
979
993
|
it("can observe observable objects being mutated directly", () => {
|
|
980
994
|
// Given a mobx class with a computed
|
|
981
995
|
const instance = new ObservableObject();
|
|
982
|
-
const formState = (0, objectField_1.createObjectState)(
|
|
983
|
-
firstName: { type: "value" },
|
|
984
|
-
lastName: { type: "value" },
|
|
985
|
-
fullName: { type: "value", computed: true },
|
|
986
|
-
}, instance);
|
|
996
|
+
const formState = (0, objectField_1.createObjectState)(authorWithFullName, instance);
|
|
987
997
|
expect(formState.firstName.value).toEqual("first");
|
|
988
998
|
expect(formState.fullName.value).toEqual("first last");
|
|
989
999
|
// And we hook up reactivity to the computed
|
|
@@ -999,9 +1009,11 @@ describe("formState", () => {
|
|
|
999
1009
|
expect(formState.fullName.value).toEqual("change last");
|
|
1000
1010
|
// And when the underlying mobx class has a field unset
|
|
1001
1011
|
instance.firstName = undefined;
|
|
1002
|
-
// Then our
|
|
1012
|
+
// Then our form-state computed re-runs again
|
|
1003
1013
|
expect(numCalcs).toBe(3);
|
|
1004
1014
|
expect(formState.fullName.value).toEqual("undefined last");
|
|
1015
|
+
// And knows the field should be sent as null
|
|
1016
|
+
expect(formState.changedValue).toEqual({ firstName: null });
|
|
1005
1017
|
});
|
|
1006
1018
|
it("can set computeds that have setters", () => {
|
|
1007
1019
|
const formState = (0, objectField_1.createObjectState)({
|
|
@@ -1086,6 +1098,18 @@ describe("formState", () => {
|
|
|
1086
1098
|
// because this might trigger creating a throw-away entity
|
|
1087
1099
|
expect(formState.changedValue).toEqual({ firstName: "f" });
|
|
1088
1100
|
});
|
|
1101
|
+
it("changedValue skips computeds", () => {
|
|
1102
|
+
// Given a new author that calls full name
|
|
1103
|
+
const formState = (0, objectField_1.createObjectState)(authorWithFullName, new ObservableObject());
|
|
1104
|
+
expect(formState.fullName.value).toEqual("first last");
|
|
1105
|
+
// When the firstName changes
|
|
1106
|
+
formState.firstName.value = "First";
|
|
1107
|
+
// Then the fullName changes
|
|
1108
|
+
expect(formState.fullName.value).toBe("First last");
|
|
1109
|
+
expect(formState.fullName.dirty).toBe(true);
|
|
1110
|
+
// And we don't include it in the changedValue
|
|
1111
|
+
expect(formState.changedValue).toEqual({ firstName: "First" });
|
|
1112
|
+
});
|
|
1089
1113
|
it("can return only changed list fields", () => {
|
|
1090
1114
|
// Given an author with some books
|
|
1091
1115
|
const formState = (0, objectField_1.createObjectState)(authorWithBooksConfig, {
|
|
@@ -1579,6 +1603,11 @@ const authorWithBooksConfig = configBuilders_1.f.config({
|
|
|
1579
1603
|
function createAuthorInputState(input, maybeAutoSave) {
|
|
1580
1604
|
return (0, objectField_1.createObjectState)(authorWithBooksConfig, input, { maybeAutoSave });
|
|
1581
1605
|
}
|
|
1606
|
+
const authorWithFullName = {
|
|
1607
|
+
firstName: { type: "value" },
|
|
1608
|
+
lastName: { type: "value" },
|
|
1609
|
+
fullName: { type: "value", computed: true },
|
|
1610
|
+
};
|
|
1582
1611
|
const authorWithAddressConfig = {
|
|
1583
1612
|
id: { type: "value" },
|
|
1584
1613
|
firstName: { type: "value" },
|