@homebound/form-state 2.9.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.
@@ -7,7 +7,7 @@ const mobx_1 = require("mobx");
7
7
  const mobx_react_1 = require("mobx-react");
8
8
  const react_2 = require("react");
9
9
  const FormStateApp_1 = require("./FormStateApp");
10
- const formState_1 = require("./formState");
10
+ const rules_1 = require("./rules");
11
11
  const useFormState_1 = require("./useFormState");
12
12
  describe("useFormState", () => {
13
13
  it("calls init.map if init.input is defined", async () => {
@@ -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 = Math.random() >= 0 ? { firstName: "bob" } : undefined;
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" },
@@ -468,7 +483,7 @@ const authorWithBooksConfig = {
468
483
  type: "list",
469
484
  config: {
470
485
  id: { type: "value" },
471
- title: { type: "value", rules: [formState_1.required] },
486
+ title: { type: "value", rules: [rules_1.required] },
472
487
  classification: { type: "value" },
473
488
  },
474
489
  },
@@ -481,7 +496,7 @@ const authorWithAddressAndBooksConfig = {
481
496
  type: "object",
482
497
  config: {
483
498
  id: { type: "value" },
484
- street: { type: "value", rules: [formState_1.required] },
499
+ street: { type: "value", rules: [rules_1.required] },
485
500
  city: { type: "value" },
486
501
  },
487
502
  },
@@ -489,7 +504,7 @@ const authorWithAddressAndBooksConfig = {
489
504
  type: "list",
490
505
  config: {
491
506
  id: { type: "value" },
492
- title: { type: "value", rules: [formState_1.required] },
507
+ title: { type: "value", rules: [rules_1.required] },
493
508
  classification: { type: "value" },
494
509
  },
495
510
  },
@@ -1,4 +1,5 @@
1
- import { ObjectConfig, ObjectState } from "./formState";
1
+ import { ObjectConfig } from "./config";
2
+ import { ObjectState } from "./fields/objectField";
2
3
  export declare type ObjectStateCache<T, I> = Record<string, [ObjectState<T>, I]>;
3
4
  declare type UseFormStatesOpts<T, I> = {
4
5
  /**
@@ -34,8 +35,15 @@ declare type UseFormStatesOpts<T, I> = {
34
35
  * Does not need to be stable/useMemo'd.
35
36
  */
36
37
  map?: (input: Exclude<I, null | undefined>) => T;
38
+ /**
39
+ * Sets all `ObjectState`s to readOnly.
40
+ */
41
+ readOnly?: boolean;
37
42
  };
38
- export declare function useFormStates<T, I = T>(opts: UseFormStatesOpts<T, I>): {
39
- getFormState: (input: I) => ObjectState<T>;
43
+ declare type UseFormStatesHook<T, I> = {
44
+ getFormState: (input: I, opts?: {
45
+ readOnly?: boolean;
46
+ }) => ObjectState<T>;
40
47
  };
48
+ export declare function useFormStates<T, I = T>(opts: UseFormStatesOpts<T, I>): UseFormStatesHook<T, I>;
41
49
  export {};
@@ -2,10 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useFormStates = void 0;
4
4
  const react_1 = require("react");
5
- const formState_1 = require("./formState");
5
+ const objectField_1 = require("./fields/objectField");
6
6
  const utils_1 = require("./utils");
7
7
  function useFormStates(opts) {
8
- const { config, autoSave, getId, map, addRules } = opts;
8
+ const { config, autoSave, getId, map, addRules, readOnly = false } = opts;
9
9
  const objectStateCache = react_1.useMemo(() => ({}),
10
10
  // Force resetting the cache if config changes
11
11
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -15,7 +15,10 @@ function useFormStates(opts) {
15
15
  // Use a ref so our memo'ized `autoSave` always see the latest value
16
16
  const autoSaveRef = react_1.useRef(autoSave);
17
17
  autoSaveRef.current = autoSave;
18
- const getFormState = react_1.useCallback((input) => {
18
+ // Use a ref b/c we're memod
19
+ const readOnlyRef = react_1.useRef(readOnly);
20
+ readOnlyRef.current = readOnly;
21
+ const getFormState = react_1.useCallback((input, opts = {}) => {
19
22
  const existing = objectStateCache[getId(input)];
20
23
  let form = existing === null || existing === void 0 ? void 0 : existing[0];
21
24
  async function maybeAutoSave(form) {
@@ -46,7 +49,7 @@ function useFormStates(opts) {
46
49
  }
47
50
  // If it didn't exist, then add to the cache.
48
51
  if (!form) {
49
- form = formState_1.createObjectState(config, utils_1.initValue(config, map ? { map, input } : input), {
52
+ form = objectField_1.createObjectState(config, utils_1.initValue(config, map ? { map, input } : input), {
50
53
  maybeAutoSave: () => maybeAutoSave(form),
51
54
  });
52
55
  if (addRules) {
@@ -59,6 +62,7 @@ function useFormStates(opts) {
59
62
  form.set(utils_1.initValue(config, map ? { map, input } : input), { refreshing: true });
60
63
  existing[1] = input;
61
64
  }
65
+ form.readOnly = readOnlyRef.current || !!opts.readOnly;
62
66
  return form;
63
67
  },
64
68
  // Allow the user to not stable-ize getId, map, addRules, and autoSave
@@ -4,8 +4,8 @@ const jsx_runtime_1 = require("react/jsx-runtime");
4
4
  const rtl_utils_1 = require("@homebound/rtl-utils");
5
5
  const mobx_1 = require("mobx");
6
6
  const react_1 = require("react");
7
- const formState_1 = require("./formState");
8
7
  const FormStateApp_1 = require("./FormStateApp");
8
+ const rules_1 = require("./rules");
9
9
  const useFormStates_1 = require("./useFormStates");
10
10
  describe("useFormStates", () => {
11
11
  it("can lazily create form states", async () => {
@@ -114,7 +114,7 @@ describe("useFormStates", () => {
114
114
  const originalConfig = { id: { type: "value" }, firstName: { type: "value" } };
115
115
  const updatedConfig = {
116
116
  id: { type: "value" },
117
- firstName: { type: "value", rules: [formState_1.required] },
117
+ firstName: { type: "value", rules: [rules_1.required] },
118
118
  };
119
119
  function TestComponent() {
120
120
  const [config, setConfig] = react_1.useState(originalConfig);
@@ -148,7 +148,7 @@ describe("useFormStates", () => {
148
148
  jsx_runtime_1.jsx(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }) }, void 0)] }, void 0));
149
149
  }
150
150
  // When we render
151
- const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
151
+ await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, {}, void 0));
152
152
  // Then addRules was only called once
153
153
  expect(addRules).toHaveBeenCalledTimes(1);
154
154
  });
@@ -156,11 +156,6 @@ describe("useFormStates", () => {
156
156
  // Given a user wants to use auto save
157
157
  const autoSave = jest.fn();
158
158
  function TestComponent() {
159
- const config = {
160
- id: { type: "value" },
161
- firstName: { type: "value" },
162
- lastName: { type: "value" },
163
- };
164
159
  const { getFormState } = useFormStates_1.useFormStates({
165
160
  config,
166
161
  getId: (o) => o.id,
@@ -184,7 +179,51 @@ describe("useFormStates", () => {
184
179
  expect(autoSave).toHaveBeenCalledTimes(1);
185
180
  expect(autoSave).toHaveBeenCalledWith({ id: "a:1", firstName: "first", lastName: "first" });
186
181
  });
182
+ it("can set readOnly via the hook opt", async () => {
183
+ // Given a test component
184
+ function TestComponent({ readOnly }) {
185
+ const { getFormState } = useFormStates_1.useFormStates({
186
+ config,
187
+ getId: (o) => o.id,
188
+ // And it passes readOnly directly to useFormStates
189
+ readOnly,
190
+ });
191
+ return jsx_runtime_1.jsx(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }) }, void 0);
192
+ }
193
+ // When we render
194
+ const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, { readOnly: true }, void 0));
195
+ // Then it's read only
196
+ expect(r.firstName()).toHaveAttribute("data-readonly", "true");
197
+ // And when we rerender
198
+ await r.rerender(jsx_runtime_1.jsx(TestComponent, { readOnly: false }, void 0));
199
+ // Then it's not read only
200
+ expect(r.firstName()).toHaveAttribute("data-readonly", "false");
201
+ });
202
+ it("can set readOnly via the getFormState function", async () => {
203
+ // Given a test component
204
+ function TestComponent({ readOnly }) {
205
+ const { getFormState } = useFormStates_1.useFormStates({
206
+ config,
207
+ getId: (o) => o.id,
208
+ });
209
+ // And it passes readOnly directly to getFormState
210
+ return jsx_runtime_1.jsx(ChildComponent, { os: getFormState({ id: "a:1", firstName: "Brandon" }, { readOnly }) }, void 0);
211
+ }
212
+ // When we render
213
+ const r = await rtl_utils_1.render(jsx_runtime_1.jsx(TestComponent, { readOnly: true }, void 0));
214
+ // Then it's read only
215
+ expect(r.firstName()).toHaveAttribute("data-readonly", "true");
216
+ // And when we rerender
217
+ await r.rerender(jsx_runtime_1.jsx(TestComponent, { readOnly: false }, void 0));
218
+ // Then it's not read only
219
+ expect(r.firstName()).toHaveAttribute("data-readonly", "false");
220
+ });
187
221
  });
222
+ const config = {
223
+ id: { type: "value" },
224
+ firstName: { type: "value" },
225
+ lastName: { type: "value" },
226
+ };
188
227
  function ChildComponent({ os }) {
189
- return jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName" }, { children: os.firstName.value }), void 0);
228
+ return (jsx_runtime_1.jsx("div", Object.assign({ "data-testid": "firstName", "data-readonly": os.firstName.readOnly }, { children: os.firstName.value }), void 0));
190
229
  }
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,12 @@
1
- import { ObjectConfig } from "./formState";
1
+ import { ObjectConfig } from "./config";
2
+ export declare type Builtin = Date | Function | Uint8Array | string | number | boolean;
3
+ export declare type Primitive = undefined | null | boolean | string | number | Function | Date | {
4
+ toJSON(): any;
5
+ };
6
+ /** Makes the keys in `T` required while keeping the values undefined. */
7
+ export declare type DeepRequired<T> = T extends Primitive ? T : {
8
+ [P in keyof Required<T>]: T[P] extends Array<infer U> ? Array<DeepRequired<U>> : T[P] extends ReadonlyArray<infer U2> ? ReadonlyArray<DeepRequired<U2>> : DeepRequired<T[P]>;
9
+ };
2
10
  export declare function fail(message?: string): never;
3
11
  export declare function assertNever(x: never): never;
4
12
  /** Introspects the `init` prop to see if has a `map` function/etc. and returns the form value. */
@@ -15,3 +23,18 @@ export declare function initValue<T>(config: ObjectConfig<T>, init: any): T;
15
23
  export declare function pickFields<T, I>(formConfig: ObjectConfig<T>, instance: I): {
16
24
  [K in keyof T]: K extends keyof I ? I[K] : never;
17
25
  };
26
+ export declare function isNotUndefined<T>(value: T | undefined): value is T;
27
+ export declare function isEmpty(value: any): boolean;
28
+ /**
29
+ * An equals that does deep-ish equality.
30
+ *
31
+ * We only do non-identity equals for:
32
+ *
33
+ * - "plain" objects that have no custom prototype/i.e. are object literals
34
+ * - objects that implement `toJSON`
35
+ *
36
+ */
37
+ export declare function areEqual<T>(a?: T, b?: T): boolean;
38
+ export declare function hasToJSON(o?: unknown): o is {
39
+ toJSON(): void;
40
+ };
package/dist/utils.js CHANGED
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.pickFields = exports.initValue = exports.assertNever = exports.fail = void 0;
6
+ exports.hasToJSON = exports.areEqual = exports.isEmpty = exports.isNotUndefined = exports.pickFields = exports.initValue = exports.assertNever = exports.fail = void 0;
7
+ const fast_deep_equal_1 = __importDefault(require("fast-deep-equal"));
4
8
  const is_plain_object_1 = require("is-plain-object");
5
9
  const mobx_1 = require("mobx");
6
10
  function fail(message) {
@@ -68,3 +72,39 @@ function pickFields(formConfig, instance) {
68
72
  }));
69
73
  }
70
74
  exports.pickFields = pickFields;
75
+ function isNotUndefined(value) {
76
+ return value !== undefined;
77
+ }
78
+ exports.isNotUndefined = isNotUndefined;
79
+ function isEmpty(value) {
80
+ return value === undefined || value === null || value === "";
81
+ }
82
+ exports.isEmpty = isEmpty;
83
+ /**
84
+ * An equals that does deep-ish equality.
85
+ *
86
+ * We only do non-identity equals for:
87
+ *
88
+ * - "plain" objects that have no custom prototype/i.e. are object literals
89
+ * - objects that implement `toJSON`
90
+ *
91
+ */
92
+ function areEqual(a, b) {
93
+ if (is_plain_object_1.isPlainObject(a)) {
94
+ return fast_deep_equal_1.default(mobx_1.toJS(a), mobx_1.toJS(b));
95
+ }
96
+ if (hasToJSON(a) || hasToJSON(b)) {
97
+ const a1 = hasToJSON(a) ? a.toJSON() : a;
98
+ const b1 = hasToJSON(b) ? b.toJSON() : b;
99
+ return fast_deep_equal_1.default(a1, b1);
100
+ }
101
+ if (a && b && a instanceof Array && b instanceof Array) {
102
+ return fast_deep_equal_1.default(a, b);
103
+ }
104
+ return a === b;
105
+ }
106
+ exports.areEqual = areEqual;
107
+ function hasToJSON(o) {
108
+ return !!(o && typeof o === "object" && "toJSON" in o);
109
+ }
110
+ exports.hasToJSON = hasToJSON;
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const mobx_1 = require("mobx");
4
- const formState_1 = require("./formState");
4
+ const rules_1 = require("./rules");
5
5
  const utils_1 = require("./utils");
6
6
  describe("utils", () => {
7
7
  describe("pickFields", () => {
@@ -82,7 +82,7 @@ const authorWithAddressConfig = {
82
82
  address: {
83
83
  type: "object",
84
84
  config: {
85
- street: { type: "value", rules: [formState_1.required] },
85
+ street: { type: "value", rules: [rules_1.required] },
86
86
  city: { type: "value" },
87
87
  },
88
88
  },
@@ -95,7 +95,7 @@ const authorWithBooksConfig = {
95
95
  type: "list",
96
96
  config: {
97
97
  id: { type: "value" },
98
- title: { type: "value", rules: [formState_1.required] },
98
+ title: { type: "value", rules: [rules_1.required] },
99
99
  classification: { type: "value" },
100
100
  },
101
101
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/form-state",
3
- "version": "2.9.1",
3
+ "version": "2.11.0",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -1,192 +0,0 @@
1
- /**
2
- * Wraps a given input/on-the-wire type `T` for editing in a form.
3
- *
4
- * We basically mimic every field in `T` (i.e. `firstName`, `lastName`, etc.) but decorate them
5
- * with form-specific state like `touched`, `dirty`, `errors`, etc.
6
- *
7
- * The intent is that, after ensuring all fields are `valid`/etc., callers can take the
8
- * result of this `objectState.value` (or `objectState.originalValue` for the non-proxy version) and
9
- * have exactly the on-the-wire type `T` that they need to submit to the backend, without doing the
10
- * manual mapping of "data that was in the form controls" into "data that the backend wants".
11
- *
12
- * Note that this can be hierarchical by having by having a field of `ListFieldState` that
13
- * themselves each wrap an `ObjectState`, i.e.:
14
- *
15
- * ```
16
- * ObjectState for author
17
- * - firstName: FieldState
18
- * - lastName: FieldState
19
- * - rows: ListFieldState
20
- * - [0]: ObjectState for book 1
21
- * - title: FieldState
22
- * - [1]: ObjectState for book 2
23
- * - title: FieldState
24
- * ```
25
- */
26
- export declare type ObjectState<T, P = any> = FieldStates<T> & FieldState<P, T> & {
27
- /** Sets the state of fields in `state`. */
28
- set(state: Partial<T>, opts?: SetOpts): void;
29
- /** Returns whether the object can be saved, i.e. is valid, but also as a side-effect marks touched. */
30
- canSave(): boolean;
31
- };
32
- export declare type Builtin = Date | Function | Uint8Array | string | number | boolean;
33
- /** 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. */
34
- declare type FieldStates<T> = {
35
- [K in keyof T]-?: T[K] extends Array<infer U> | null | undefined ? [U] extends [Builtin] ? FieldState<T, T[K]> : ListFieldState<T, U> : T[K] extends Builtin | null | undefined ? FieldState<T, T[K]> : ObjectState<T[K], T>;
36
- };
37
- declare type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
38
- /** A validation rule, given the value and name, return the error string if valid, or undefined if valid. */
39
- export declare type Rule<T, V> = (opts: {
40
- value: V;
41
- key: string;
42
- originalValue: V;
43
- object: IfAny<T, any, ObjectState<T>>;
44
- }) => string | undefined;
45
- /** A rule that validates `value` is not `undefined`, `null`, or empty string. */
46
- export declare const required: <V>({ value: v }: {
47
- value: V;
48
- }) => string | undefined;
49
- /**
50
- * Form state for a primitive field in the form, i.e. its value but also touched/validation/etc. state.
51
- *
52
- * This API also provides hooks for form elements to call into, i.e. `blur()` and `set(...)` that will
53
- * update the field state and re-render, i.e. when including in an `ObjectState`-typed literal that is
54
- * an mobx `useLocalObservable`/observable.
55
- *
56
- * Note that `V` will always have `null | undefined` added to it by `FieldStates`, b/c most form fields
57
- * i.e. text boxes, can always be cleared out/deleted.
58
- */
59
- export interface FieldState<T, V> {
60
- readonly key: string;
61
- value: V;
62
- readonly originalValue: V;
63
- touched: boolean;
64
- readOnly: boolean;
65
- readonly required: boolean;
66
- readonly dirty: boolean;
67
- readonly valid: boolean;
68
- readonly isNewEntity: boolean;
69
- rules: Rule<T, V>[];
70
- readonly errors: string[];
71
- /** Returns a subset of V with only the changed values. Currently not observable. */
72
- readonly changedValue: V;
73
- /** Focuses the field. Disables changes from `ObjectState.set` calls. */
74
- focus(): void;
75
- /** Blur marks the field as touched and triggers an auto-save. */
76
- blur(): void;
77
- /** Triggers an auto-save; the caller (i.e. useFormState) should still dirty & valid check. */
78
- maybeAutoSave(): void;
79
- set(value: V, opts?: SetOpts): void;
80
- /** Reverts back to the original value and resets dirty/touched. */
81
- reset(): void;
82
- /** Accepts the current changed value (if any) as the original and resets dirty/touched. */
83
- save(): void;
84
- }
85
- /** Public options for our `set` command. */
86
- export interface SetOpts {
87
- /** Whether this `set` should trigger an auto-save; defaults to true. */
88
- autoSave?: boolean;
89
- }
90
- /** Internal `.set` opts for conditionally that form-state internally percolates. */
91
- interface InternalSetOpts extends SetOpts {
92
- /** `resetting` is the code calling `.reset()` to revert to original values. */
93
- resetting?: boolean;
94
- /** `refreshing` is when `useFormState` sees a new value. */
95
- refreshing?: boolean;
96
- }
97
- export interface FieldStateInternal<T, V> extends FieldState<T, V> {
98
- set(value: V, opts?: InternalSetOpts): void;
99
- _isIdKey: boolean;
100
- _isDeleteKey: boolean;
101
- _isReadOnlyKey: boolean;
102
- _focused: boolean;
103
- }
104
- export declare type ObjectStateInternal<T, P = any> = ObjectState<T, P> & {
105
- set(value: P, opts?: InternalSetOpts): void;
106
- };
107
- /** Form state for list of children, i.e. `U` is a `Book` in a form with a `books: Book[]`. */
108
- export interface ListFieldState<T, U> extends Omit<FieldState<T, U[]>, "originalValue"> {
109
- readonly rows: ReadonlyArray<ObjectState<U>>;
110
- add(value: U, index?: number): void;
111
- remove(indexOrValue: number | U): void;
112
- }
113
- /**
114
- * Config rules for each field in `T` that we're editing in a form.
115
- *
116
- * Basically every field is either a value/primitive or a list, and this `ObjectConfig` lets
117
- * the caller define field-specific behavior, i.e. validation rules.
118
- */
119
- export declare type ObjectConfig<T> = {
120
- [P in keyof OmitIf<T, Function>]: 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]>;
121
- };
122
- declare type OmitIf<Base, Condition> = Pick<Base, {
123
- [Key in keyof Base]: Base[Key] extends Condition ? never : Key;
124
- }[keyof Base]>;
125
- /** Field configuration for primitive values, i.e. strings/numbers/Dates/user-defined types. */
126
- export declare type ValueFieldConfig<T, V> = {
127
- type: "value";
128
- rules?: Rule<T, V | null | undefined>[];
129
- /**
130
- * If true, marks this field as the id, which will be used for things like "always include in changedValue".
131
- *
132
- * Defaults to true for fields named `id`.
133
- */
134
- isIdKey?: boolean;
135
- /** If true, and this value is used on an entity in a list, the entity won't count towards the list validity. */
136
- isDeleteKey?: boolean;
137
- /** If true, the entity that contains this value will be treated as read only. */
138
- isReadOnlyKey?: boolean;
139
- /**
140
- * Marks a field as being backed by a mobx class computed field.
141
- *
142
- * Note that it might still be settable (some computed have setters), but we do
143
- * exclude from the `reset` operation, i.e. we assume resetting other non-computed fields
144
- * will effectively reset this field as well.
145
- */
146
- computed?: boolean;
147
- /** Marks a field as being initiallyread-only, i.e. `field.readOnly = true/false` can change this default. */
148
- readOnly?: boolean;
149
- };
150
- /** Field configuration for list values, i.e. `U` is `Book` in a form with `books: Book[]`. */
151
- export declare type ListFieldConfig<T, U> = {
152
- type: "list";
153
- /** Rules that can run on the full list of children. */
154
- rules?: Rule<T, readonly ObjectState<U>[]>[];
155
- /** Config for each child's form state, i.e. each book. */
156
- config: ObjectConfig<U>;
157
- /**
158
- * What the server-side update behavior is for this collection.
159
- *
160
- * When exhaustive, we include all rows in `changedValue` so that we
161
- * don't orhan unchanged rows.
162
- *
163
- * When incremental, we only include changed rows in `changedValue`.
164
- *
165
- * Defaults to `exhaustive` b/c that is the safest and also Joist's
166
- * default behavior.
167
- */
168
- update?: "exhaustive" | "incremental";
169
- };
170
- export declare type ObjectFieldConfig<U> = {
171
- type: "object";
172
- /** Config for the child's form state, i.e. each book. */
173
- config: ObjectConfig<U>;
174
- };
175
- /**
176
- * Creates a new `ObjectState` for a given form object `T` given config rules in `config`.
177
- *
178
- * The returned `ObjectState` can be used in a mobx `useLocalObservable` to driven an
179
- * interactive form that tracks the current valid/touched/etc. state of both each
180
- * individual fields as well as the top-level form/object itself.
181
- */
182
- export declare function createObjectState<T>(config: ObjectConfig<T>, instance: T, opts?: {
183
- maybeAutoSave?: () => void;
184
- }): ObjectState<T>;
185
- declare type Primitive = undefined | null | boolean | string | number | Function | Date | {
186
- toJSON(): any;
187
- };
188
- /** Makes the keys in `T` required while keeping the values undefined. */
189
- export declare type DeepRequired<T> = T extends Primitive ? T : {
190
- [P in keyof Required<T>]: T[P] extends Array<infer U> ? Array<DeepRequired<U>> : T[P] extends ReadonlyArray<infer U2> ? ReadonlyArray<DeepRequired<U2>> : DeepRequired<T[P]>;
191
- };
192
- export {};