@homebound/form-state 2.22.4 → 2.22.5
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
|
@@ -73,6 +73,9 @@ function newListFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
73
73
|
get dirty() {
|
|
74
74
|
return this.rows.some((r) => r.dirty) || this.hasChanged();
|
|
75
75
|
},
|
|
76
|
+
get focused() {
|
|
77
|
+
return this.rows.some((r) => r.focused);
|
|
78
|
+
},
|
|
76
79
|
get required() {
|
|
77
80
|
return this.rules.some((rule) => rule === rules_1.required);
|
|
78
81
|
},
|
|
@@ -97,6 +97,9 @@ function newObjectState(config, parentState, parentListState, instance, key, may
|
|
|
97
97
|
const readOnlyField = getFields(this).find((f) => f._isReadOnlyKey);
|
|
98
98
|
return !!(readOnlyField === null || readOnlyField === void 0 ? void 0 : readOnlyField.value);
|
|
99
99
|
},
|
|
100
|
+
get focused() {
|
|
101
|
+
return getFields(this).some((f) => f.focused);
|
|
102
|
+
},
|
|
100
103
|
get touched() {
|
|
101
104
|
return getFields(this).some((f) => f.touched);
|
|
102
105
|
},
|
|
@@ -22,6 +22,7 @@ export interface FieldState<V> {
|
|
|
22
22
|
readonly required: boolean;
|
|
23
23
|
readonly dirty: boolean;
|
|
24
24
|
readonly valid: boolean;
|
|
25
|
+
readonly focused: boolean;
|
|
25
26
|
readonly isNewEntity: boolean;
|
|
26
27
|
rules: Rule<V>[];
|
|
27
28
|
readonly errors: string[];
|
|
@@ -69,6 +70,5 @@ export interface FieldStateInternal<T, V> extends FieldState<V> {
|
|
|
69
70
|
_isIdKey: boolean;
|
|
70
71
|
_isDeleteKey: boolean;
|
|
71
72
|
_isReadOnlyKey: boolean;
|
|
72
|
-
_focused: boolean;
|
|
73
73
|
}
|
|
74
74
|
export declare function newValueFieldState<T, K extends keyof T>(parentCopy: T, parentInstance: T, parentState: () => ObjectState<T>, key: K, rules: Rule<T[K] | null | undefined>[], isIdKey: boolean, isDeleteKey: boolean, isReadOnlyKey: boolean, computed: boolean, readOnly: boolean, strictOrder: boolean, maybeAutoSave: () => void): FieldState<T[K] | null | undefined>;
|
|
@@ -17,9 +17,9 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
17
17
|
/** Current readOnly value. */
|
|
18
18
|
_readOnly: readOnly || false,
|
|
19
19
|
_loading: false,
|
|
20
|
-
_focused: false,
|
|
21
20
|
// Expose so computed can be skipped in changedValue
|
|
22
21
|
_computed: computed,
|
|
22
|
+
_focused: false,
|
|
23
23
|
_isIdKey: isIdKey,
|
|
24
24
|
_isDeleteKey: isDeleteKey,
|
|
25
25
|
_isReadOnlyKey: isReadOnlyKey,
|
|
@@ -35,6 +35,9 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
35
35
|
set value(v) {
|
|
36
36
|
this.set(v);
|
|
37
37
|
},
|
|
38
|
+
get focused() {
|
|
39
|
+
return this._focused;
|
|
40
|
+
},
|
|
38
41
|
get dirty() {
|
|
39
42
|
return !(0, utils_1.areEqual)(this.originalValue, this.value, strictOrder);
|
|
40
43
|
},
|
|
@@ -167,6 +170,16 @@ function newValueFieldState(parentCopy, parentInstance, parentState, key, rules,
|
|
|
167
170
|
}
|
|
168
171
|
},
|
|
169
172
|
};
|
|
173
|
+
// If we're wrapping a mobx observer, watching for external mutations, i.e. from callers not
|
|
174
|
+
// going through our FieldState.set/FieldState.value setters.
|
|
175
|
+
if ((0, mobx_1.isObservable)(parentInstance)) {
|
|
176
|
+
(0, mobx_1.reaction)(() => parentInstance[key], () => {
|
|
177
|
+
// Don't auto-save on maybe-external mutation if our field, or another other field (potentially a computed),
|
|
178
|
+
// is currently focused, because then it's probably just us doing the writes through `Bound...Field` components.
|
|
179
|
+
if (!parentState().focused)
|
|
180
|
+
maybeAutoSave();
|
|
181
|
+
});
|
|
182
|
+
}
|
|
170
183
|
return field;
|
|
171
184
|
}
|
|
172
185
|
exports.newValueFieldState = newValueFieldState;
|
|
@@ -283,6 +283,32 @@ describe("useFormState", () => {
|
|
|
283
283
|
expect(r.booksLength.textContent).toEqual("1");
|
|
284
284
|
expect(r.booksDirty.textContent).toEqual("false");
|
|
285
285
|
});
|
|
286
|
+
it("can trigger auto-save when underlying observable is changed", async () => {
|
|
287
|
+
// Given a component
|
|
288
|
+
// And it's using a class/mobx proxy as the basis for the data
|
|
289
|
+
class AuthorRow {
|
|
290
|
+
constructor(firstName) {
|
|
291
|
+
this.firstName = firstName;
|
|
292
|
+
(0, mobx_1.makeAutoObservable)(this);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const autoSave = jest.fn();
|
|
296
|
+
function TestComponent() {
|
|
297
|
+
// And the author starts out with f1
|
|
298
|
+
const author = (0, react_2.useMemo)(() => new AuthorRow("f1"), []);
|
|
299
|
+
const config = (0, react_2.useMemo)(() => ({ firstName: { type: "value" } }), []);
|
|
300
|
+
const form = (0, useFormState_1.useFormState)({ config, init: { input: author, map: (a) => a }, autoSave });
|
|
301
|
+
return ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("button", { "data-testid": "changeFirstName", onClick: () => (author.firstName = "f2") }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "clearFirstName", onClick: () => (author.firstName = undefined) })] }));
|
|
302
|
+
}
|
|
303
|
+
// When we change the underlying observable
|
|
304
|
+
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(TestComponent, {}));
|
|
305
|
+
expect(autoSave).toBeCalledTimes(0);
|
|
306
|
+
await (0, rtl_utils_1.clickAndWait)(r.changeFirstName);
|
|
307
|
+
// Then auto save is triggered
|
|
308
|
+
expect(autoSave).toBeCalledTimes(1);
|
|
309
|
+
await (0, rtl_utils_1.clickAndWait)(r.clearFirstName);
|
|
310
|
+
expect(autoSave).toBeCalledTimes(2);
|
|
311
|
+
});
|
|
286
312
|
it("can trigger auto save for fields in list that were initially undefined", async () => {
|
|
287
313
|
const autoSave = jest.fn();
|
|
288
314
|
// Given a component
|