@homebound/form-state 2.26.12 → 2.27.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/config.d.ts CHANGED
@@ -56,10 +56,14 @@ export type ListFieldConfig<U> = {
56
56
  *
57
57
  * When incremental, we only include changed rows in `changedValue`.
58
58
  *
59
+ * When deep-exhaustive, we include all rows in `changedValue` and also
60
+ * all fields in the child entities, i.e. not just placeholder `{ id: "b:1" }`
61
+ * values for otherwise-unchanged children.
62
+ *
59
63
  * Defaults to `exhaustive` b/c that is the safest and also Joist's
60
64
  * default behavior.
61
65
  */
62
- update?: "exhaustive" | "incremental";
66
+ update?: "exhaustive" | "incremental" | "deep-exhaustive";
63
67
  /** Set to not consider the order of the list when evaluating changed/dirty states, defaults true. */
64
68
  strictOrder?: false;
65
69
  };
@@ -8,4 +8,4 @@ export interface ListFieldState<U> extends FieldState<U[]> {
8
8
  add(value: U, index?: number): void;
9
9
  remove(indexOrValue: number | U): void;
10
10
  }
11
- export declare function newListFieldState<T, K extends keyof T, U>(parentCopy: T, parentInstance: T, parentState: () => ObjectState<T>, key: K, rules: Rule<readonly ObjectState<U>[]>[], listConfig: ListFieldConfig<U>, config: ObjectFieldConfig<U>, strictOrder: boolean, maybeAutoSave: () => void): ListFieldState<U>;
11
+ export declare function newListFieldState<T, K extends keyof T, U>(parentCopy: T, parentInstance: T, parentState: () => ObjectState<T>, key: K, rules: Rule<readonly ObjectState<U>[]>[], listConfig: ListFieldConfig<U>, config: ObjectFieldConfig<U>, strictOrder: boolean, maybeAutoSave: () => void, deepExhaustive: boolean): ListFieldState<U>;
@@ -13,7 +13,7 @@ function newListFieldState(
13
13
  // parentCopy is objectField's deepClone-d `originalCopy` that it/we use for `dirty` checking
14
14
  parentCopy,
15
15
  // parentInstance is objectField's `instance`, that we're currently mutating
16
- parentInstance, parentState, key, rules, listConfig, config, strictOrder, maybeAutoSave) {
16
+ parentInstance, parentState, key, rules, listConfig, config, strictOrder, maybeAutoSave, deepExhaustive) {
17
17
  var _a;
18
18
  // Keep a map of "item in the parentInstance list" -> "that item's ObjectState"
19
19
  const rowMap = new Map();
@@ -30,6 +30,7 @@ parentInstance, parentState, key, rules, listConfig, config, strictOrder, maybeA
30
30
  });
31
31
  // Given a child POJO (or a copy/clone of a child POJO), return its ObjectState wrapper.
32
32
  function getOrCreateChildState(child, opts) {
33
+ var _a;
33
34
  let childState = rowMap.get(child);
34
35
  // If we're being reverted to our originalValue, i.e. values is actually
35
36
  // a list of copies, use the copyMap to recover the non-copy original value
@@ -50,7 +51,7 @@ parentInstance, parentState, key, rules, listConfig, config, strictOrder, maybeA
50
51
  }
51
52
  // If we didn't have an existing child, just make a new object state
52
53
  if (!childState) {
53
- childState = (0, objectField_1.newObjectState)(config, parentState, undefined, list, child, undefined, maybeAutoSave);
54
+ childState = (0, objectField_1.newObjectState)(config, parentState, undefined, list, child, undefined, maybeAutoSave, (_a = listConfig.update === "deep-exhaustive") !== null && _a !== void 0 ? _a : deepExhaustive);
54
55
  rowMap.set(child, childState);
55
56
  }
56
57
  return childState;
@@ -59,5 +59,5 @@ export declare function createObjectState<T>(config: ObjectConfig<T>, instance:
59
59
  maybeAutoSave?: () => void;
60
60
  }): ObjectState<T>;
61
61
  /** A more internal version of `createObjectState`. */
62
- export declare function newObjectState<T, P = any>(config: ObjectFieldConfig<T>, parentState: (() => ObjectState<P>) | undefined, parentInstance: P | undefined, parentListState: FieldState<any> | undefined, instance: T, key: keyof T | undefined, maybeAutoSave: () => void): ObjectState<T>;
62
+ export declare function newObjectState<T, P = any>(config: ObjectFieldConfig<T>, parentState: (() => ObjectState<P>) | undefined, parentInstance: P | undefined, parentListState: FieldState<any> | undefined, instance: T, key: keyof T | undefined, maybeAutoSave: () => void, deepExhaustive: boolean): ObjectState<T>;
63
63
  export {};
@@ -21,10 +21,10 @@ function fragment(value) {
21
21
  */
22
22
  function createObjectState(config, instance, opts = {}) {
23
23
  const noop = () => { };
24
- return newObjectState({ type: "object", config }, undefined, undefined, undefined, instance, undefined, opts.maybeAutoSave || noop);
24
+ return newObjectState({ type: "object", config }, undefined, undefined, undefined, instance, undefined, opts.maybeAutoSave || noop, false);
25
25
  }
26
26
  /** A more internal version of `createObjectState`. */
27
- function newObjectState(config, parentState, parentInstance, parentListState, instance, key, maybeAutoSave) {
27
+ function newObjectState(config, parentState, parentInstance, parentListState, instance, key, maybeAutoSave, deepExhaustive) {
28
28
  // This is what we return, but we only know it's value until we call `observable`, so
29
29
  // we create a mutable variable to capture it so that we can create fields/call their
30
30
  // constructors and give them a way to access it later.
@@ -55,7 +55,7 @@ function newObjectState(config, parentState, parentInstance, parentListState, in
55
55
  ((0, mobx_1.isObservable)(instance) ? !(key in originalCopy) : false), (_b = config.readOnly) !== null && _b !== void 0 ? _b : false, (_c = config.strictOrder) !== null && _c !== void 0 ? _c : true, maybeAutoSave);
56
56
  }
57
57
  else if (config.type === "list") {
58
- field = (0, listField_1.newListFieldState)(originalCopy, instance, getObjectState, key, config.rules || [], config, { type: "object", config: config.config }, (_d = config.strictOrder) !== null && _d !== void 0 ? _d : true, maybeAutoSave);
58
+ field = (0, listField_1.newListFieldState)(originalCopy, instance, getObjectState, key, config.rules || [], config, { type: "object", config: config.config }, (_d = config.strictOrder) !== null && _d !== void 0 ? _d : true, maybeAutoSave, deepExhaustive);
59
59
  }
60
60
  else if (config.type === "object") {
61
61
  // Because our objectField will fundamentally want to do `child.firstName.set(...)` or
@@ -65,7 +65,7 @@ function newObjectState(config, parentState, parentInstance, parentListState, in
65
65
  if (!instance[key]) {
66
66
  instance[key] = {};
67
67
  }
68
- field = newObjectState(config, getObjectState, instance, undefined, instance[key], key, maybeAutoSave);
68
+ field = newObjectState(config, getObjectState, instance, undefined, instance[key], key, maybeAutoSave, deepExhaustive);
69
69
  }
70
70
  else if (config.type === "fragment") {
71
71
  field = (0, fragmentField_1.newFragmentField)(instance, key);
@@ -223,7 +223,9 @@ function newObjectState(config, parentState, parentInstance, parentListState, in
223
223
  f.changedValue &&
224
224
  Object.entries(f.changedValue).length > 0) ||
225
225
  // ...or they're non-empty sub-lists
226
- (this.isNewEntity && f._kind === "list" && ((_a = f.value) === null || _a === void 0 ? void 0 : _a.length) > 0)) {
226
+ (this.isNewEntity && f._kind === "list" && ((_a = f.value) === null || _a === void 0 ? void 0 : _a.length) > 0) ||
227
+ // ...or the parent list is deep-exhaustive
228
+ deepExhaustive) {
227
229
  result[f.key] = f.changedValue;
228
230
  }
229
231
  });
@@ -1339,7 +1339,7 @@ describe("formState", () => {
1339
1339
  // And we don't include it in the changedValue
1340
1340
  expect(formState.changedValue).toEqual({ firstName: "First" });
1341
1341
  });
1342
- it("can return only changed list fields", () => {
1342
+ it("can return only changed and exhaustive list fields", () => {
1343
1343
  // Given an author with some books
1344
1344
  const formState = (0, objectField_1.createObjectState)(authorWithBooksConfig, {
1345
1345
  id: "a:1",
@@ -1430,6 +1430,40 @@ describe("formState", () => {
1430
1430
  books: [{ id: "b:1", title: "t1b" }],
1431
1431
  });
1432
1432
  });
1433
+ it("can return only changed but deep exhaustive list fields", () => {
1434
+ // Given an author
1435
+ const formState = (0, objectField_1.createObjectState)({
1436
+ id: { type: "value" },
1437
+ // And the books collection is marked as deep-exhaustive
1438
+ books: {
1439
+ type: "list",
1440
+ update: "deep-exhaustive",
1441
+ config: {
1442
+ id: { type: "value" },
1443
+ title: { type: "value", rules: [rules_1.required] },
1444
+ },
1445
+ },
1446
+ }, {
1447
+ id: "a:1",
1448
+ firstName: "f",
1449
+ books: [
1450
+ { id: "b:1", title: "t1" },
1451
+ { id: "b:2", title: "t2" },
1452
+ ],
1453
+ });
1454
+ // And initially nothing is changed
1455
+ expect(formState.changedValue).toEqual({ id: "a:1" });
1456
+ // When we change the 1st book
1457
+ formState.books.rows[0].title.value = "t1b";
1458
+ // Then we see all books & all attributes
1459
+ expect(formState.changedValue).toEqual({
1460
+ id: "a:1",
1461
+ books: [
1462
+ { id: "b:1", title: "t1b" },
1463
+ { id: "b:2", title: "t2" },
1464
+ ],
1465
+ });
1466
+ });
1433
1467
  it("uses the op key as a hint to use incremental list behavior", () => {
1434
1468
  // Given an author
1435
1469
  const formState = (0, objectField_1.createObjectState)({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/form-state",
3
- "version": "2.26.12",
3
+ "version": "2.27.0",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",