@homebound/form-state 2.19.0 → 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.
@@ -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;
@@ -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", Object.assign({ className: "App" }, { children: jsx_runtime_1.jsxs("header", Object.assign({ className: "App-header" }, { children: [jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsx("b", { children: "Author" }, void 0),
30
- jsx_runtime_1.jsx(TextField, { field: formState.firstName }, void 0),
31
- jsx_runtime_1.jsx(TextField, { field: formState.lastName }, void 0)] }, void 0),
32
- jsx_runtime_1.jsxs("div", { children: [jsx_runtime_1.jsxs("strong", { children: ["Books ", jsx_runtime_1.jsx("button", Object.assign({ onClick: () => formState.books.add({}) }, { children: "Add book" }), void 0)] }, void 0),
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, ":"] }, void 0),
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
- } }, void 0) }, void 0),
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, {}, void 0));
10
- expect(r.firstName_dirty()).toHaveTextContent("false");
11
- 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
- rtl_utils_1.click(r.commitChanges);
16
- expect(r.firstName_dirty()).toHaveTextContent("false");
17
- expect(r.firstName_touched()).toHaveTextContent("false");
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, {}, void 0));
21
- expect(r.firstName_original()).toHaveTextContent("a1");
22
- rtl_utils_1.click(r.set);
23
- rtl_utils_1.click(r.commitChanges);
24
- expect(r.firstName_original()).toHaveTextContent("a2");
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 declare type ObjectConfig<T> = {
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
- declare type OmitIf<Base, Condition> = Pick<Base, {
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 declare type FragmentFieldConfig = {
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 declare type ValueFieldConfig<T, V> = {
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 declare type ListFieldConfig<T, U> = {
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 declare type ObjectFieldConfig<U> = {
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;
@@ -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 declare type ObjectState<T> = FieldStates<T> & FieldState<T> & {
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 declare type ObjectStateInternal<T> = ObjectState<T> & {
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 declare type Fragment<V> = V & {
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
- declare type FieldStates<T> = {
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,
@@ -26,17 +27,17 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
26
27
  rules,
27
28
  get value() {
28
29
  // If we're wrapping a mobx store, then we'll get reactivity from parentInstance[key]
29
- const value = _tick.value > 0 ? parentInstance[key] : utils_1.fail();
30
+ const value = _tick.value > 0 ? parentInstance[key] : (0, utils_1.fail)();
30
31
  // Re-create the `keepNull` logic on sets but for our initial read where our
31
32
  // originalValue is null (empty) but we want to expose it as undefined for
32
33
  // consistency of "empty-ness" to our UI components.
33
- return value === null && utils_1.isEmpty(_originalValue) ? undefined : value;
34
+ return value === null && (0, utils_1.isEmpty)(_originalValue) ? undefined : value;
34
35
  },
35
36
  set value(v) {
36
37
  this.set(v);
37
38
  },
38
39
  get dirty() {
39
- return !utils_1.areEqual(this.originalValue, this.value, strictOrder);
40
+ return !(0, utils_1.areEqual)(this.originalValue, this.value, strictOrder);
40
41
  },
41
42
  /** Returns whether this field is readOnly, although if our parent is readOnly then it trumps. */
42
43
  get readOnly() {
@@ -88,7 +89,7 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
88
89
  },
89
90
  set(value, opts = {}) {
90
91
  if (this.readOnly && !opts.resetting && !opts.refreshing) {
91
- throw new Error(`${key} is currently readOnly`);
92
+ throw new Error(`${String(key)} is currently readOnly`);
92
93
  }
93
94
  if (opts.refreshing && this.dirty && this.value !== value) {
94
95
  // Ignore incoming values if we have changes (this.dirty) unless our latest change (this.value)
@@ -102,12 +103,12 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
102
103
  }
103
104
  // If the user has deleted/emptied a value that was originally set, keep it as `null`
104
105
  // so that our partial update to the backend correctly unsets it.
105
- 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;
106
107
  // If a list of primitives was originally undefined, coerce `[]` to `undefined`
107
- const coerceEmptyList = value && value instanceof Array && value.length === 0 && utils_1.isEmpty(this.originalValue);
108
- 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;
109
110
  // Set the value on our parent object
110
- const changed = !utils_1.areEqual(newValue, this.value, strictOrder);
111
+ const changed = !(0, utils_1.areEqual)(newValue, this.value, strictOrder);
111
112
  parentInstance[key] = newValue;
112
113
  _tick.value++;
113
114
  if (opts.refreshing) {
@@ -119,6 +120,9 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
119
120
  this.maybeAutoSave();
120
121
  }
121
122
  },
123
+ adapt(adapter) {
124
+ return adapt(this, adapter);
125
+ },
122
126
  revertChanges() {
123
127
  if (!computed) {
124
128
  this.set(this.originalValue, { resetting: true });
@@ -126,8 +130,8 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
126
130
  this.touched = false;
127
131
  },
128
132
  commitChanges() {
129
- if (is_plain_object_1.isPlainObject(this.originalValue)) {
130
- 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);
131
135
  }
132
136
  else {
133
137
  this.originalValue = this.value;
@@ -159,3 +163,33 @@ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, is
159
163
  return field;
160
164
  }
161
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
- /// <reference types="react" />
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;
@@ -8,6 +8,6 @@ exports.default = {
8
8
  title: "Components/Form State",
9
9
  };
10
10
  function AppExample() {
11
- return jsx_runtime_1.jsx(FormStateApp_1.FormStateApp, {}, void 0);
11
+ return (0, jsx_runtime_1.jsx)(FormStateApp_1.FormStateApp, {});
12
12
  }
13
13
  exports.AppExample = AppExample;