@homebound/form-state 2.9.0 → 2.10.1

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.
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.newValueFieldState = void 0;
4
+ const is_plain_object_1 = require("is-plain-object");
5
+ const mobx_1 = require("mobx");
6
+ const rules_1 = require("../rules");
7
+ const utils_1 = require("../utils");
8
+ function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, isDeleteKey, isReadOnlyKey, computed, readOnly, maybeAutoSave) {
9
+ // keep a copy here for reference equality
10
+ const value = parentInstance[key];
11
+ let _originalValue = value === null ? undefined : is_plain_object_1.isPlainObject(value) ? mobx_1.toJS(value) : value;
12
+ // Because we read/write the value directly back into parentInstance[key],
13
+ // 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 });
16
+ const field = {
17
+ key,
18
+ touched: false,
19
+ /** Current readOnly value. */
20
+ _readOnly: readOnly || false,
21
+ _focused: false,
22
+ _isIdKey: isIdKey,
23
+ _isDeleteKey: isDeleteKey,
24
+ _isReadOnlyKey: isReadOnlyKey,
25
+ rules,
26
+ get value() {
27
+ const value = _tick.value > 0 ? parentInstance[key] : utils_1.fail();
28
+ // Re-create the `keepNull` logic on sets but for our initial read where our
29
+ // originalValue is null (empty) but we want to expose it as undefined for
30
+ // consistency of "empty-ness" to our UI components.
31
+ return value === null && utils_1.isEmpty(_originalValue) ? undefined : value;
32
+ },
33
+ set value(v) {
34
+ this.set(v);
35
+ },
36
+ get dirty() {
37
+ return !utils_1.areEqual(this.originalValue, this.value);
38
+ },
39
+ /** Returns whether this field is readOnly, although if our parent is readOnly then it trumps. */
40
+ get readOnly() {
41
+ return parentState().readOnly || this._readOnly;
42
+ },
43
+ /** Sets the field readOnly, but `v` will still be or-d with the parent's readOnly state. */
44
+ set readOnly(v) {
45
+ this._readOnly = v;
46
+ },
47
+ // For primitive fields, the changed value is just the value.
48
+ get changedValue() {
49
+ return this.value;
50
+ },
51
+ get valid() {
52
+ const opts = { value: this.value, key: key, originalValue: this.originalValue, object: parentState() };
53
+ return this.rules.every((r) => r(opts) === undefined);
54
+ },
55
+ get errors() {
56
+ const opts = { value: this.value, key: key, originalValue: this.originalValue, object: parentState() };
57
+ return this.rules.map((r) => r(opts)).filter(utils_1.isNotUndefined);
58
+ },
59
+ get required() {
60
+ return this.rules.some((rule) => rule === rules_1.required);
61
+ },
62
+ get isNewEntity() {
63
+ return parentState().isNewEntity;
64
+ },
65
+ focus() {
66
+ this._focused = true;
67
+ },
68
+ blur() {
69
+ this.maybeAutoSave();
70
+ this._focused = false;
71
+ },
72
+ maybeAutoSave() {
73
+ // Now that the user is done editing the field (note maybe w/o blurring, i.e. if they hit enter), we sneak in some trim logic
74
+ this.maybeTrim();
75
+ // touched is readonly, but we're allowed to change it
76
+ this.touched = true;
77
+ maybeAutoSave();
78
+ },
79
+ set(value, opts = {}) {
80
+ if (this.readOnly && !opts.resetting && !opts.refreshing) {
81
+ throw new Error(`${key} is currently readOnly`);
82
+ }
83
+ if (opts.refreshing && this.dirty && this.value !== value) {
84
+ // Ignore incoming values if we have changes (this.dirty) unless our latest change (this.value)
85
+ // matches the incoming value (value), b/c if it does we should accept it and reset originalValue
86
+ // so that we're not longer dirty.
87
+ return;
88
+ }
89
+ else if (computed && (opts.resetting || opts.refreshing)) {
90
+ // Computeds can't be either reset or refreshed
91
+ return;
92
+ }
93
+ // If the user has deleted/emptied a value that was originally set, keep it as `null`
94
+ // so that our partial update to the backend correctly unsets it.
95
+ const keepNull = !utils_1.isEmpty(this.originalValue) && utils_1.isEmpty(value);
96
+ // If a list of primitives was originally undefined, coerce `[]` to `undefined`
97
+ const coerceEmptyList = value && value instanceof Array && value.length === 0 && utils_1.isEmpty(this.originalValue);
98
+ const newValue = keepNull ? null : utils_1.isEmpty(value) || coerceEmptyList ? undefined : value;
99
+ // Set the value on our parent object
100
+ const changed = !utils_1.areEqual(newValue, this.value);
101
+ parentInstance[key] = newValue;
102
+ _tick.value++;
103
+ if (opts.refreshing) {
104
+ this.originalValue = newValue;
105
+ }
106
+ // If we're being set programmatically, i.e. we don't currently have focus,
107
+ // call blur to trigger any auto-saves.
108
+ if (!this._focused && !opts.refreshing && !opts.resetting && this.dirty && changed && opts.autoSave !== false) {
109
+ this.maybeAutoSave();
110
+ }
111
+ },
112
+ reset() {
113
+ if (!computed) {
114
+ this.set(this.originalValue, { resetting: true });
115
+ }
116
+ this.touched = false;
117
+ },
118
+ save() {
119
+ if (is_plain_object_1.isPlainObject(this.originalValue)) {
120
+ this.originalValue = mobx_1.toJS(this.value);
121
+ }
122
+ else {
123
+ this.originalValue = this.value;
124
+ }
125
+ this.touched = false;
126
+ },
127
+ get originalValue() {
128
+ // A dummy check to for reactivity around our non-proxy value
129
+ return _originalValueTick.value > -1 ? _originalValue : _originalValue;
130
+ },
131
+ set originalValue(v) {
132
+ _originalValue = v;
133
+ _originalValueTick.value++;
134
+ },
135
+ maybeTrim() {
136
+ if (typeof this.value === "string") {
137
+ let newValue = this.value.trim();
138
+ if (newValue === "") {
139
+ newValue = undefined;
140
+ }
141
+ if (newValue !== this.value) {
142
+ this.set(newValue);
143
+ }
144
+ }
145
+ },
146
+ };
147
+ return field;
148
+ }
149
+ exports.newValueFieldState = newValueFieldState;
@@ -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,20 +683,56 @@ 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
  });
690
+ it("has an object-level readOnly=true override field-level readOnly=false", () => {
691
+ // Given a top-level object
692
+ const a1 = createAuthorInputState({
693
+ firstName: "a1",
694
+ lastName: "aL1",
695
+ books: [{ title: "b1", classification: formStateDomain_1.dd100 }],
696
+ });
697
+ // And it is read-only
698
+ a1.readOnly = true;
699
+ // Then it's fields are read-only
700
+ const fields = [a1, a1.firstName, a1.books, a1.books.rows[0].title, a1.books.rows[0].classification];
701
+ fields.forEach((f) => expect(f.readOnly).toBeTruthy());
702
+ // And even if a specific field tries to _not_ be read-only,
703
+ // i.e. due to a more granular business rule that happens to
704
+ // be allowed right now
705
+ a1.firstName.readOnly = false;
706
+ // Then the field-level rule is ignored, and it's still treated as read-only
707
+ expect(a1.firstName.readOnly).toBeTruthy();
708
+ });
709
+ it("has an field-level readOnly=true override object-level readOnly=false", () => {
710
+ // Given a top-level object
711
+ const a1 = createAuthorInputState({
712
+ firstName: "a1",
713
+ lastName: "aL1",
714
+ books: [{ title: "b1", classification: formStateDomain_1.dd100 }],
715
+ });
716
+ // And the top-level form is explicitly set to read-only=false
717
+ a1.readOnly = false;
718
+ // Then it's fields are not read-only
719
+ const fields = [a1, a1.firstName, a1.books, a1.books.rows[0].title, a1.books.rows[0].classification];
720
+ fields.forEach((f) => expect(f.readOnly).toBeFalsy());
721
+ // But when one of the fields opts in to readOnly
722
+ a1.firstName.readOnly = true;
723
+ // Then the field-level rule is respected
724
+ expect(a1.firstName.readOnly).toBeTruthy();
725
+ });
689
726
  it("canSave returns dirty and touches", () => {
690
- const a1 = formState_1.createObjectState({
691
- firstName: { type: "value", rules: [formState_1.required] },
727
+ const a1 = objectField_1.createObjectState({
728
+ firstName: { type: "value", rules: [rules_1.required] },
692
729
  }, {});
693
730
  expect(a1.firstName.touched).toBeFalsy();
694
731
  expect(a1.canSave()).toBeFalsy();
695
732
  expect(a1.firstName.touched).toBeTruthy();
696
733
  });
697
734
  it("uses toJSON if available for dirty checks", () => {
698
- const a1 = formState_1.createObjectState({
735
+ const a1 = objectField_1.createObjectState({
699
736
  birthday: { type: "value" },
700
737
  }, { birthday: new formStateDomain_1.DateOnly(formStateDomain_1.jan1) });
701
738
  a1.birthday.set(new formStateDomain_1.DateOnly(formStateDomain_1.jan1));
@@ -710,10 +747,10 @@ describe("formState", () => {
710
747
  expect(a.valid).toBeTruthy();
711
748
  });
712
749
  it("can validate a list input", () => {
713
- const a = formState_1.createObjectState({
750
+ const a = objectField_1.createObjectState({
714
751
  books: {
715
752
  type: "list",
716
- config: { title: { type: "value", rules: [formState_1.required] } },
753
+ config: { title: { type: "value", rules: [rules_1.required] } },
717
754
  rules: [({ value }) => (value.find((b) => b.title.value === "t1") ? "Cannot have t1" : undefined)],
718
755
  },
719
756
  }, {});
@@ -727,11 +764,11 @@ describe("formState", () => {
727
764
  expect(lastErrors).toEqual("");
728
765
  });
729
766
  it("can ignore a deleted list entry", () => {
730
- const a = formState_1.createObjectState({
767
+ const a = objectField_1.createObjectState({
731
768
  books: {
732
769
  type: "list",
733
770
  config: {
734
- title: { type: "value", rules: [formState_1.required] },
771
+ title: { type: "value", rules: [rules_1.required] },
735
772
  delete: { type: "value", isDeleteKey: true },
736
773
  },
737
774
  },
@@ -746,11 +783,11 @@ describe("formState", () => {
746
783
  expect(a.books.valid).toBeTruthy();
747
784
  });
748
785
  it("can treat a list entry as read-only", () => {
749
- const a = formState_1.createObjectState({
786
+ const a = objectField_1.createObjectState({
750
787
  books: {
751
788
  type: "list",
752
789
  config: {
753
- title: { type: "value", rules: [formState_1.required] },
790
+ title: { type: "value", rules: [rules_1.required] },
754
791
  isPublished: { type: "value", isReadOnlyKey: true },
755
792
  },
756
793
  },
@@ -814,7 +851,7 @@ describe("formState", () => {
814
851
  expect(config).toBeDefined();
815
852
  });
816
853
  it("can reset observable objects with computeds", () => {
817
- const formState = formState_1.createObjectState({
854
+ const formState = objectField_1.createObjectState({
818
855
  firstName: { type: "value" },
819
856
  lastName: { type: "value" },
820
857
  fullName: { type: "value", computed: true },
@@ -827,7 +864,7 @@ describe("formState", () => {
827
864
  expect(formState.firstName.value).toEqual("first");
828
865
  });
829
866
  it("can set computeds that have setters", () => {
830
- const formState = formState_1.createObjectState({
867
+ const formState = objectField_1.createObjectState({
831
868
  firstName: { type: "value" },
832
869
  lastName: { type: "value" },
833
870
  fullName: { type: "value", computed: true },
@@ -840,7 +877,7 @@ describe("formState", () => {
840
877
  expect(formState.firstName.value).toEqual("first");
841
878
  });
842
879
  it("can mark fields as read only", () => {
843
- const formState = formState_1.createObjectState({
880
+ const formState = objectField_1.createObjectState({
844
881
  firstName: { type: "value" },
845
882
  lastName: { type: "value" },
846
883
  fullName: { type: "value", readOnly: true },
@@ -853,7 +890,7 @@ describe("formState", () => {
853
890
  expect(formState.firstName.value).toEqual("first");
854
891
  });
855
892
  it("lets rules validate against other fields", () => {
856
- const formState = formState_1.createObjectState({
893
+ const formState = objectField_1.createObjectState({
857
894
  firstName: { type: "value", rules: [] },
858
895
  lastName: {
859
896
  type: "value",
@@ -872,7 +909,7 @@ describe("formState", () => {
872
909
  });
873
910
  it("can return only changed primitive fields", () => {
874
911
  // Given an author
875
- const formState = formState_1.createObjectState(authorWithBooksConfig, {
912
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, {
876
913
  id: "a:1",
877
914
  firstName: "f",
878
915
  lastName: "l",
@@ -890,7 +927,7 @@ describe("formState", () => {
890
927
  });
891
928
  it("can return only changed object fields", () => {
892
929
  // Given an author with an address object
893
- const formState = formState_1.createObjectState(authorWithAddressConfig, {
930
+ const formState = objectField_1.createObjectState(authorWithAddressConfig, {
894
931
  id: "a:1",
895
932
  firstName: "f",
896
933
  address: { city: "c1", street: "s1" },
@@ -907,7 +944,7 @@ describe("formState", () => {
907
944
  });
908
945
  it("changedValue skips empty nested fields", () => {
909
946
  // Given a new author with an address object
910
- const formState = formState_1.createObjectState(authorWithAddressConfig, {
947
+ const formState = objectField_1.createObjectState(authorWithAddressConfig, {
911
948
  firstName: "f",
912
949
  // And nothing is in the address
913
950
  address: {},
@@ -918,7 +955,7 @@ describe("formState", () => {
918
955
  });
919
956
  it("changedValue skips effectively empty nested fields", () => {
920
957
  // Given a new author with an address object
921
- const formState = formState_1.createObjectState(authorWithAddressConfig, {
958
+ const formState = objectField_1.createObjectState(authorWithAddressConfig, {
922
959
  firstName: "f",
923
960
  // And only undefined keys are in the address
924
961
  address: { city: undefined },
@@ -929,7 +966,7 @@ describe("formState", () => {
929
966
  });
930
967
  it("can return only changed list fields", () => {
931
968
  // Given an author with some books
932
- const formState = formState_1.createObjectState(authorWithBooksConfig, {
969
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, {
933
970
  id: "a:1",
934
971
  firstName: "f",
935
972
  books: [
@@ -950,7 +987,7 @@ describe("formState", () => {
950
987
  });
951
988
  it("can return only changed but incremental list fields", () => {
952
989
  // Given an author
953
- const formState = formState_1.createObjectState({
990
+ const formState = objectField_1.createObjectState({
954
991
  id: { type: "value" },
955
992
  // And the books collection is marked as incremental (i.e. line items)
956
993
  books: {
@@ -958,7 +995,7 @@ describe("formState", () => {
958
995
  update: "incremental",
959
996
  config: {
960
997
  id: { type: "value" },
961
- title: { type: "value", rules: [formState_1.required] },
998
+ title: { type: "value", rules: [rules_1.required] },
962
999
  },
963
1000
  },
964
1001
  }, {
@@ -981,14 +1018,14 @@ describe("formState", () => {
981
1018
  });
982
1019
  it("uses the op key as a hint to use incremental list behavior", () => {
983
1020
  // Given an author
984
- const formState = formState_1.createObjectState({
1021
+ const formState = objectField_1.createObjectState({
985
1022
  id: { type: "value" },
986
1023
  // And the books collection is not explicitly marked as incremental
987
1024
  books: {
988
1025
  type: "list",
989
1026
  config: {
990
1027
  id: { type: "value" },
991
- title: { type: "value", rules: [formState_1.required] },
1028
+ title: { type: "value", rules: [rules_1.required] },
992
1029
  // But it does have an op key
993
1030
  op: { type: "value" },
994
1031
  },
@@ -1014,14 +1051,14 @@ describe("formState", () => {
1014
1051
  });
1015
1052
  it("defaults the incremental op key to included when not set", () => {
1016
1053
  // Given an author
1017
- const formState = formState_1.createObjectState({
1054
+ const formState = objectField_1.createObjectState({
1018
1055
  id: { type: "value" },
1019
1056
  // And the books collection is not explicitly marked as incremental
1020
1057
  books: {
1021
1058
  type: "list",
1022
1059
  config: {
1023
1060
  id: { type: "value" },
1024
- title: { type: "value", rules: [formState_1.required] },
1061
+ title: { type: "value", rules: [rules_1.required] },
1025
1062
  // But it does have an op key
1026
1063
  op: { type: "value" },
1027
1064
  },
@@ -1055,7 +1092,7 @@ describe("formState", () => {
1055
1092
  });
1056
1093
  });
1057
1094
  it("can observe value changes", () => {
1058
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f" });
1095
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f" });
1059
1096
  let ticks = 0;
1060
1097
  mobx_1.reaction(() => formState.value, () => ticks++, { equals: () => false });
1061
1098
  expect(ticks).toEqual(0);
@@ -1065,7 +1102,7 @@ describe("formState", () => {
1065
1102
  expect(ticks).toEqual(1);
1066
1103
  });
1067
1104
  it("does not override a focused, changed field", () => {
1068
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1105
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1069
1106
  // Given first name is focused
1070
1107
  formState.firstName.focus();
1071
1108
  // And they have wip edits
@@ -1080,7 +1117,7 @@ describe("formState", () => {
1080
1117
  expect(formState.firstName.value).toEqual("fff");
1081
1118
  });
1082
1119
  it("does update a focused, unchanged field", () => {
1083
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1120
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1084
1121
  // Given first name is focused, but has not changed
1085
1122
  formState.firstName.focus();
1086
1123
  // When a mutation result sets both firstName and lastName
@@ -1089,7 +1126,7 @@ describe("formState", () => {
1089
1126
  expect(formState.firstName.value).toEqual("f2");
1090
1127
  });
1091
1128
  it("trims string values on blur", () => {
1092
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1129
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1093
1130
  // Given the user is typing with spaces
1094
1131
  formState.firstName.focus();
1095
1132
  formState.firstName.set("f ");
@@ -1101,7 +1138,7 @@ describe("formState", () => {
1101
1138
  expect(formState.firstName.value).toEqual("f");
1102
1139
  });
1103
1140
  it("trims string values on maybeAutoSave", () => {
1104
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1141
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1105
1142
  // Given the user is typing with spaces
1106
1143
  formState.firstName.focus();
1107
1144
  formState.firstName.set("f ");
@@ -1115,7 +1152,7 @@ describe("formState", () => {
1115
1152
  expect(formState.firstName.touched).toEqual(true);
1116
1153
  });
1117
1154
  it("trims empty values to undefined on blur", () => {
1118
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1155
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1119
1156
  // Given the user is typing with only spaces
1120
1157
  formState.firstName.focus();
1121
1158
  formState.firstName.set(" ");
@@ -1133,7 +1170,7 @@ describe("formState", () => {
1133
1170
  });
1134
1171
  it("can work with both required inputs and optional fields", () => {
1135
1172
  // And we drop it in a form.
1136
- const form = formState_1.createObjectState({
1173
+ const form = objectField_1.createObjectState({
1137
1174
  id: { type: "value" },
1138
1175
  firstName: { type: "value" },
1139
1176
  }, { id: "a:1" });
@@ -1154,7 +1191,7 @@ describe("formState", () => {
1154
1191
  });
1155
1192
  it("can have child object states passed in as field states", () => {
1156
1193
  // Given an author
1157
- const a = formState_1.createObjectState({
1194
+ const a = objectField_1.createObjectState({
1158
1195
  id: { type: "value" },
1159
1196
  // And address is modeled as a value object
1160
1197
  address: { type: "value" },
@@ -1171,7 +1208,7 @@ describe("formState", () => {
1171
1208
  });
1172
1209
  it("provides isNewEntity", () => {
1173
1210
  // Given an author without an id
1174
- const formState = formState_1.createObjectState(authorWithAddressConfig, {
1211
+ const formState = objectField_1.createObjectState(authorWithAddressConfig, {
1175
1212
  firstName: "f",
1176
1213
  address: { city: "c1", street: "s1" },
1177
1214
  });
@@ -1186,7 +1223,7 @@ describe("formState", () => {
1186
1223
  });
1187
1224
  it("isNewEntity is false if there is no id field", () => {
1188
1225
  // Given an author without an id field defined
1189
- const formState = formState_1.createObjectState({
1226
+ const formState = objectField_1.createObjectState({
1190
1227
  firstName: { type: "value" },
1191
1228
  address: { type: "object", config: { street: { type: "value" } } },
1192
1229
  }, {});
@@ -1196,7 +1233,7 @@ describe("formState", () => {
1196
1233
  });
1197
1234
  it("provides isNewEntity for lists", () => {
1198
1235
  // Given an author & books without an id
1199
- const formState = formState_1.createObjectState(authorWithBooksConfig, {
1236
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, {
1200
1237
  books: [{ title: "t1" }, { title: "t2" }],
1201
1238
  });
1202
1239
  // Then both books know they are new
@@ -1264,13 +1301,13 @@ const authorWithBooksConfig = {
1264
1301
  type: "list",
1265
1302
  config: {
1266
1303
  id: { type: "value" },
1267
- title: { type: "value", rules: [formState_1.required] },
1304
+ title: { type: "value", rules: [rules_1.required] },
1268
1305
  classification: { type: "value" },
1269
1306
  },
1270
1307
  },
1271
1308
  };
1272
1309
  function createAuthorInputState(input, maybeAutoSave) {
1273
- return formState_1.createObjectState(authorWithBooksConfig, input, { maybeAutoSave });
1310
+ return objectField_1.createObjectState(authorWithBooksConfig, input, { maybeAutoSave });
1274
1311
  }
1275
1312
  const authorWithAddressConfig = {
1276
1313
  id: { type: "value" },
@@ -1279,7 +1316,7 @@ const authorWithAddressConfig = {
1279
1316
  address: {
1280
1317
  type: "object",
1281
1318
  config: {
1282
- street: { type: "value", rules: [formState_1.required] },
1319
+ street: { type: "value", rules: [rules_1.required] },
1283
1320
  city: { type: "value" },
1284
1321
  },
1285
1322
  },
@@ -1292,7 +1329,7 @@ const authorWithAddressAndBooksConfig = {
1292
1329
  type: "object",
1293
1330
  config: {
1294
1331
  id: { type: "value" },
1295
- street: { type: "value", rules: [formState_1.required] },
1332
+ street: { type: "value", rules: [rules_1.required] },
1296
1333
  city: { type: "value" },
1297
1334
  },
1298
1335
  },
@@ -1300,11 +1337,11 @@ const authorWithAddressAndBooksConfig = {
1300
1337
  type: "list",
1301
1338
  config: {
1302
1339
  id: { type: "value" },
1303
- title: { type: "value", rules: [formState_1.required] },
1340
+ title: { type: "value", rules: [rules_1.required] },
1304
1341
  classification: { type: "value" },
1305
1342
  },
1306
1343
  },
1307
1344
  };
1308
1345
  function createAuthorWithAddressInputState(input) {
1309
- return formState_1.createObjectState(authorWithAddressConfig, input);
1346
+ return objectField_1.createObjectState(authorWithAddressConfig, input);
1310
1347
  }
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";