@homebound/form-state 2.10.0 → 2.12.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,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const mobx_1 = require("mobx");
4
+ const objectField_1 = require("./fields/objectField");
4
5
  const formStateDomain_1 = require("./formStateDomain");
5
- const formState_1 = require("./formState");
6
+ const rules_1 = require("./rules");
6
7
  describe("formState", () => {
7
8
  it("mobx lists maintain observable identity", () => {
8
9
  // given a parent observable
@@ -14,11 +15,11 @@ describe("formState", () => {
14
15
  expect(a.list[0] === c1).toEqual(true);
15
16
  });
16
17
  it("can create a simple object", () => {
17
- const a = formState_1.createObjectState({ title: { type: "value", rules: [formState_1.required] } }, {});
18
+ const a = objectField_1.createObjectState({ title: { type: "value", rules: [rules_1.required] } }, {});
18
19
  expect(a.valid).toBeFalsy();
19
20
  });
20
21
  it("can validate a simple input", () => {
21
- const a = formState_1.createObjectState({ title: { type: "value", rules: [formState_1.required] } }, { title: "b1" });
22
+ const a = objectField_1.createObjectState({ title: { type: "value", rules: [rules_1.required] } }, { title: "b1" });
22
23
  let numErrors = 0;
23
24
  mobx_1.autorun(() => {
24
25
  numErrors = a.title.errors.length;
@@ -32,15 +33,15 @@ describe("formState", () => {
32
33
  expect(a.errors).toEqual(["title: Required"]);
33
34
  });
34
35
  it("can tell what is required", () => {
35
- const a = formState_1.createObjectState({
36
- title: { type: "value", rules: [formState_1.required] },
36
+ const a = objectField_1.createObjectState({
37
+ title: { type: "value", rules: [rules_1.required] },
37
38
  isPublished: { type: "value" },
38
39
  }, {});
39
40
  expect(a.title.required).toBeTruthy();
40
41
  expect(a.isPublished.required).toBeFalsy();
41
42
  });
42
43
  it("trims whitespace within the required rule", () => {
43
- const a = formState_1.createObjectState({ title: { type: "value", rules: [formState_1.required] } }, { title: "initial valid title" });
44
+ const a = objectField_1.createObjectState({ title: { type: "value", rules: [rules_1.required] } }, { title: "initial valid title" });
44
45
  let numErrors = 0;
45
46
  mobx_1.autorun(() => {
46
47
  numErrors = a.title.errors.length;
@@ -55,16 +56,16 @@ describe("formState", () => {
55
56
  expect(a.errors).toEqual(["title: Required"]);
56
57
  });
57
58
  it("can set values", () => {
58
- const a = formState_1.createObjectState({ title: { type: "value" } }, { title: "b1" });
59
+ const a = objectField_1.createObjectState({ title: { type: "value" } }, { title: "b1" });
59
60
  expect(a.title.value).toEqual("b1");
60
61
  });
61
62
  it("can read values", () => {
62
- const a = formState_1.createObjectState({ title: { type: "value" } }, { title: "b1" });
63
+ const a = objectField_1.createObjectState({ title: { type: "value" } }, { title: "b1" });
63
64
  expect(a.value.title).toEqual("b1");
64
65
  expect(a.title.value).toEqual("b1");
65
66
  });
66
67
  it("can set dates", () => {
67
- const a = formState_1.createObjectState({
68
+ const a = objectField_1.createObjectState({
68
69
  birthday: {
69
70
  type: "value",
70
71
  rules: [({ value }) => ((value === null || value === void 0 ? void 0 : value.getTime()) === formStateDomain_1.jan2.getTime() ? "cannot be born on jan2" : undefined)],
@@ -149,7 +150,7 @@ describe("formState", () => {
149
150
  };
150
151
  // And we initially have ids in the input
151
152
  const a1 = { id: "1", firstName: "a1", books: [{ id: "2", title: "t1" }] };
152
- const state = formState_1.createObjectState(config, a1);
153
+ const state = objectField_1.createObjectState(config, a1);
153
154
  // And we edit a few things
154
155
  state.firstName.set("a2");
155
156
  state.books.add({ title: "t2" });
@@ -286,7 +287,7 @@ describe("formState", () => {
286
287
  expect(numErrors).toEqual(0);
287
288
  });
288
289
  it("can validate across fields", () => {
289
- const a = formState_1.createObjectState({
290
+ const a = objectField_1.createObjectState({
290
291
  firstName: { type: "value", rules: [] },
291
292
  lastName: { type: "value" },
292
293
  }, {});
@@ -303,7 +304,7 @@ describe("formState", () => {
303
304
  it("passes sanitized values to validation rules", () => {
304
305
  // Given a field with a validation rule
305
306
  const rule = jest.fn().mockReturnValue("Required");
306
- const a = formState_1.createObjectState({ firstName: { type: "value", rules: [rule] } }, {});
307
+ const a = objectField_1.createObjectState({ firstName: { type: "value", rules: [rule] } }, {});
307
308
  expect(rule).toHaveBeenCalledTimes(0);
308
309
  // When the field is set to empty string
309
310
  a.firstName.value = "";
@@ -320,7 +321,7 @@ describe("formState", () => {
320
321
  it("passes trimmed values to validation rules on maybeAutoSave", () => {
321
322
  // Given we have a required rule
322
323
  const rule = jest.fn().mockReturnValue("Required");
323
- const a = formState_1.createObjectState({ firstName: { type: "value", rules: [rule] } }, {});
324
+ const a = objectField_1.createObjectState({ firstName: { type: "value", rules: [rule] } }, {});
324
325
  expect(rule).toHaveBeenCalledTimes(0);
325
326
  // When the user initially sets an empty string value
326
327
  a.firstName.focus();
@@ -347,8 +348,8 @@ describe("formState", () => {
347
348
  });
348
349
  });
349
350
  it("simple value changes trigger observers", () => {
350
- const a = formState_1.createObjectState({
351
- title: { type: "value", rules: [formState_1.required] },
351
+ const a = objectField_1.createObjectState({
352
+ title: { type: "value", rules: [rules_1.required] },
352
353
  }, { title: "t2" });
353
354
  let lastTitle = undefined;
354
355
  let ticks = 0;
@@ -465,7 +466,7 @@ describe("formState", () => {
465
466
  expect(maybeAutoSave).toBeCalledTimes(0);
466
467
  });
467
468
  it("knows list of primitives are dirty", () => {
468
- const a1 = formState_1.createObjectState({ favoriteColors: { type: "value" } }, {});
469
+ const a1 = objectField_1.createObjectState({ favoriteColors: { type: "value" } }, {});
469
470
  expect(a1.favoriteColors.dirty).toBeFalsy();
470
471
  a1.favoriteColors.set([formStateDomain_1.Color.Blue]);
471
472
  expect(a1.dirty).toBeTruthy();
@@ -476,7 +477,7 @@ describe("formState", () => {
476
477
  expect(a1.dirty).toBeFalsy();
477
478
  });
478
479
  it("knows list of primitives are dirty with initialized as empty list", () => {
479
- const a1 = formState_1.createObjectState({ favoriteColors: { type: "value" } }, { favoriteColors: [] });
480
+ const a1 = objectField_1.createObjectState({ favoriteColors: { type: "value" } }, { favoriteColors: [] });
480
481
  expect(a1.favoriteColors.dirty).toBeFalsy();
481
482
  a1.favoriteColors.set([formStateDomain_1.Color.Blue]);
482
483
  expect(a1.dirty).toBeTruthy();
@@ -656,7 +657,7 @@ describe("formState", () => {
656
657
  expect(a1.value.firstName).toBeNull();
657
658
  });
658
659
  it("can map properties to other types", () => {
659
- const a1 = formState_1.createObjectState({
660
+ const a1 = objectField_1.createObjectState({
660
661
  person: { type: "value" },
661
662
  }, { person: { firstName: "a1", lastName: "b1" } });
662
663
  a1.person.set({ firstName: "a2", lastName: "b2" });
@@ -682,7 +683,7 @@ describe("formState", () => {
682
683
  });
683
684
  it("maintain field readOnly state when form is readOnly", () => {
684
685
  // Given a formState
685
- const formState = formState_1.createObjectState({ title: { type: "value", rules: [formState_1.required], readOnly: true } }, {});
686
+ const formState = objectField_1.createObjectState({ title: { type: "value", rules: [rules_1.required], readOnly: true } }, {});
686
687
  // Then expect form
687
688
  expect(formState.title.readOnly).toBeTruthy();
688
689
  });
@@ -695,7 +696,7 @@ describe("formState", () => {
695
696
  });
696
697
  // And it is read-only
697
698
  a1.readOnly = true;
698
- // Then it's fields are read-only
699
+ // Then its fields are read-only
699
700
  const fields = [a1, a1.firstName, a1.books, a1.books.rows[0].title, a1.books.rows[0].classification];
700
701
  fields.forEach((f) => expect(f.readOnly).toBeTruthy());
701
702
  // And even if a specific field tries to _not_ be read-only,
@@ -714,7 +715,7 @@ describe("formState", () => {
714
715
  });
715
716
  // And the top-level form is explicitly set to read-only=false
716
717
  a1.readOnly = false;
717
- // Then it's fields are not read-only
718
+ // Then its fields are not read-only
718
719
  const fields = [a1, a1.firstName, a1.books, a1.books.rows[0].title, a1.books.rows[0].classification];
719
720
  fields.forEach((f) => expect(f.readOnly).toBeFalsy());
720
721
  // But when one of the fields opts in to readOnly
@@ -722,16 +723,50 @@ describe("formState", () => {
722
723
  // Then the field-level rule is respected
723
724
  expect(a1.firstName.readOnly).toBeTruthy();
724
725
  });
726
+ it("has an object-level loading=true override field-level loading=false", () => {
727
+ // Given a top-level object
728
+ const a1 = createAuthorInputState({
729
+ firstName: "a1",
730
+ lastName: "aL1",
731
+ books: [{ title: "b1", classification: formStateDomain_1.dd100 }],
732
+ });
733
+ // And it is loading
734
+ a1.loading = true;
735
+ // Then its fields are read-only
736
+ const fields = [a1, a1.firstName, a1.books, a1.books.rows[0].title, a1.books.rows[0].classification];
737
+ fields.forEach((f) => expect(f.loading).toBeTruthy());
738
+ // And even if a specific field tries to _not_ be loading
739
+ a1.firstName.loading = false;
740
+ // Then the field-level value is ignored, and it's still treated as loading
741
+ expect(a1.firstName.loading).toBeTruthy();
742
+ });
743
+ it("has an field-level loading=true override object-level loading=false", () => {
744
+ // Given a top-level object
745
+ const a1 = createAuthorInputState({
746
+ firstName: "a1",
747
+ lastName: "aL1",
748
+ books: [{ title: "b1", classification: formStateDomain_1.dd100 }],
749
+ });
750
+ // And the top-level form is explicitly set to loading=false
751
+ a1.loading = false;
752
+ // Then its fields are not loading
753
+ const fields = [a1, a1.firstName, a1.books, a1.books.rows[0].title, a1.books.rows[0].classification];
754
+ fields.forEach((f) => expect(f.loading).toBeFalsy());
755
+ // But when one of the fields opts in to loading
756
+ a1.firstName.loading = true;
757
+ // Then the field-level loading is respected
758
+ expect(a1.firstName.loading).toBeTruthy();
759
+ });
725
760
  it("canSave returns dirty and touches", () => {
726
- const a1 = formState_1.createObjectState({
727
- firstName: { type: "value", rules: [formState_1.required] },
761
+ const a1 = objectField_1.createObjectState({
762
+ firstName: { type: "value", rules: [rules_1.required] },
728
763
  }, {});
729
764
  expect(a1.firstName.touched).toBeFalsy();
730
765
  expect(a1.canSave()).toBeFalsy();
731
766
  expect(a1.firstName.touched).toBeTruthy();
732
767
  });
733
768
  it("uses toJSON if available for dirty checks", () => {
734
- const a1 = formState_1.createObjectState({
769
+ const a1 = objectField_1.createObjectState({
735
770
  birthday: { type: "value" },
736
771
  }, { birthday: new formStateDomain_1.DateOnly(formStateDomain_1.jan1) });
737
772
  a1.birthday.set(new formStateDomain_1.DateOnly(formStateDomain_1.jan1));
@@ -746,10 +781,10 @@ describe("formState", () => {
746
781
  expect(a.valid).toBeTruthy();
747
782
  });
748
783
  it("can validate a list input", () => {
749
- const a = formState_1.createObjectState({
784
+ const a = objectField_1.createObjectState({
750
785
  books: {
751
786
  type: "list",
752
- config: { title: { type: "value", rules: [formState_1.required] } },
787
+ config: { title: { type: "value", rules: [rules_1.required] } },
753
788
  rules: [({ value }) => (value.find((b) => b.title.value === "t1") ? "Cannot have t1" : undefined)],
754
789
  },
755
790
  }, {});
@@ -763,11 +798,11 @@ describe("formState", () => {
763
798
  expect(lastErrors).toEqual("");
764
799
  });
765
800
  it("can ignore a deleted list entry", () => {
766
- const a = formState_1.createObjectState({
801
+ const a = objectField_1.createObjectState({
767
802
  books: {
768
803
  type: "list",
769
804
  config: {
770
- title: { type: "value", rules: [formState_1.required] },
805
+ title: { type: "value", rules: [rules_1.required] },
771
806
  delete: { type: "value", isDeleteKey: true },
772
807
  },
773
808
  },
@@ -782,11 +817,11 @@ describe("formState", () => {
782
817
  expect(a.books.valid).toBeTruthy();
783
818
  });
784
819
  it("can treat a list entry as read-only", () => {
785
- const a = formState_1.createObjectState({
820
+ const a = objectField_1.createObjectState({
786
821
  books: {
787
822
  type: "list",
788
823
  config: {
789
- title: { type: "value", rules: [formState_1.required] },
824
+ title: { type: "value", rules: [rules_1.required] },
790
825
  isPublished: { type: "value", isReadOnlyKey: true },
791
826
  },
792
827
  },
@@ -850,7 +885,7 @@ describe("formState", () => {
850
885
  expect(config).toBeDefined();
851
886
  });
852
887
  it("can reset observable objects with computeds", () => {
853
- const formState = formState_1.createObjectState({
888
+ const formState = objectField_1.createObjectState({
854
889
  firstName: { type: "value" },
855
890
  lastName: { type: "value" },
856
891
  fullName: { type: "value", computed: true },
@@ -863,7 +898,7 @@ describe("formState", () => {
863
898
  expect(formState.firstName.value).toEqual("first");
864
899
  });
865
900
  it("can set computeds that have setters", () => {
866
- const formState = formState_1.createObjectState({
901
+ const formState = objectField_1.createObjectState({
867
902
  firstName: { type: "value" },
868
903
  lastName: { type: "value" },
869
904
  fullName: { type: "value", computed: true },
@@ -876,7 +911,7 @@ describe("formState", () => {
876
911
  expect(formState.firstName.value).toEqual("first");
877
912
  });
878
913
  it("can mark fields as read only", () => {
879
- const formState = formState_1.createObjectState({
914
+ const formState = objectField_1.createObjectState({
880
915
  firstName: { type: "value" },
881
916
  lastName: { type: "value" },
882
917
  fullName: { type: "value", readOnly: true },
@@ -889,7 +924,7 @@ describe("formState", () => {
889
924
  expect(formState.firstName.value).toEqual("first");
890
925
  });
891
926
  it("lets rules validate against other fields", () => {
892
- const formState = formState_1.createObjectState({
927
+ const formState = objectField_1.createObjectState({
893
928
  firstName: { type: "value", rules: [] },
894
929
  lastName: {
895
930
  type: "value",
@@ -908,7 +943,7 @@ describe("formState", () => {
908
943
  });
909
944
  it("can return only changed primitive fields", () => {
910
945
  // Given an author
911
- const formState = formState_1.createObjectState(authorWithBooksConfig, {
946
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, {
912
947
  id: "a:1",
913
948
  firstName: "f",
914
949
  lastName: "l",
@@ -926,7 +961,7 @@ describe("formState", () => {
926
961
  });
927
962
  it("can return only changed object fields", () => {
928
963
  // Given an author with an address object
929
- const formState = formState_1.createObjectState(authorWithAddressConfig, {
964
+ const formState = objectField_1.createObjectState(authorWithAddressConfig, {
930
965
  id: "a:1",
931
966
  firstName: "f",
932
967
  address: { city: "c1", street: "s1" },
@@ -943,7 +978,7 @@ describe("formState", () => {
943
978
  });
944
979
  it("changedValue skips empty nested fields", () => {
945
980
  // Given a new author with an address object
946
- const formState = formState_1.createObjectState(authorWithAddressConfig, {
981
+ const formState = objectField_1.createObjectState(authorWithAddressConfig, {
947
982
  firstName: "f",
948
983
  // And nothing is in the address
949
984
  address: {},
@@ -954,7 +989,7 @@ describe("formState", () => {
954
989
  });
955
990
  it("changedValue skips effectively empty nested fields", () => {
956
991
  // Given a new author with an address object
957
- const formState = formState_1.createObjectState(authorWithAddressConfig, {
992
+ const formState = objectField_1.createObjectState(authorWithAddressConfig, {
958
993
  firstName: "f",
959
994
  // And only undefined keys are in the address
960
995
  address: { city: undefined },
@@ -965,7 +1000,7 @@ describe("formState", () => {
965
1000
  });
966
1001
  it("can return only changed list fields", () => {
967
1002
  // Given an author with some books
968
- const formState = formState_1.createObjectState(authorWithBooksConfig, {
1003
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, {
969
1004
  id: "a:1",
970
1005
  firstName: "f",
971
1006
  books: [
@@ -986,7 +1021,7 @@ describe("formState", () => {
986
1021
  });
987
1022
  it("can return only changed but incremental list fields", () => {
988
1023
  // Given an author
989
- const formState = formState_1.createObjectState({
1024
+ const formState = objectField_1.createObjectState({
990
1025
  id: { type: "value" },
991
1026
  // And the books collection is marked as incremental (i.e. line items)
992
1027
  books: {
@@ -994,7 +1029,7 @@ describe("formState", () => {
994
1029
  update: "incremental",
995
1030
  config: {
996
1031
  id: { type: "value" },
997
- title: { type: "value", rules: [formState_1.required] },
1032
+ title: { type: "value", rules: [rules_1.required] },
998
1033
  },
999
1034
  },
1000
1035
  }, {
@@ -1017,14 +1052,14 @@ describe("formState", () => {
1017
1052
  });
1018
1053
  it("uses the op key as a hint to use incremental list behavior", () => {
1019
1054
  // Given an author
1020
- const formState = formState_1.createObjectState({
1055
+ const formState = objectField_1.createObjectState({
1021
1056
  id: { type: "value" },
1022
1057
  // And the books collection is not explicitly marked as incremental
1023
1058
  books: {
1024
1059
  type: "list",
1025
1060
  config: {
1026
1061
  id: { type: "value" },
1027
- title: { type: "value", rules: [formState_1.required] },
1062
+ title: { type: "value", rules: [rules_1.required] },
1028
1063
  // But it does have an op key
1029
1064
  op: { type: "value" },
1030
1065
  },
@@ -1050,14 +1085,14 @@ describe("formState", () => {
1050
1085
  });
1051
1086
  it("defaults the incremental op key to included when not set", () => {
1052
1087
  // Given an author
1053
- const formState = formState_1.createObjectState({
1088
+ const formState = objectField_1.createObjectState({
1054
1089
  id: { type: "value" },
1055
1090
  // And the books collection is not explicitly marked as incremental
1056
1091
  books: {
1057
1092
  type: "list",
1058
1093
  config: {
1059
1094
  id: { type: "value" },
1060
- title: { type: "value", rules: [formState_1.required] },
1095
+ title: { type: "value", rules: [rules_1.required] },
1061
1096
  // But it does have an op key
1062
1097
  op: { type: "value" },
1063
1098
  },
@@ -1091,7 +1126,7 @@ describe("formState", () => {
1091
1126
  });
1092
1127
  });
1093
1128
  it("can observe value changes", () => {
1094
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f" });
1129
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f" });
1095
1130
  let ticks = 0;
1096
1131
  mobx_1.reaction(() => formState.value, () => ticks++, { equals: () => false });
1097
1132
  expect(ticks).toEqual(0);
@@ -1101,7 +1136,7 @@ describe("formState", () => {
1101
1136
  expect(ticks).toEqual(1);
1102
1137
  });
1103
1138
  it("does not override a focused, changed field", () => {
1104
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1139
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1105
1140
  // Given first name is focused
1106
1141
  formState.firstName.focus();
1107
1142
  // And they have wip edits
@@ -1116,7 +1151,7 @@ describe("formState", () => {
1116
1151
  expect(formState.firstName.value).toEqual("fff");
1117
1152
  });
1118
1153
  it("does update a focused, unchanged field", () => {
1119
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1154
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1120
1155
  // Given first name is focused, but has not changed
1121
1156
  formState.firstName.focus();
1122
1157
  // When a mutation result sets both firstName and lastName
@@ -1125,7 +1160,7 @@ describe("formState", () => {
1125
1160
  expect(formState.firstName.value).toEqual("f2");
1126
1161
  });
1127
1162
  it("trims string values on blur", () => {
1128
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1163
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1129
1164
  // Given the user is typing with spaces
1130
1165
  formState.firstName.focus();
1131
1166
  formState.firstName.set("f ");
@@ -1137,7 +1172,7 @@ describe("formState", () => {
1137
1172
  expect(formState.firstName.value).toEqual("f");
1138
1173
  });
1139
1174
  it("trims string values on maybeAutoSave", () => {
1140
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1175
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1141
1176
  // Given the user is typing with spaces
1142
1177
  formState.firstName.focus();
1143
1178
  formState.firstName.set("f ");
@@ -1151,7 +1186,7 @@ describe("formState", () => {
1151
1186
  expect(formState.firstName.touched).toEqual(true);
1152
1187
  });
1153
1188
  it("trims empty values to undefined on blur", () => {
1154
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1189
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1155
1190
  // Given the user is typing with only spaces
1156
1191
  formState.firstName.focus();
1157
1192
  formState.firstName.set(" ");
@@ -1169,7 +1204,7 @@ describe("formState", () => {
1169
1204
  });
1170
1205
  it("can work with both required inputs and optional fields", () => {
1171
1206
  // And we drop it in a form.
1172
- const form = formState_1.createObjectState({
1207
+ const form = objectField_1.createObjectState({
1173
1208
  id: { type: "value" },
1174
1209
  firstName: { type: "value" },
1175
1210
  }, { id: "a:1" });
@@ -1190,7 +1225,7 @@ describe("formState", () => {
1190
1225
  });
1191
1226
  it("can have child object states passed in as field states", () => {
1192
1227
  // Given an author
1193
- const a = formState_1.createObjectState({
1228
+ const a = objectField_1.createObjectState({
1194
1229
  id: { type: "value" },
1195
1230
  // And address is modeled as a value object
1196
1231
  address: { type: "value" },
@@ -1207,7 +1242,7 @@ describe("formState", () => {
1207
1242
  });
1208
1243
  it("provides isNewEntity", () => {
1209
1244
  // Given an author without an id
1210
- const formState = formState_1.createObjectState(authorWithAddressConfig, {
1245
+ const formState = objectField_1.createObjectState(authorWithAddressConfig, {
1211
1246
  firstName: "f",
1212
1247
  address: { city: "c1", street: "s1" },
1213
1248
  });
@@ -1222,7 +1257,7 @@ describe("formState", () => {
1222
1257
  });
1223
1258
  it("isNewEntity is false if there is no id field", () => {
1224
1259
  // Given an author without an id field defined
1225
- const formState = formState_1.createObjectState({
1260
+ const formState = objectField_1.createObjectState({
1226
1261
  firstName: { type: "value" },
1227
1262
  address: { type: "object", config: { street: { type: "value" } } },
1228
1263
  }, {});
@@ -1232,7 +1267,7 @@ describe("formState", () => {
1232
1267
  });
1233
1268
  it("provides isNewEntity for lists", () => {
1234
1269
  // Given an author & books without an id
1235
- const formState = formState_1.createObjectState(authorWithBooksConfig, {
1270
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, {
1236
1271
  books: [{ title: "t1" }, { title: "t2" }],
1237
1272
  });
1238
1273
  // Then both books know they are new
@@ -1300,13 +1335,13 @@ const authorWithBooksConfig = {
1300
1335
  type: "list",
1301
1336
  config: {
1302
1337
  id: { type: "value" },
1303
- title: { type: "value", rules: [formState_1.required] },
1338
+ title: { type: "value", rules: [rules_1.required] },
1304
1339
  classification: { type: "value" },
1305
1340
  },
1306
1341
  },
1307
1342
  };
1308
1343
  function createAuthorInputState(input, maybeAutoSave) {
1309
- return formState_1.createObjectState(authorWithBooksConfig, input, { maybeAutoSave });
1344
+ return objectField_1.createObjectState(authorWithBooksConfig, input, { maybeAutoSave });
1310
1345
  }
1311
1346
  const authorWithAddressConfig = {
1312
1347
  id: { type: "value" },
@@ -1315,7 +1350,7 @@ const authorWithAddressConfig = {
1315
1350
  address: {
1316
1351
  type: "object",
1317
1352
  config: {
1318
- street: { type: "value", rules: [formState_1.required] },
1353
+ street: { type: "value", rules: [rules_1.required] },
1319
1354
  city: { type: "value" },
1320
1355
  },
1321
1356
  },
@@ -1328,7 +1363,7 @@ const authorWithAddressAndBooksConfig = {
1328
1363
  type: "object",
1329
1364
  config: {
1330
1365
  id: { type: "value" },
1331
- street: { type: "value", rules: [formState_1.required] },
1366
+ street: { type: "value", rules: [rules_1.required] },
1332
1367
  city: { type: "value" },
1333
1368
  },
1334
1369
  },
@@ -1336,11 +1371,11 @@ const authorWithAddressAndBooksConfig = {
1336
1371
  type: "list",
1337
1372
  config: {
1338
1373
  id: { type: "value" },
1339
- title: { type: "value", rules: [formState_1.required] },
1374
+ title: { type: "value", rules: [rules_1.required] },
1340
1375
  classification: { type: "value" },
1341
1376
  },
1342
1377
  },
1343
1378
  };
1344
1379
  function createAuthorWithAddressInputState(input) {
1345
- return formState_1.createObjectState(authorWithAddressConfig, input);
1380
+ return objectField_1.createObjectState(authorWithAddressConfig, input);
1346
1381
  }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,7 @@
1
- export { createObjectState, FieldState, ListFieldState, ObjectConfig, ObjectState, required, Rule, } from "./formState";
1
+ export { ObjectConfig } from "./config";
2
+ export { ListFieldState } from "./fields/listField";
3
+ export { createObjectState, ObjectState } from "./fields/objectField";
4
+ export { FieldState } from "./fields/valueField";
5
+ export { required, Rule } from "./rules";
2
6
  export { useFormState } from "./useFormState";
3
7
  export { useFormStates } from "./useFormStates";
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useFormStates = exports.useFormState = exports.required = exports.createObjectState = void 0;
4
- var formState_1 = require("./formState");
5
- Object.defineProperty(exports, "createObjectState", { enumerable: true, get: function () { return formState_1.createObjectState; } });
6
- Object.defineProperty(exports, "required", { enumerable: true, get: function () { return formState_1.required; } });
4
+ var objectField_1 = require("./fields/objectField");
5
+ Object.defineProperty(exports, "createObjectState", { enumerable: true, get: function () { return objectField_1.createObjectState; } });
6
+ var rules_1 = require("./rules");
7
+ Object.defineProperty(exports, "required", { enumerable: true, get: function () { return rules_1.required; } });
7
8
  var useFormState_1 = require("./useFormState");
8
9
  Object.defineProperty(exports, "useFormState", { enumerable: true, get: function () { return useFormState_1.useFormState; } });
9
10
  var useFormStates_1 = require("./useFormStates");
@@ -0,0 +1,14 @@
1
+ import { ObjectState } from "./fields/objectField";
2
+ declare type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
3
+ /** A validation rule, given the value and name, return the error string if valid, or undefined if valid. */
4
+ export declare type Rule<T, V> = (opts: {
5
+ value: V;
6
+ key: string;
7
+ originalValue: V;
8
+ object: IfAny<T, any, ObjectState<T>>;
9
+ }) => string | undefined;
10
+ /** A rule that validates `value` is not `undefined`, `null`, or empty string. */
11
+ export declare const required: <V>({ value: v }: {
12
+ value: V;
13
+ }) => string | undefined;
14
+ export {};
package/dist/rules.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.required = void 0;
4
+ const mobx_1 = require("mobx");
5
+ /** A rule that validates `value` is not `undefined`, `null`, or empty string. */
6
+ // We pre-emptively make this a mobx action so that it's identity doesn't change when proxied
7
+ // and breaks our ability to do `rules.some(r => r === required)`.
8
+ exports.required = mobx_1.action(({ value: v }) => {
9
+ const isEmptyString = typeof v === "string" ? v.trim() === "" : false;
10
+ return v !== undefined && v !== null && !isEmptyString ? undefined : "Required";
11
+ });
@@ -1,4 +1,20 @@
1
- import { ObjectConfig, ObjectState } from "./formState";
1
+ import { ObjectConfig } from "./config";
2
+ import { ObjectState } from "./fields/objectField";
3
+ export declare type Query<I> = {
4
+ data: I;
5
+ loading: boolean;
6
+ error?: any;
7
+ };
8
+ export declare type InputAndMap<T, I> = {
9
+ input: I;
10
+ map: (input: Exclude<I, null | undefined>) => T;
11
+ ifUndefined?: T;
12
+ };
13
+ export declare type QueryAndMap<T, I> = {
14
+ query: Query<I>;
15
+ map: (input: Exclude<I, null | undefined>) => T;
16
+ ifUndefined?: T;
17
+ };
2
18
  export declare type UseFormStateOpts<T, I> = {
3
19
  /** The form configuration, should be a module-level const or useMemo'd. */
4
20
  config: ObjectConfig<T>;
@@ -19,11 +35,7 @@ export declare type UseFormStateOpts<T, I> = {
19
35
  * only call `init.map` if it's set, otherwise we'll use `init.ifDefined` or `{}`, saving you
20
36
  * from having to null check within your `init.map` function.
21
37
  */
22
- init?: T | {
23
- input: I;
24
- map: (input: Exclude<I, null | undefined>) => T;
25
- ifUndefined?: T;
26
- };
38
+ init?: T | InputAndMap<T, I> | QueryAndMap<T, I>;
27
39
  /**
28
40
  * A hook to add custom, cross-field validation rules that can be difficult to setup directly in the config DSL.
29
41
  *
@@ -32,6 +44,13 @@ export declare type UseFormStateOpts<T, I> = {
32
44
  addRules?: (state: ObjectState<T>) => void;
33
45
  /** Whether the form should be read only, when changed it won't re-create the whole form. */
34
46
  readOnly?: boolean;
47
+ /**
48
+ * Whether the form is loading.
49
+ *
50
+ * Note that we also will infer loading from `init.input === undefined` or `init.query.loading`,
51
+ * so you only need to set this directly if you're not using either of those conventions.
52
+ */
53
+ loading?: boolean;
35
54
  /**
36
55
  * Fired when the form should auto-save, i.e. after a) blur and b) all fields are valid.
37
56
  *