@homebound/form-state 2.10.1 → 2.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/dist/fields/listField.js +7 -0
- package/dist/fields/objectField.js +7 -0
- package/dist/fields/valueField.d.ts +1 -0
- package/dist/fields/valueField.js +12 -3
- package/dist/formState.test.js +36 -2
- package/dist/useFormState.js +6 -0
- package/dist/useFormState.test.js +17 -2
- package/package.json +1 -1
package/dist/fields/listField.js
CHANGED
|
@@ -19,12 +19,19 @@ function newListFieldState(parentInstance, parentState, key, rules, listConfig,
|
|
|
19
19
|
},
|
|
20
20
|
_focused: false,
|
|
21
21
|
_readOnly: false,
|
|
22
|
+
_loading: false,
|
|
22
23
|
get readOnly() {
|
|
23
24
|
return this._readOnly || parentState().readOnly;
|
|
24
25
|
},
|
|
25
26
|
set readOnly(readOnly) {
|
|
26
27
|
this._readOnly = readOnly;
|
|
27
28
|
},
|
|
29
|
+
get loading() {
|
|
30
|
+
return this._loading || parentState().loading;
|
|
31
|
+
},
|
|
32
|
+
set loading(loading) {
|
|
33
|
+
this._loading = loading;
|
|
34
|
+
},
|
|
28
35
|
set value(v) {
|
|
29
36
|
this.set(v);
|
|
30
37
|
},
|
|
@@ -68,6 +68,7 @@ function newObjectState(config, parentState, parentListState, instance, key, may
|
|
|
68
68
|
},
|
|
69
69
|
// private
|
|
70
70
|
_readOnly: false,
|
|
71
|
+
_loading: false,
|
|
71
72
|
_considerDeleted() {
|
|
72
73
|
const deleteField = getFields(this).find((f) => f._isDeleteKey);
|
|
73
74
|
return !!(deleteField === null || deleteField === void 0 ? void 0 : deleteField.value);
|
|
@@ -91,6 +92,12 @@ function newObjectState(config, parentState, parentListState, instance, key, may
|
|
|
91
92
|
set readOnly(readOnly) {
|
|
92
93
|
this._readOnly = readOnly;
|
|
93
94
|
},
|
|
95
|
+
get loading() {
|
|
96
|
+
return this._loading || (parentState && parentState().loading) || !!(parentListState === null || parentListState === void 0 ? void 0 : parentListState.loading);
|
|
97
|
+
},
|
|
98
|
+
set loading(loading) {
|
|
99
|
+
this._loading = loading;
|
|
100
|
+
},
|
|
94
101
|
get valid() {
|
|
95
102
|
return getFields(this).every((f) => f.valid);
|
|
96
103
|
},
|
|
@@ -18,6 +18,7 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
|
|
|
18
18
|
touched: false,
|
|
19
19
|
/** Current readOnly value. */
|
|
20
20
|
_readOnly: readOnly || false,
|
|
21
|
+
_loading: false,
|
|
21
22
|
_focused: false,
|
|
22
23
|
_isIdKey: isIdKey,
|
|
23
24
|
_isDeleteKey: isDeleteKey,
|
|
@@ -40,9 +41,17 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
|
|
|
40
41
|
get readOnly() {
|
|
41
42
|
return parentState().readOnly || this._readOnly;
|
|
42
43
|
},
|
|
43
|
-
/** Sets the field readOnly, but `
|
|
44
|
-
set readOnly(
|
|
45
|
-
this._readOnly =
|
|
44
|
+
/** Sets the field readOnly, but `loading` will still be or-d with the parent's readOnly state. */
|
|
45
|
+
set readOnly(readOnly) {
|
|
46
|
+
this._readOnly = readOnly;
|
|
47
|
+
},
|
|
48
|
+
/** Returns whether this field is loading, or if our parent is loading. */
|
|
49
|
+
get loading() {
|
|
50
|
+
return parentState().loading || this._loading;
|
|
51
|
+
},
|
|
52
|
+
/** Sets the field loading, but `loading` will still be or-d with the parent's loading state. */
|
|
53
|
+
set loading(loading) {
|
|
54
|
+
this._loading = loading;
|
|
46
55
|
},
|
|
47
56
|
// For primitive fields, the changed value is just the value.
|
|
48
57
|
get changedValue() {
|
package/dist/formState.test.js
CHANGED
|
@@ -696,7 +696,7 @@ describe("formState", () => {
|
|
|
696
696
|
});
|
|
697
697
|
// And it is read-only
|
|
698
698
|
a1.readOnly = true;
|
|
699
|
-
// Then
|
|
699
|
+
// Then its fields are read-only
|
|
700
700
|
const fields = [a1, a1.firstName, a1.books, a1.books.rows[0].title, a1.books.rows[0].classification];
|
|
701
701
|
fields.forEach((f) => expect(f.readOnly).toBeTruthy());
|
|
702
702
|
// And even if a specific field tries to _not_ be read-only,
|
|
@@ -715,7 +715,7 @@ describe("formState", () => {
|
|
|
715
715
|
});
|
|
716
716
|
// And the top-level form is explicitly set to read-only=false
|
|
717
717
|
a1.readOnly = false;
|
|
718
|
-
// Then
|
|
718
|
+
// Then its fields are not read-only
|
|
719
719
|
const fields = [a1, a1.firstName, a1.books, a1.books.rows[0].title, a1.books.rows[0].classification];
|
|
720
720
|
fields.forEach((f) => expect(f.readOnly).toBeFalsy());
|
|
721
721
|
// But when one of the fields opts in to readOnly
|
|
@@ -723,6 +723,40 @@ describe("formState", () => {
|
|
|
723
723
|
// Then the field-level rule is respected
|
|
724
724
|
expect(a1.firstName.readOnly).toBeTruthy();
|
|
725
725
|
});
|
|
726
|
+
it("has an object-level loading=true override field-level loading=false", () => {
|
|
727
|
+
// Given a top-level object
|
|
728
|
+
const a1 = createAuthorInputState({
|
|
729
|
+
firstName: "a1",
|
|
730
|
+
lastName: "aL1",
|
|
731
|
+
books: [{ title: "b1", classification: formStateDomain_1.dd100 }],
|
|
732
|
+
});
|
|
733
|
+
// And it is loading
|
|
734
|
+
a1.loading = true;
|
|
735
|
+
// Then its fields are read-only
|
|
736
|
+
const fields = [a1, a1.firstName, a1.books, a1.books.rows[0].title, a1.books.rows[0].classification];
|
|
737
|
+
fields.forEach((f) => expect(f.loading).toBeTruthy());
|
|
738
|
+
// And even if a specific field tries to _not_ be loading
|
|
739
|
+
a1.firstName.loading = false;
|
|
740
|
+
// Then the field-level value is ignored, and it's still treated as loading
|
|
741
|
+
expect(a1.firstName.loading).toBeTruthy();
|
|
742
|
+
});
|
|
743
|
+
it("has an field-level loading=true override object-level loading=false", () => {
|
|
744
|
+
// Given a top-level object
|
|
745
|
+
const a1 = createAuthorInputState({
|
|
746
|
+
firstName: "a1",
|
|
747
|
+
lastName: "aL1",
|
|
748
|
+
books: [{ title: "b1", classification: formStateDomain_1.dd100 }],
|
|
749
|
+
});
|
|
750
|
+
// And the top-level form is explicitly set to loading=false
|
|
751
|
+
a1.loading = false;
|
|
752
|
+
// Then its fields are not loading
|
|
753
|
+
const fields = [a1, a1.firstName, a1.books, a1.books.rows[0].title, a1.books.rows[0].classification];
|
|
754
|
+
fields.forEach((f) => expect(f.loading).toBeFalsy());
|
|
755
|
+
// But when one of the fields opts in to loading
|
|
756
|
+
a1.firstName.loading = true;
|
|
757
|
+
// Then the field-level loading is respected
|
|
758
|
+
expect(a1.firstName.loading).toBeTruthy();
|
|
759
|
+
});
|
|
726
760
|
it("canSave returns dirty and touches", () => {
|
|
727
761
|
const a1 = objectField_1.createObjectState({
|
|
728
762
|
firstName: { type: "value", rules: [rules_1.required] },
|
package/dist/useFormState.js
CHANGED
|
@@ -62,6 +62,9 @@ function useFormState(opts) {
|
|
|
62
62
|
const value = firstRunRef.current ? firstInitValue : utils_1.initValue(config, init);
|
|
63
63
|
const form = objectField_1.createObjectState(config, value, { maybeAutoSave });
|
|
64
64
|
form.readOnly = readOnly;
|
|
65
|
+
if (init && "input" in init && !("ifUndefined" in init)) {
|
|
66
|
+
form.loading = init.input === undefined;
|
|
67
|
+
}
|
|
65
68
|
// The identity of `addRules` is not stable, but assume that it is for better UX.
|
|
66
69
|
(addRules || (() => { }))(form);
|
|
67
70
|
firstRunRef.current = true;
|
|
@@ -80,6 +83,9 @@ function useFormState(opts) {
|
|
|
80
83
|
firstRunRef.current = false;
|
|
81
84
|
return;
|
|
82
85
|
}
|
|
86
|
+
if (init && "input" in init && !("ifUndefined" in init)) {
|
|
87
|
+
form.loading = init.input === undefined;
|
|
88
|
+
}
|
|
83
89
|
form.set(utils_1.initValue(config, init), { refreshing: true });
|
|
84
90
|
}, [form, ...dep]);
|
|
85
91
|
// Use useEffect so that we don't touch the form.init proxy during a render
|
|
@@ -15,10 +15,10 @@ describe("useFormState", () => {
|
|
|
15
15
|
function TestComponent() {
|
|
16
16
|
const config = { firstName: { type: "value" } };
|
|
17
17
|
// And we have query data that may or may not be defined
|
|
18
|
-
const data =
|
|
19
|
-
// Then the lambda is passed the "de-undefined" data
|
|
18
|
+
const data = { firstName: "bob" };
|
|
20
19
|
const form = useFormState_1.useFormState({
|
|
21
20
|
config,
|
|
21
|
+
// Then the lambda is passed the "de-undefined" data, i.e. `d.firstName` is not a compile error
|
|
22
22
|
init: { input: data, map: (d) => ({ firstName: d.firstName }) },
|
|
23
23
|
});
|
|
24
24
|
return jsx_runtime_1.jsx("div", { children: form.firstName.value }, void 0);
|
|
@@ -454,6 +454,21 @@ describe("useFormState", () => {
|
|
|
454
454
|
expect(autoSaveStub).toBeCalledTimes(1);
|
|
455
455
|
expect(autoSaveStub).toBeCalledWith({ id: "a:1", firstName: "first", lastName: "first" });
|
|
456
456
|
});
|
|
457
|
+
it("sets loading if input.data is undefined", async () => {
|
|
458
|
+
const config = { firstName: { type: "value" } };
|
|
459
|
+
function TestComponent({ data }) {
|
|
460
|
+
const form = useFormState_1.useFormState({ config, init: { input: data, map: (d) => d } });
|
|
461
|
+
return jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "loading" }, { children: String(form.loading) }), void 0) }, void 0);
|
|
462
|
+
}
|
|
463
|
+
// And we initially pass in `init.input: undefined`
|
|
464
|
+
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, { data: undefined }, void 0));
|
|
465
|
+
// Then the form is marked as loading
|
|
466
|
+
expect(r.loading()).toHaveTextContent("true");
|
|
467
|
+
// And when the data is loading
|
|
468
|
+
await r.rerender(jsx_runtime_1.jsx(TestComponent, { data: { firstName: "first" } }, void 0));
|
|
469
|
+
// Then the form is marked as not loading
|
|
470
|
+
expect(r.loading()).toHaveTextContent("false");
|
|
471
|
+
});
|
|
457
472
|
});
|
|
458
473
|
const authorConfig = {
|
|
459
474
|
id: { type: "value" },
|