@homebound/form-state 2.18.1 → 2.20.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/FormStateApp.d.ts +2 -3
- package/dist/FormStateApp.js +8 -47
- package/dist/FormStateApp.test.js +14 -14
- package/dist/config.d.ts +6 -6
- package/dist/fields/fragmentField.js +3 -3
- package/dist/fields/listField.js +12 -12
- package/dist/fields/objectField.d.ts +4 -4
- package/dist/fields/objectField.js +10 -10
- package/dist/fields/valueField.d.ts +13 -0
- package/dist/fields/valueField.js +49 -14
- package/dist/formState.stories.d.ts +2 -4
- package/dist/formState.stories.js +1 -1
- package/dist/formState.test.js +171 -77
- package/dist/formStateDomain.js +1 -1
- package/dist/index.d.ts +3 -4
- package/dist/index.js +0 -11
- package/dist/proxies.d.ts +5 -0
- package/dist/proxies.js +21 -0
- package/dist/rules.d.ts +1 -1
- package/dist/rules.js +1 -1
- package/dist/setupTests.js +2 -2
- package/dist/useFormState.d.ts +4 -4
- package/dist/useFormState.js +13 -17
- package/dist/useFormState.test.js +141 -198
- package/dist/useFormStates.d.ts +3 -3
- package/dist/useFormStates.js +7 -11
- package/dist/useFormStates.test.js +56 -89
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +8 -8
- package/dist/utils.test.js +49 -49
- package/package.json +44 -39
- package/dist/AutoSaveStatus/AutoSaveStatusProvider.d.ts +0 -24
- package/dist/AutoSaveStatus/AutoSaveStatusProvider.js +0 -91
- package/dist/AutoSaveStatus/index.d.ts +0 -2
- package/dist/AutoSaveStatus/index.js +0 -9
- package/dist/AutoSaveStatus/useAutoSaveStatus.d.ts +0 -1
- package/dist/AutoSaveStatus/useAutoSaveStatus.js +0 -9
- package/dist/AutoSaveStatus/useAutoSaveStatus.test.d.ts +0 -1
- package/dist/AutoSaveStatus/useAutoSaveStatus.test.js +0 -152
package/dist/FormStateApp.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
1
|
import { FieldState } from "./index";
|
|
3
|
-
export declare function FormStateApp(): JSX.Element;
|
|
2
|
+
export declare function FormStateApp(): import("react/jsx-runtime").JSX.Element;
|
|
4
3
|
export declare function TextField(props: {
|
|
5
4
|
field: FieldState<string | null | undefined>;
|
|
6
|
-
}): JSX.Element;
|
|
5
|
+
}): import("react/jsx-runtime").JSX.Element;
|
package/dist/FormStateApp.js
CHANGED
|
@@ -5,7 +5,7 @@ const jsx_runtime_1 = require("react/jsx-runtime");
|
|
|
5
5
|
const mobx_react_1 = require("mobx-react");
|
|
6
6
|
const index_1 = require("./index");
|
|
7
7
|
function FormStateApp() {
|
|
8
|
-
const formState = index_1.useFormState({
|
|
8
|
+
const formState = (0, index_1.useFormState)({
|
|
9
9
|
config: formConfig,
|
|
10
10
|
// Simulate getting the initial form state back from a server call
|
|
11
11
|
init: {
|
|
@@ -24,38 +24,12 @@ function FormStateApp() {
|
|
|
24
24
|
console.log("saving", formState.changedValue);
|
|
25
25
|
},
|
|
26
26
|
});
|
|
27
|
-
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => {
|
|
27
|
+
return ((0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => {
|
|
28
28
|
var _a;
|
|
29
|
-
return (jsx_runtime_1.jsx("div",
|
|
30
|
-
|
|
31
|
-
jsx_runtime_1.jsx(
|
|
32
|
-
|
|
33
|
-
(_a = formState.books.rows) === null || _a === void 0 ? void 0 : _a.map((row, i) => {
|
|
34
|
-
return (jsx_runtime_1.jsxs("div", { children: ["Book ", i,
|
|
35
|
-
jsx_runtime_1.jsx("button", Object.assign({ onClick: () => formState.books.remove(row.value) }, { children: "X" }), void 0),
|
|
36
|
-
jsx_runtime_1.jsx(TextField, { field: row.title }, void 0)] }, i));
|
|
37
|
-
})] }, void 0),
|
|
38
|
-
jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("strong", { children: "Rows" }, void 0),
|
|
39
|
-
jsx_runtime_1.jsxs("table", Object.assign({ cellPadding: "4px" }, { children: [jsx_runtime_1.jsx("thead", { children: jsx_runtime_1.jsxs("tr", { children: [jsx_runtime_1.jsx("th", { children: "touched" }, void 0),
|
|
40
|
-
jsx_runtime_1.jsx("th", { children: "valid" }, void 0),
|
|
41
|
-
jsx_runtime_1.jsx("th", { children: "dirty" }, void 0),
|
|
42
|
-
jsx_runtime_1.jsx("th", { children: "errors" }, void 0)] }, void 0) }, void 0),
|
|
43
|
-
jsx_runtime_1.jsx("tbody", { children: jsx_runtime_1.jsxs("tr", { children: [jsx_runtime_1.jsx("td", { children: formState.books.touched.toString() }, void 0),
|
|
44
|
-
jsx_runtime_1.jsx("td", { children: formState.books.valid.toString() }, void 0),
|
|
45
|
-
jsx_runtime_1.jsx("td", { children: formState.books.dirty.toString() }, void 0),
|
|
46
|
-
jsx_runtime_1.jsx("td", { children: formState.books.errors }, void 0)] }, void 0) }, void 0)] }), void 0)] }, void 0),
|
|
47
|
-
jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("strong", { children: "Form" }, void 0),
|
|
48
|
-
jsx_runtime_1.jsxs("table", Object.assign({ cellPadding: "4px" }, { children: [jsx_runtime_1.jsx("thead", { children: jsx_runtime_1.jsxs("tr", { children: [jsx_runtime_1.jsx("th", { children: "touched" }, void 0),
|
|
49
|
-
jsx_runtime_1.jsx("th", { children: "valid" }, void 0),
|
|
50
|
-
jsx_runtime_1.jsx("th", { children: "dirty" }, void 0)] }, void 0) }, void 0),
|
|
51
|
-
jsx_runtime_1.jsx("tbody", { children: jsx_runtime_1.jsxs("tr", { children: [jsx_runtime_1.jsx("td", { children: formState.touched.toString() }, void 0),
|
|
52
|
-
jsx_runtime_1.jsx("td", { children: formState.valid.toString() }, void 0),
|
|
53
|
-
jsx_runtime_1.jsx("td", { children: formState.dirty.toString() }, void 0)] }, void 0) }, void 0)] }), void 0),
|
|
54
|
-
jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("button", Object.assign({ "data-testid": "touch", onClick: () => (formState.touched = !formState.touched) }, { children: "touch" }), void 0),
|
|
55
|
-
jsx_runtime_1.jsx("button", Object.assign({ "data-testid": "revertChanges", onClick: () => formState.revertChanges() }, { children: "revert" }), void 0),
|
|
56
|
-
jsx_runtime_1.jsx("button", Object.assign({ "data-testid": "commitChanges", onClick: () => formState.commitChanges() }, { children: "commit" }), void 0),
|
|
57
|
-
jsx_runtime_1.jsx("button", Object.assign({ "data-testid": "set", onClick: () => formState.set({ firstName: "a2" }) }, { children: "set" }), void 0)] }, void 0)] }, void 0)] }), void 0) }), void 0));
|
|
58
|
-
} }, void 0));
|
|
29
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "App", children: (0, jsx_runtime_1.jsxs)("header", { className: "App-header", children: [(0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("b", { children: "Author" }), (0, jsx_runtime_1.jsx)(TextField, { field: formState.firstName }), (0, jsx_runtime_1.jsx)(TextField, { field: formState.lastName })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("strong", { children: ["Books ", (0, jsx_runtime_1.jsx)("button", { onClick: () => formState.books.add({}), children: "Add book" })] }), (_a = formState.books.rows) === null || _a === void 0 ? void 0 : _a.map((row, i) => {
|
|
30
|
+
return ((0, jsx_runtime_1.jsxs)("div", { children: ["Book ", i, (0, jsx_runtime_1.jsx)("button", { onClick: () => formState.books.remove(row.value), children: "X" }), (0, jsx_runtime_1.jsx)(TextField, { field: row.title })] }, i));
|
|
31
|
+
})] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Rows" }), (0, jsx_runtime_1.jsxs)("table", { cellPadding: "4px", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "touched" }), (0, jsx_runtime_1.jsx)("th", { children: "valid" }), (0, jsx_runtime_1.jsx)("th", { children: "dirty" }), (0, jsx_runtime_1.jsx)("th", { children: "errors" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: formState.books.touched.toString() }), (0, jsx_runtime_1.jsx)("td", { children: formState.books.valid.toString() }), (0, jsx_runtime_1.jsx)("td", { children: formState.books.dirty.toString() }), (0, jsx_runtime_1.jsx)("td", { children: formState.books.errors })] }) })] })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("strong", { children: "Form" }), (0, jsx_runtime_1.jsxs)("table", { cellPadding: "4px", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "touched" }), (0, jsx_runtime_1.jsx)("th", { children: "valid" }), (0, jsx_runtime_1.jsx)("th", { children: "dirty" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { children: formState.touched.toString() }), (0, jsx_runtime_1.jsx)("td", { children: formState.valid.toString() }), (0, jsx_runtime_1.jsx)("td", { children: formState.dirty.toString() })] }) })] }), (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("button", { "data-testid": "touch", onClick: () => (formState.touched = !formState.touched), children: "touch" }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "revertChanges", onClick: () => formState.revertChanges(), children: "revert" }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "commitChanges", onClick: () => formState.commitChanges(), children: "commit" }), (0, jsx_runtime_1.jsx)("button", { "data-testid": "set", onClick: () => formState.set({ firstName: "a2" }), children: "set" })] })] })] }) }));
|
|
32
|
+
} }));
|
|
59
33
|
}
|
|
60
34
|
exports.FormStateApp = FormStateApp;
|
|
61
35
|
// Configure the fields/behavior for AuthorInput's fields
|
|
@@ -74,21 +48,8 @@ function TextField(props) {
|
|
|
74
48
|
const { field } = props;
|
|
75
49
|
// Somewhat odd: input won't update unless we use <Observer>, even though our
|
|
76
50
|
// parent uses `<Observer>`
|
|
77
|
-
return (jsx_runtime_1.jsx(mobx_react_1.Observer, { children: () => (jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsxs("span", { children: [field.key, ":"] },
|
|
78
|
-
jsx_runtime_1.jsx("div", { children: jsx_runtime_1.jsx("input", { "data-testid": field.key, value: field.value || "", onBlur: () => field.blur(), readOnly: field.readOnly, onChange: (e) => {
|
|
51
|
+
return ((0, jsx_runtime_1.jsx)(mobx_react_1.Observer, { children: () => ((0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsxs)("span", { children: [field.key, ":"] }), (0, jsx_runtime_1.jsx)("div", { children: (0, jsx_runtime_1.jsx)("input", { "data-testid": field.key, value: field.value || "", onBlur: () => field.blur(), readOnly: field.readOnly, onChange: (e) => {
|
|
79
52
|
field.set(e.target.value);
|
|
80
|
-
} },
|
|
81
|
-
jsx_runtime_1.jsxs("table", Object.assign({ cellPadding: "4px" }, { children: [jsx_runtime_1.jsx("thead", { children: jsx_runtime_1.jsxs("tr", { children: [jsx_runtime_1.jsx("th", { children: "touched" }, void 0),
|
|
82
|
-
jsx_runtime_1.jsx("th", { children: "valid" }, void 0),
|
|
83
|
-
jsx_runtime_1.jsx("th", { children: "dirty" }, void 0),
|
|
84
|
-
jsx_runtime_1.jsx("th", { children: "readOnly" }, void 0),
|
|
85
|
-
jsx_runtime_1.jsx("th", { children: "errors" }, void 0),
|
|
86
|
-
jsx_runtime_1.jsx("th", { children: "original value" }, void 0)] }, void 0) }, void 0),
|
|
87
|
-
jsx_runtime_1.jsx("tbody", { children: jsx_runtime_1.jsxs("tr", { children: [jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_touched` }, { children: field.touched.toString() }), void 0),
|
|
88
|
-
jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_valid` }, { children: field.valid.toString() }), void 0),
|
|
89
|
-
jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_dirty` }, { children: field.dirty.toString() }), void 0),
|
|
90
|
-
jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_readOnly` }, { children: field.readOnly.toString() }), void 0),
|
|
91
|
-
jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_errors` }, { children: field.errors }), void 0),
|
|
92
|
-
jsx_runtime_1.jsx("td", Object.assign({ "data-testid": `${field.key}_original` }, { children: field.originalValue }), void 0)] }, void 0) }, void 0)] }), void 0)] }, void 0)) }, void 0));
|
|
53
|
+
} }) }), (0, jsx_runtime_1.jsxs)("table", { cellPadding: "4px", children: [(0, jsx_runtime_1.jsx)("thead", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("th", { children: "touched" }), (0, jsx_runtime_1.jsx)("th", { children: "valid" }), (0, jsx_runtime_1.jsx)("th", { children: "dirty" }), (0, jsx_runtime_1.jsx)("th", { children: "readOnly" }), (0, jsx_runtime_1.jsx)("th", { children: "errors" }), (0, jsx_runtime_1.jsx)("th", { children: "original value" })] }) }), (0, jsx_runtime_1.jsx)("tbody", { children: (0, jsx_runtime_1.jsxs)("tr", { children: [(0, jsx_runtime_1.jsx)("td", { "data-testid": `${field.key}_touched`, children: field.touched.toString() }), (0, jsx_runtime_1.jsx)("td", { "data-testid": `${field.key}_valid`, children: field.valid.toString() }), (0, jsx_runtime_1.jsx)("td", { "data-testid": `${field.key}_dirty`, children: field.dirty.toString() }), (0, jsx_runtime_1.jsx)("td", { "data-testid": `${field.key}_readOnly`, children: field.readOnly.toString() }), (0, jsx_runtime_1.jsx)("td", { "data-testid": `${field.key}_errors`, children: field.errors }), (0, jsx_runtime_1.jsx)("td", { "data-testid": `${field.key}_original`, children: field.originalValue })] }) })] })] })) }));
|
|
93
54
|
}
|
|
94
55
|
exports.TextField = TextField;
|
|
@@ -6,21 +6,21 @@ const react_1 = require("@testing-library/react");
|
|
|
6
6
|
const FormStateApp_1 = require("./FormStateApp");
|
|
7
7
|
describe("FormStateApp", () => {
|
|
8
8
|
it("save resets dirty reactively", async () => {
|
|
9
|
-
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(FormStateApp_1.FormStateApp, {}
|
|
10
|
-
expect(r.firstName_dirty
|
|
11
|
-
rtl_utils_1.type(r.firstName, "changed");
|
|
12
|
-
expect(r.firstName_dirty
|
|
13
|
-
react_1.fireEvent.blur(r.firstName
|
|
14
|
-
expect(r.firstName_touched
|
|
15
|
-
rtl_utils_1.click(r.commitChanges);
|
|
16
|
-
expect(r.firstName_dirty
|
|
17
|
-
expect(r.firstName_touched
|
|
9
|
+
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(FormStateApp_1.FormStateApp, {}));
|
|
10
|
+
expect(r.firstName_dirty).toHaveTextContent("false");
|
|
11
|
+
(0, rtl_utils_1.type)(r.firstName, "changed");
|
|
12
|
+
expect(r.firstName_dirty).toHaveTextContent("true");
|
|
13
|
+
react_1.fireEvent.blur(r.firstName);
|
|
14
|
+
expect(r.firstName_touched).toHaveTextContent("true");
|
|
15
|
+
(0, rtl_utils_1.click)(r.commitChanges);
|
|
16
|
+
expect(r.firstName_dirty).toHaveTextContent("false");
|
|
17
|
+
expect(r.firstName_touched).toHaveTextContent("false");
|
|
18
18
|
});
|
|
19
19
|
it("originalValue is reactive", async () => {
|
|
20
|
-
const r = await rtl_utils_1.render(jsx_runtime_1.jsx(FormStateApp_1.FormStateApp, {}
|
|
21
|
-
expect(r.firstName_original
|
|
22
|
-
rtl_utils_1.click(r.set);
|
|
23
|
-
rtl_utils_1.click(r.commitChanges);
|
|
24
|
-
expect(r.firstName_original
|
|
20
|
+
const r = await (0, rtl_utils_1.render)((0, jsx_runtime_1.jsx)(FormStateApp_1.FormStateApp, {}));
|
|
21
|
+
expect(r.firstName_original).toHaveTextContent("a1");
|
|
22
|
+
(0, rtl_utils_1.click)(r.set);
|
|
23
|
+
(0, rtl_utils_1.click)(r.commitChanges);
|
|
24
|
+
expect(r.firstName_original).toHaveTextContent("a2");
|
|
25
25
|
});
|
|
26
26
|
});
|
package/dist/config.d.ts
CHANGED
|
@@ -7,18 +7,18 @@ import { Builtin } from "./utils";
|
|
|
7
7
|
* Basically every field is either a value/primitive or a list, and this `ObjectConfig` lets
|
|
8
8
|
* the caller define field-specific behavior, i.e. validation rules.
|
|
9
9
|
*/
|
|
10
|
-
export
|
|
10
|
+
export type ObjectConfig<T> = {
|
|
11
11
|
[P in keyof OmitIf<T, Function>]: T[P] extends Fragment<infer V> ? FragmentFieldConfig : T[P] extends Array<infer U> | null | undefined ? U extends Builtin ? ValueFieldConfig<T, T[P]> : ListFieldConfig<T, U> : ValueFieldConfig<T, T[P]> | ObjectFieldConfig<T[P]>;
|
|
12
12
|
};
|
|
13
|
-
|
|
13
|
+
type OmitIf<Base, Condition> = Pick<Base, {
|
|
14
14
|
[Key in keyof Base]: Base[Key] extends Condition ? never : Key;
|
|
15
15
|
}[keyof Base]>;
|
|
16
16
|
/** Field configuration for an opaque value that we don't actually want to include. */
|
|
17
|
-
export
|
|
17
|
+
export type FragmentFieldConfig = {
|
|
18
18
|
type: "fragment";
|
|
19
19
|
};
|
|
20
20
|
/** Field configuration for primitive values, i.e. strings/numbers/Dates/user-defined types. */
|
|
21
|
-
export
|
|
21
|
+
export type ValueFieldConfig<T, V> = {
|
|
22
22
|
type: "value";
|
|
23
23
|
rules?: Rule<V | null | undefined>[];
|
|
24
24
|
/**
|
|
@@ -45,7 +45,7 @@ export declare type ValueFieldConfig<T, V> = {
|
|
|
45
45
|
strictOrder?: false;
|
|
46
46
|
};
|
|
47
47
|
/** Field configuration for list values, i.e. `U` is `Book` in a form with `books: Book[]`. */
|
|
48
|
-
export
|
|
48
|
+
export type ListFieldConfig<T, U> = {
|
|
49
49
|
type: "list";
|
|
50
50
|
/** Rules that can run on the full list of children. */
|
|
51
51
|
rules?: Rule<readonly ObjectState<U>[]>[];
|
|
@@ -66,7 +66,7 @@ export declare type ListFieldConfig<T, U> = {
|
|
|
66
66
|
/** Set to not consider the order of the list when evaluating changed/dirty states. */
|
|
67
67
|
strictOrder?: false;
|
|
68
68
|
};
|
|
69
|
-
export
|
|
69
|
+
export type ObjectFieldConfig<U> = {
|
|
70
70
|
type: "object";
|
|
71
71
|
/** Config for the child's form state, i.e. each book. */
|
|
72
72
|
config: ObjectConfig<U>;
|
|
@@ -6,14 +6,14 @@ const utils_1 = require("../utils");
|
|
|
6
6
|
function newFragmentField(parentInstance, key) {
|
|
7
7
|
// We always return the same `instance` field from our `value` method, but
|
|
8
8
|
// we want to pretend that it's observable, so use a tick to force it.
|
|
9
|
-
const _tick = mobx_1.observable({ value: 1 });
|
|
9
|
+
const _tick = (0, mobx_1.observable)({ value: 1 });
|
|
10
10
|
// We steal the fragment from our parent, so that it doesn't
|
|
11
11
|
// accidentally end up on the wire
|
|
12
12
|
let value = parentInstance[key];
|
|
13
13
|
delete parentInstance[key];
|
|
14
14
|
const obj = {
|
|
15
15
|
get value() {
|
|
16
|
-
_tick.value > 0 || utils_1.fail();
|
|
16
|
+
_tick.value > 0 || (0, utils_1.fail)();
|
|
17
17
|
return value;
|
|
18
18
|
},
|
|
19
19
|
set value(v) {
|
|
@@ -21,6 +21,6 @@ function newFragmentField(parentInstance, key) {
|
|
|
21
21
|
_tick.value++;
|
|
22
22
|
},
|
|
23
23
|
};
|
|
24
|
-
return mobx_1.makeAutoObservable(obj, { value: false });
|
|
24
|
+
return (0, mobx_1.makeAutoObservable)(obj, { value: false });
|
|
25
25
|
}
|
|
26
26
|
exports.newFragmentField = newFragmentField;
|
package/dist/fields/listField.js
CHANGED
|
@@ -8,15 +8,15 @@ const utils_1 = require("../utils");
|
|
|
8
8
|
function newListFieldState(parentInstance, parentState, key, rules, listConfig, config, strictOrder, maybeAutoSave) {
|
|
9
9
|
// Keep a map of "item in the parent list" -> "that item's ObjectState"
|
|
10
10
|
const rowMap = new Map();
|
|
11
|
-
const _tick = mobx_1.observable({ value: 1 });
|
|
12
|
-
const _childTick = mobx_1.observable({ value: 1 });
|
|
11
|
+
const _tick = (0, mobx_1.observable)({ value: 1 });
|
|
12
|
+
const _childTick = (0, mobx_1.observable)({ value: 1 });
|
|
13
13
|
// this is for dirty checking, not object identity
|
|
14
14
|
let originalCopy = [...(parentInstance[key] || [])];
|
|
15
15
|
const list = {
|
|
16
16
|
key: key,
|
|
17
17
|
// Our fundamental state of wrapped Us
|
|
18
18
|
get value() {
|
|
19
|
-
return _tick.value > 0 && _childTick.value > 0 ? parentInstance[key] : utils_1.fail();
|
|
19
|
+
return _tick.value > 0 && _childTick.value > 0 ? parentInstance[key] : (0, utils_1.fail)();
|
|
20
20
|
},
|
|
21
21
|
_focused: false,
|
|
22
22
|
_readOnly: false,
|
|
@@ -68,14 +68,14 @@ function newListFieldState(parentInstance, parentState, key, rules, listConfig,
|
|
|
68
68
|
// It's unclear why we need to access _tick.value here, b/c calling `this.value` should
|
|
69
69
|
// transitively register us as a dependency on it
|
|
70
70
|
if (_tick.value < 0)
|
|
71
|
-
utils_1.fail();
|
|
71
|
+
(0, utils_1.fail)();
|
|
72
72
|
// Avoid using `this.value` to avoid registering `_childTick` as a dependency
|
|
73
73
|
const value = parentInstance[key];
|
|
74
74
|
return (value || []).map((child) => {
|
|
75
75
|
// Because we're reading from this.value, child will be the proxy version
|
|
76
76
|
let childState = rowMap.get(child);
|
|
77
77
|
if (!childState) {
|
|
78
|
-
childState = objectField_1.newObjectState(config, parentState, list, child, undefined, maybeAutoSave);
|
|
78
|
+
childState = (0, objectField_1.newObjectState)(config, parentState, list, child, undefined, maybeAutoSave);
|
|
79
79
|
rowMap.set(child, childState);
|
|
80
80
|
}
|
|
81
81
|
return childState;
|
|
@@ -99,7 +99,7 @@ function newListFieldState(parentInstance, parentState, key, rules, listConfig,
|
|
|
99
99
|
},
|
|
100
100
|
get errors() {
|
|
101
101
|
if (_tick.value < 0)
|
|
102
|
-
utils_1.fail();
|
|
102
|
+
(0, utils_1.fail)();
|
|
103
103
|
const opts = { value: this.rows, key: key, originalValue: originalCopy || [], object: parentState() };
|
|
104
104
|
return this.rules.map((r) => r(opts)).filter(utils_1.isNotUndefined);
|
|
105
105
|
},
|
|
@@ -138,7 +138,7 @@ function newListFieldState(parentInstance, parentState, key, rules, listConfig,
|
|
|
138
138
|
},
|
|
139
139
|
set(values, opts = {}) {
|
|
140
140
|
if (this.readOnly && !opts.resetting && !opts.refreshing) {
|
|
141
|
-
throw new Error(`${key} is currently readOnly`);
|
|
141
|
+
throw new Error(`${String(key)} is currently readOnly`);
|
|
142
142
|
}
|
|
143
143
|
// We should be passed values that are non-proxies.
|
|
144
144
|
parentInstance[key] = values.map((value) => {
|
|
@@ -153,7 +153,7 @@ function newListFieldState(parentInstance, parentState, key, rules, listConfig,
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
// If we didn't have an existing child, just make a new object state
|
|
156
|
-
childState = objectField_1.createObjectState(config, value, { maybeAutoSave });
|
|
156
|
+
childState = (0, objectField_1.createObjectState)(config, value, { maybeAutoSave });
|
|
157
157
|
rowMap.set(value, childState);
|
|
158
158
|
}
|
|
159
159
|
// Return the already-observable'd value so that our `parent.value[key] = values` doesn't re-proxy things
|
|
@@ -167,7 +167,7 @@ function newListFieldState(parentInstance, parentState, key, rules, listConfig,
|
|
|
167
167
|
},
|
|
168
168
|
add(value, spliceIndex) {
|
|
169
169
|
// This is called by the user, so value should be a non-proxy value we should keep
|
|
170
|
-
const childState = objectField_1.createObjectState(config, value, { maybeAutoSave });
|
|
170
|
+
const childState = (0, objectField_1.createObjectState)(config, value, { maybeAutoSave });
|
|
171
171
|
rowMap.set(value, childState);
|
|
172
172
|
this.ensureSet();
|
|
173
173
|
this.value.splice(typeof spliceIndex === "number" ? spliceIndex : this.value.length, 0, childState.value);
|
|
@@ -206,13 +206,13 @@ function newListFieldState(parentInstance, parentState, key, rules, listConfig,
|
|
|
206
206
|
_tick.value++;
|
|
207
207
|
},
|
|
208
208
|
};
|
|
209
|
-
const proxy = mobx_1.makeAutoObservable(list, {
|
|
209
|
+
const proxy = (0, mobx_1.makeAutoObservable)(list, {
|
|
210
210
|
// See other makeAutoObservable comment
|
|
211
|
-
value: mobx_1.computed({ equals: () => false }),
|
|
211
|
+
value: (0, mobx_1.computed)({ equals: () => false }),
|
|
212
212
|
});
|
|
213
213
|
// Any time a row's value changes, percolate that to our `.value` (so the callers to our
|
|
214
214
|
// `.value` will rerun given the value they saw has deeply changed.)
|
|
215
|
-
mobx_1.reaction(() => proxy.rows.map((r) => r.value), () => _childTick.value++);
|
|
215
|
+
(0, mobx_1.reaction)(() => proxy.rows.map((r) => r.value), () => _childTick.value++);
|
|
216
216
|
return proxy;
|
|
217
217
|
}
|
|
218
218
|
exports.newListFieldState = newListFieldState;
|
|
@@ -28,22 +28,22 @@ import { Builtin } from "../utils";
|
|
|
28
28
|
* - title: FieldState
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
|
-
export
|
|
31
|
+
export type ObjectState<T> = FieldStates<T> & FieldState<T> & {
|
|
32
32
|
/** Sets the state of fields in `state`. */
|
|
33
33
|
set(state: Partial<T>, opts?: SetOpts): void;
|
|
34
34
|
/** Returns whether the object can be saved, i.e. is valid, but also as a side-effect marks touched. */
|
|
35
35
|
canSave(): boolean;
|
|
36
36
|
};
|
|
37
|
-
export
|
|
37
|
+
export type ObjectStateInternal<T> = ObjectState<T> & {
|
|
38
38
|
set(value: T, opts?: InternalSetOpts): void;
|
|
39
39
|
};
|
|
40
40
|
declare const fragmentSym: unique symbol;
|
|
41
|
-
export
|
|
41
|
+
export type Fragment<V> = V & {
|
|
42
42
|
[fragmentSym]: true;
|
|
43
43
|
};
|
|
44
44
|
export declare function fragment<V>(value: V): Fragment<V>;
|
|
45
45
|
/** For a given input type `T`, decorate each field into the "field state" type that holds our form-relevant state, i.e. valid/touched/etc. */
|
|
46
|
-
|
|
46
|
+
type FieldStates<T> = {
|
|
47
47
|
[K in keyof T]-?: T[K] extends Fragment<infer V> ? FragmentField<V> : T[K] extends Array<infer U> | null | undefined ? [U] extends [Builtin] ? FieldState<T[K]> : ListFieldState<U> : T[K] extends Builtin | null | undefined ? FieldState<T[K]> : ObjectState<T[K]>;
|
|
48
48
|
};
|
|
49
49
|
/**
|
|
@@ -41,13 +41,13 @@ function newObjectState(config, parentState, parentListState, instance, key, may
|
|
|
41
41
|
const config = _config;
|
|
42
42
|
let field;
|
|
43
43
|
if (config.type === "value") {
|
|
44
|
-
field = valueField_1.newValueFieldState(instance, getObjectState, key, config.rules || [], config.isIdKey ||
|
|
44
|
+
field = (0, valueField_1.newValueFieldState)(instance, getObjectState, key, config.rules || [], config.isIdKey ||
|
|
45
45
|
// Default the id key to "id" unless some other field has isIdKey set
|
|
46
46
|
(key === "id" &&
|
|
47
47
|
!Object.entries(objectConfig).some(([other, c]) => other !== key && c.isIdKey)), config.isDeleteKey || false, config.isReadOnlyKey || false, config.computed || false, config.readOnly || false, (_a = config.strictOrder) !== null && _a !== void 0 ? _a : true, maybeAutoSave);
|
|
48
48
|
}
|
|
49
49
|
else if (config.type === "list") {
|
|
50
|
-
field = listField_1.newListFieldState(instance, getObjectState, key, config.rules || [], config, config.config, (_b = config.strictOrder) !== null && _b !== void 0 ? _b : true, maybeAutoSave);
|
|
50
|
+
field = (0, listField_1.newListFieldState)(instance, getObjectState, key, config.rules || [], config, config.config, (_b = config.strictOrder) !== null && _b !== void 0 ? _b : true, maybeAutoSave);
|
|
51
51
|
}
|
|
52
52
|
else if (config.type === "object") {
|
|
53
53
|
if (!instance[key]) {
|
|
@@ -56,7 +56,7 @@ function newObjectState(config, parentState, parentListState, instance, key, may
|
|
|
56
56
|
field = newObjectState(config.config, getObjectState, undefined, instance[key], key, maybeAutoSave);
|
|
57
57
|
}
|
|
58
58
|
else if (config.type === "fragment") {
|
|
59
|
-
field = fragmentField_1.newFragmentField(instance, key);
|
|
59
|
+
field = (0, fragmentField_1.newFragmentField)(instance, key);
|
|
60
60
|
}
|
|
61
61
|
else {
|
|
62
62
|
throw new Error("Unsupported");
|
|
@@ -65,7 +65,7 @@ function newObjectState(config, parentState, parentListState, instance, key, may
|
|
|
65
65
|
});
|
|
66
66
|
// We always return the same `instance` field from our `value` method, but
|
|
67
67
|
// we want to pretend that it's observable, so use a tick to force it.
|
|
68
|
-
const _tick = mobx_1.observable({ value: 1 });
|
|
68
|
+
const _tick = (0, mobx_1.observable)({ value: 1 });
|
|
69
69
|
const fieldNames = Object.keys(config);
|
|
70
70
|
function getFields(proxyThis) {
|
|
71
71
|
return fieldNames.map((name) => proxyThis[name]);
|
|
@@ -74,7 +74,7 @@ function newObjectState(config, parentState, parentListState, instance, key, may
|
|
|
74
74
|
...Object.fromEntries(fieldStates),
|
|
75
75
|
key,
|
|
76
76
|
get value() {
|
|
77
|
-
_tick.value > 0 || utils_1.fail();
|
|
77
|
+
_tick.value > 0 || (0, utils_1.fail)();
|
|
78
78
|
return instance;
|
|
79
79
|
},
|
|
80
80
|
set value(value) {
|
|
@@ -137,10 +137,10 @@ function newObjectState(config, parentState, parentListState, instance, key, may
|
|
|
137
137
|
// Accepts new values in bulk, i.e. when setting the form initial state from the backend.
|
|
138
138
|
set(value, opts = {}) {
|
|
139
139
|
if (this.readOnly && !opts.resetting && !opts.refreshing) {
|
|
140
|
-
throw new Error(`${key || "formState"} is currently readOnly`);
|
|
140
|
+
throw new Error(`${String(key) || "formState"} is currently readOnly`);
|
|
141
141
|
}
|
|
142
142
|
getFields(this).forEach((field) => {
|
|
143
|
-
if (field.key in value) {
|
|
143
|
+
if (value && typeof value === "object" && field.key in value) {
|
|
144
144
|
field.set(value[field.key], opts);
|
|
145
145
|
}
|
|
146
146
|
});
|
|
@@ -187,15 +187,15 @@ function newObjectState(config, parentState, parentListState, instance, key, may
|
|
|
187
187
|
return this[idField.key].value === other[idField.key];
|
|
188
188
|
},
|
|
189
189
|
};
|
|
190
|
-
proxy = mobx_1.makeAutoObservable(obj, {
|
|
190
|
+
proxy = (0, mobx_1.makeAutoObservable)(obj, {
|
|
191
191
|
// Use custom equality on `value` that is _never_ equals. This sounds weird, but
|
|
192
192
|
// because our `value` is always the same `instance` that was passed to `newObjectState`,
|
|
193
193
|
// to mobx this looks like the value never changes, and it will never invoke observers
|
|
194
194
|
// even with our tick-based hacks.
|
|
195
|
-
value: mobx_1.computed({ equals: () => false }),
|
|
195
|
+
value: (0, mobx_1.computed)({ equals: () => false }),
|
|
196
196
|
});
|
|
197
197
|
// Any time a field changes, percolate that change up to us
|
|
198
|
-
mobx_1.reaction(() => getFields(proxy).map((f) => f.value), () => _tick.value++);
|
|
198
|
+
(0, mobx_1.reaction)(() => getFields(proxy).map((f) => f.value), () => _tick.value++);
|
|
199
199
|
return proxy;
|
|
200
200
|
}
|
|
201
201
|
exports.newObjectState = newObjectState;
|
|
@@ -36,6 +36,19 @@ export interface FieldState<V> {
|
|
|
36
36
|
revertChanges(): void;
|
|
37
37
|
/** Accepts the current changed value (if any) as the original and resets dirty/touched. */
|
|
38
38
|
commitChanges(): void;
|
|
39
|
+
/** Creates a new FieldState with a transformation of the value, i.e. string to int, or feet to inches. */
|
|
40
|
+
adapt<V2>(adapter: ValueAdapter<V, V2>): FieldState<V2>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Allows changing a type in the formState (like a string) to a different type in the UI (like a number).
|
|
44
|
+
*
|
|
45
|
+
* Or doing unit of measure conversions within the same type, like from meters to feet.
|
|
46
|
+
*/
|
|
47
|
+
export interface ValueAdapter<V, V2 = V> {
|
|
48
|
+
/** Converts the original FieldState's value `V` into new `V2` type. */
|
|
49
|
+
toValue(value: V): V2;
|
|
50
|
+
/** Converts the adapted FieldState's value `V2` back into the original `V` type. */
|
|
51
|
+
fromValue(value: V2): V;
|
|
39
52
|
}
|
|
40
53
|
/** Public options for our `set` command. */
|
|
41
54
|
export interface SetOpts {
|
|
@@ -3,18 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.newValueFieldState = void 0;
|
|
4
4
|
const is_plain_object_1 = require("is-plain-object");
|
|
5
5
|
const mobx_1 = require("mobx");
|
|
6
|
+
const proxies_1 = require("../proxies");
|
|
6
7
|
const rules_1 = require("../rules");
|
|
7
8
|
const utils_1 = require("../utils");
|
|
8
9
|
function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, isDeleteKey, isReadOnlyKey, computed, readOnly, strictOrder, maybeAutoSave) {
|
|
9
10
|
// keep a copy here for reference equality
|
|
10
11
|
const value = parentInstance[key];
|
|
11
|
-
let _originalValue = value === null ? undefined : is_plain_object_1.isPlainObject(value) ? mobx_1.toJS(value) : value;
|
|
12
|
+
let _originalValue = value === null ? undefined : (0, is_plain_object_1.isPlainObject)(value) ? (0, mobx_1.toJS)(value) : value;
|
|
12
13
|
// Because we read/write the value directly back into parentInstance[key],
|
|
13
14
|
// which itself is not a proxy, we use this as our "value changed" trigger.
|
|
14
|
-
const _tick = mobx_1.observable({ value: 1 });
|
|
15
|
-
const _originalValueTick = mobx_1.observable({ value: 1 });
|
|
15
|
+
const _tick = (0, mobx_1.observable)({ value: 1 });
|
|
16
|
+
const _originalValueTick = (0, mobx_1.observable)({ value: 1 });
|
|
16
17
|
const field = {
|
|
17
|
-
key,
|
|
18
|
+
key: key,
|
|
18
19
|
touched: false,
|
|
19
20
|
/** Current readOnly value. */
|
|
20
21
|
_readOnly: readOnly || false,
|
|
@@ -25,17 +26,18 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
|
|
|
25
26
|
_isReadOnlyKey: isReadOnlyKey,
|
|
26
27
|
rules,
|
|
27
28
|
get value() {
|
|
28
|
-
|
|
29
|
+
// If we're wrapping a mobx store, then we'll get reactivity from parentInstance[key]
|
|
30
|
+
const value = _tick.value > 0 ? parentInstance[key] : (0, utils_1.fail)();
|
|
29
31
|
// Re-create the `keepNull` logic on sets but for our initial read where our
|
|
30
32
|
// originalValue is null (empty) but we want to expose it as undefined for
|
|
31
33
|
// consistency of "empty-ness" to our UI components.
|
|
32
|
-
return value === null && utils_1.isEmpty(_originalValue) ? undefined : value;
|
|
34
|
+
return value === null && (0, utils_1.isEmpty)(_originalValue) ? undefined : value;
|
|
33
35
|
},
|
|
34
36
|
set value(v) {
|
|
35
37
|
this.set(v);
|
|
36
38
|
},
|
|
37
39
|
get dirty() {
|
|
38
|
-
return !utils_1.areEqual(this.originalValue, this.value, strictOrder);
|
|
40
|
+
return !(0, utils_1.areEqual)(this.originalValue, this.value, strictOrder);
|
|
39
41
|
},
|
|
40
42
|
/** Returns whether this field is readOnly, although if our parent is readOnly then it trumps. */
|
|
41
43
|
get readOnly() {
|
|
@@ -87,7 +89,7 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
|
|
|
87
89
|
},
|
|
88
90
|
set(value, opts = {}) {
|
|
89
91
|
if (this.readOnly && !opts.resetting && !opts.refreshing) {
|
|
90
|
-
throw new Error(`${key} is currently readOnly`);
|
|
92
|
+
throw new Error(`${String(key)} is currently readOnly`);
|
|
91
93
|
}
|
|
92
94
|
if (opts.refreshing && this.dirty && this.value !== value) {
|
|
93
95
|
// Ignore incoming values if we have changes (this.dirty) unless our latest change (this.value)
|
|
@@ -101,12 +103,12 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
|
|
|
101
103
|
}
|
|
102
104
|
// If the user has deleted/emptied a value that was originally set, keep it as `null`
|
|
103
105
|
// so that our partial update to the backend correctly unsets it.
|
|
104
|
-
const keepNull = !utils_1.isEmpty(this.originalValue) && utils_1.isEmpty(value) && !opts.refreshing;
|
|
106
|
+
const keepNull = !(0, utils_1.isEmpty)(this.originalValue) && (0, utils_1.isEmpty)(value) && !opts.refreshing;
|
|
105
107
|
// If a list of primitives was originally undefined, coerce `[]` to `undefined`
|
|
106
|
-
const coerceEmptyList = value && value instanceof Array && value.length === 0 && utils_1.isEmpty(this.originalValue);
|
|
107
|
-
const newValue = keepNull ? null : utils_1.isEmpty(value) || coerceEmptyList ? undefined : value;
|
|
108
|
+
const coerceEmptyList = value && value instanceof Array && value.length === 0 && (0, utils_1.isEmpty)(this.originalValue);
|
|
109
|
+
const newValue = keepNull ? null : (0, utils_1.isEmpty)(value) || coerceEmptyList ? undefined : value;
|
|
108
110
|
// Set the value on our parent object
|
|
109
|
-
const changed = !utils_1.areEqual(newValue, this.value, strictOrder);
|
|
111
|
+
const changed = !(0, utils_1.areEqual)(newValue, this.value, strictOrder);
|
|
110
112
|
parentInstance[key] = newValue;
|
|
111
113
|
_tick.value++;
|
|
112
114
|
if (opts.refreshing) {
|
|
@@ -118,6 +120,9 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
|
|
|
118
120
|
this.maybeAutoSave();
|
|
119
121
|
}
|
|
120
122
|
},
|
|
123
|
+
adapt(adapter) {
|
|
124
|
+
return adapt(this, adapter);
|
|
125
|
+
},
|
|
121
126
|
revertChanges() {
|
|
122
127
|
if (!computed) {
|
|
123
128
|
this.set(this.originalValue, { resetting: true });
|
|
@@ -125,8 +130,8 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
|
|
|
125
130
|
this.touched = false;
|
|
126
131
|
},
|
|
127
132
|
commitChanges() {
|
|
128
|
-
if (is_plain_object_1.isPlainObject(this.originalValue)) {
|
|
129
|
-
this.originalValue = mobx_1.toJS(this.value);
|
|
133
|
+
if ((0, is_plain_object_1.isPlainObject)(this.originalValue)) {
|
|
134
|
+
this.originalValue = (0, mobx_1.toJS)(this.value);
|
|
130
135
|
}
|
|
131
136
|
else {
|
|
132
137
|
this.originalValue = this.value;
|
|
@@ -158,3 +163,33 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
|
|
|
158
163
|
return field;
|
|
159
164
|
}
|
|
160
165
|
exports.newValueFieldState = newValueFieldState;
|
|
166
|
+
/**
|
|
167
|
+
* Returns a proxy that looks exactly like the original `field`, in terms of valid/touched/errors/etc., but
|
|
168
|
+
* has any methods that use `V` overridden to use be `V2`.
|
|
169
|
+
*
|
|
170
|
+
* Note that `V2` can be a new type, like string -> number, or just a transformation on the same
|
|
171
|
+
* type, i.e. feet -> inches where both are `number`s.
|
|
172
|
+
*/
|
|
173
|
+
function adapt(field, adapter) {
|
|
174
|
+
return (0, proxies_1.newDelegateProxy)(field, {
|
|
175
|
+
rules: [],
|
|
176
|
+
get value() {
|
|
177
|
+
return adapter.toValue(field.value);
|
|
178
|
+
},
|
|
179
|
+
set value(v) {
|
|
180
|
+
field.value = adapter.fromValue(v);
|
|
181
|
+
},
|
|
182
|
+
set: (v) => {
|
|
183
|
+
field.value = adapter.fromValue(v);
|
|
184
|
+
},
|
|
185
|
+
get changedValue() {
|
|
186
|
+
return this.value;
|
|
187
|
+
},
|
|
188
|
+
adapt(adapter) {
|
|
189
|
+
return adapt(this, adapter);
|
|
190
|
+
},
|
|
191
|
+
get originalValue() {
|
|
192
|
+
return adapter.toValue(field.originalValue);
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
import { Meta } from "@storybook/react";
|
|
3
|
-
declare const _default: Meta<import("@storybook/react").Args>;
|
|
1
|
+
declare const _default: import("@storybook/types").ComponentAnnotations<import("@storybook/react/dist/types-0a347bb9").R, import("@storybook/types").Args>;
|
|
4
2
|
export default _default;
|
|
5
|
-
export declare function AppExample(): JSX.Element;
|
|
3
|
+
export declare function AppExample(): import("react/jsx-runtime").JSX.Element;
|