@homebound/form-state 2.5.2 → 2.6.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/formState.js CHANGED
@@ -431,10 +431,22 @@ function newListFieldState(parentInstance, parentState, key, rules, listConfig,
431
431
  },
432
432
  get changedValue() {
433
433
  const result = [];
434
- const pushAll = listConfig.update !== "incremental";
434
+ const hasOpKey = Object.keys(listConfig.config).includes("op");
435
+ const hasLegacyOpKey = Object.keys(listConfig.config).some((key) => key === "delete" || key === "remove");
436
+ const incremental = listConfig.update === "incremental" ||
437
+ // Implicitly enable incremental mode if we see an op key
438
+ (listConfig.update === undefined && (hasOpKey || hasLegacyOpKey));
439
+ const exhaustive = !incremental;
435
440
  this.rows.forEach((r) => {
436
- if (pushAll || r.dirty) {
437
- result.push(r.changedValue);
441
+ var _a;
442
+ var _b;
443
+ if (exhaustive || r.dirty || r.isNewEntity) {
444
+ const changed = r.changedValue;
445
+ // Ensure we have an `op: include` key, following https://joist-orm.io/docs/features/partial-update-apis
446
+ if (incremental && hasOpKey) {
447
+ (_a = (_b = changed).op) !== null && _a !== void 0 ? _a : (_b.op = "include");
448
+ }
449
+ result.push(changed);
438
450
  }
439
451
  });
440
452
  return result;
@@ -979,6 +979,81 @@ describe("formState", () => {
979
979
  books: [{ id: "b:1", title: "t1b" }],
980
980
  });
981
981
  });
982
+ it("uses the op key as a hint to use incremental list behavior", () => {
983
+ // Given an author
984
+ const formState = formState_1.createObjectState({
985
+ id: { type: "value" },
986
+ // And the books collection is not explicitly marked as incremental
987
+ books: {
988
+ type: "list",
989
+ config: {
990
+ id: { type: "value" },
991
+ title: { type: "value", rules: [formState_1.required] },
992
+ // But it does have an op key
993
+ op: { type: "value" },
994
+ },
995
+ },
996
+ }, {
997
+ id: "a:1",
998
+ firstName: "f",
999
+ books: [
1000
+ // And the books start out as included
1001
+ { id: "b:1", title: "t1", op: "include" },
1002
+ { id: "b:2", title: "t2", op: "include" },
1003
+ ],
1004
+ });
1005
+ // And initially nothing is changed
1006
+ expect(formState.changedValue).toEqual({ id: "a:1" });
1007
+ // When we delete the 1st book
1008
+ formState.books.rows[0].op.value = "delete";
1009
+ // Then only the 1st book is included
1010
+ expect(formState.changedValue).toEqual({
1011
+ id: "a:1",
1012
+ books: [{ id: "b:1", op: "delete" }],
1013
+ });
1014
+ });
1015
+ it("defaults the incremental op key to included when not set", () => {
1016
+ // Given an author
1017
+ const formState = formState_1.createObjectState({
1018
+ id: { type: "value" },
1019
+ // And the books collection is not explicitly marked as incremental
1020
+ books: {
1021
+ type: "list",
1022
+ config: {
1023
+ id: { type: "value" },
1024
+ title: { type: "value", rules: [formState_1.required] },
1025
+ // But it does have an op key
1026
+ op: { type: "value" },
1027
+ },
1028
+ },
1029
+ }, {
1030
+ id: "a:1",
1031
+ firstName: "f",
1032
+ books: [
1033
+ // And no op keys are initially set
1034
+ { id: "b:1", title: "t1" },
1035
+ { id: "b:2", title: "t2" },
1036
+ ],
1037
+ });
1038
+ // And initially nothing is changed
1039
+ expect(formState.changedValue).toEqual({ id: "a:1" });
1040
+ // When we delete the 1st book
1041
+ formState.books.rows[0].op.value = "delete";
1042
+ // And add a 3rd book
1043
+ formState.books.add({ title: "t3" });
1044
+ // Then the 3rd book doesn't have an explicit op yet
1045
+ expect(formState.books.rows[2].op.value).toBeUndefined();
1046
+ // But when we create changedValue
1047
+ expect(formState.changedValue).toEqual({
1048
+ id: "a:1",
1049
+ books: [
1050
+ // Then the deleted book is included
1051
+ { id: "b:1", op: "delete" },
1052
+ // And the 3rd book is included w/the `op: include` flag added
1053
+ { title: "t3", op: "include" },
1054
+ ],
1055
+ });
1056
+ });
982
1057
  it("can observe value changes", () => {
983
1058
  const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f" });
984
1059
  let ticks = 0;
@@ -26,6 +26,7 @@ export interface BookInput {
26
26
  classification?: DeweyDecimalClassification;
27
27
  delete?: boolean | null | undefined;
28
28
  isPublished?: boolean;
29
+ op?: "include" | "delete" | "remove";
29
30
  }
30
31
  export interface DeweyDecimalClassification {
31
32
  number: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homebound/form-state",
3
- "version": "2.5.2",
3
+ "version": "2.6.0",
4
4
  "author": "Homebound",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",