@homebound/form-state 2.10.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,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
  });
@@ -723,15 +724,15 @@ describe("formState", () => {
723
724
  expect(a1.firstName.readOnly).toBeTruthy();
724
725
  });
725
726
  it("canSave returns dirty and touches", () => {
726
- const a1 = formState_1.createObjectState({
727
- firstName: { type: "value", rules: [formState_1.required] },
727
+ const a1 = objectField_1.createObjectState({
728
+ firstName: { type: "value", rules: [rules_1.required] },
728
729
  }, {});
729
730
  expect(a1.firstName.touched).toBeFalsy();
730
731
  expect(a1.canSave()).toBeFalsy();
731
732
  expect(a1.firstName.touched).toBeTruthy();
732
733
  });
733
734
  it("uses toJSON if available for dirty checks", () => {
734
- const a1 = formState_1.createObjectState({
735
+ const a1 = objectField_1.createObjectState({
735
736
  birthday: { type: "value" },
736
737
  }, { birthday: new formStateDomain_1.DateOnly(formStateDomain_1.jan1) });
737
738
  a1.birthday.set(new formStateDomain_1.DateOnly(formStateDomain_1.jan1));
@@ -746,10 +747,10 @@ describe("formState", () => {
746
747
  expect(a.valid).toBeTruthy();
747
748
  });
748
749
  it("can validate a list input", () => {
749
- const a = formState_1.createObjectState({
750
+ const a = objectField_1.createObjectState({
750
751
  books: {
751
752
  type: "list",
752
- config: { title: { type: "value", rules: [formState_1.required] } },
753
+ config: { title: { type: "value", rules: [rules_1.required] } },
753
754
  rules: [({ value }) => (value.find((b) => b.title.value === "t1") ? "Cannot have t1" : undefined)],
754
755
  },
755
756
  }, {});
@@ -763,11 +764,11 @@ describe("formState", () => {
763
764
  expect(lastErrors).toEqual("");
764
765
  });
765
766
  it("can ignore a deleted list entry", () => {
766
- const a = formState_1.createObjectState({
767
+ const a = objectField_1.createObjectState({
767
768
  books: {
768
769
  type: "list",
769
770
  config: {
770
- title: { type: "value", rules: [formState_1.required] },
771
+ title: { type: "value", rules: [rules_1.required] },
771
772
  delete: { type: "value", isDeleteKey: true },
772
773
  },
773
774
  },
@@ -782,11 +783,11 @@ describe("formState", () => {
782
783
  expect(a.books.valid).toBeTruthy();
783
784
  });
784
785
  it("can treat a list entry as read-only", () => {
785
- const a = formState_1.createObjectState({
786
+ const a = objectField_1.createObjectState({
786
787
  books: {
787
788
  type: "list",
788
789
  config: {
789
- title: { type: "value", rules: [formState_1.required] },
790
+ title: { type: "value", rules: [rules_1.required] },
790
791
  isPublished: { type: "value", isReadOnlyKey: true },
791
792
  },
792
793
  },
@@ -850,7 +851,7 @@ describe("formState", () => {
850
851
  expect(config).toBeDefined();
851
852
  });
852
853
  it("can reset observable objects with computeds", () => {
853
- const formState = formState_1.createObjectState({
854
+ const formState = objectField_1.createObjectState({
854
855
  firstName: { type: "value" },
855
856
  lastName: { type: "value" },
856
857
  fullName: { type: "value", computed: true },
@@ -863,7 +864,7 @@ describe("formState", () => {
863
864
  expect(formState.firstName.value).toEqual("first");
864
865
  });
865
866
  it("can set computeds that have setters", () => {
866
- const formState = formState_1.createObjectState({
867
+ const formState = objectField_1.createObjectState({
867
868
  firstName: { type: "value" },
868
869
  lastName: { type: "value" },
869
870
  fullName: { type: "value", computed: true },
@@ -876,7 +877,7 @@ describe("formState", () => {
876
877
  expect(formState.firstName.value).toEqual("first");
877
878
  });
878
879
  it("can mark fields as read only", () => {
879
- const formState = formState_1.createObjectState({
880
+ const formState = objectField_1.createObjectState({
880
881
  firstName: { type: "value" },
881
882
  lastName: { type: "value" },
882
883
  fullName: { type: "value", readOnly: true },
@@ -889,7 +890,7 @@ describe("formState", () => {
889
890
  expect(formState.firstName.value).toEqual("first");
890
891
  });
891
892
  it("lets rules validate against other fields", () => {
892
- const formState = formState_1.createObjectState({
893
+ const formState = objectField_1.createObjectState({
893
894
  firstName: { type: "value", rules: [] },
894
895
  lastName: {
895
896
  type: "value",
@@ -908,7 +909,7 @@ describe("formState", () => {
908
909
  });
909
910
  it("can return only changed primitive fields", () => {
910
911
  // Given an author
911
- const formState = formState_1.createObjectState(authorWithBooksConfig, {
912
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, {
912
913
  id: "a:1",
913
914
  firstName: "f",
914
915
  lastName: "l",
@@ -926,7 +927,7 @@ describe("formState", () => {
926
927
  });
927
928
  it("can return only changed object fields", () => {
928
929
  // Given an author with an address object
929
- const formState = formState_1.createObjectState(authorWithAddressConfig, {
930
+ const formState = objectField_1.createObjectState(authorWithAddressConfig, {
930
931
  id: "a:1",
931
932
  firstName: "f",
932
933
  address: { city: "c1", street: "s1" },
@@ -943,7 +944,7 @@ describe("formState", () => {
943
944
  });
944
945
  it("changedValue skips empty nested fields", () => {
945
946
  // Given a new author with an address object
946
- const formState = formState_1.createObjectState(authorWithAddressConfig, {
947
+ const formState = objectField_1.createObjectState(authorWithAddressConfig, {
947
948
  firstName: "f",
948
949
  // And nothing is in the address
949
950
  address: {},
@@ -954,7 +955,7 @@ describe("formState", () => {
954
955
  });
955
956
  it("changedValue skips effectively empty nested fields", () => {
956
957
  // Given a new author with an address object
957
- const formState = formState_1.createObjectState(authorWithAddressConfig, {
958
+ const formState = objectField_1.createObjectState(authorWithAddressConfig, {
958
959
  firstName: "f",
959
960
  // And only undefined keys are in the address
960
961
  address: { city: undefined },
@@ -965,7 +966,7 @@ describe("formState", () => {
965
966
  });
966
967
  it("can return only changed list fields", () => {
967
968
  // Given an author with some books
968
- const formState = formState_1.createObjectState(authorWithBooksConfig, {
969
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, {
969
970
  id: "a:1",
970
971
  firstName: "f",
971
972
  books: [
@@ -986,7 +987,7 @@ describe("formState", () => {
986
987
  });
987
988
  it("can return only changed but incremental list fields", () => {
988
989
  // Given an author
989
- const formState = formState_1.createObjectState({
990
+ const formState = objectField_1.createObjectState({
990
991
  id: { type: "value" },
991
992
  // And the books collection is marked as incremental (i.e. line items)
992
993
  books: {
@@ -994,7 +995,7 @@ describe("formState", () => {
994
995
  update: "incremental",
995
996
  config: {
996
997
  id: { type: "value" },
997
- title: { type: "value", rules: [formState_1.required] },
998
+ title: { type: "value", rules: [rules_1.required] },
998
999
  },
999
1000
  },
1000
1001
  }, {
@@ -1017,14 +1018,14 @@ describe("formState", () => {
1017
1018
  });
1018
1019
  it("uses the op key as a hint to use incremental list behavior", () => {
1019
1020
  // Given an author
1020
- const formState = formState_1.createObjectState({
1021
+ const formState = objectField_1.createObjectState({
1021
1022
  id: { type: "value" },
1022
1023
  // And the books collection is not explicitly marked as incremental
1023
1024
  books: {
1024
1025
  type: "list",
1025
1026
  config: {
1026
1027
  id: { type: "value" },
1027
- title: { type: "value", rules: [formState_1.required] },
1028
+ title: { type: "value", rules: [rules_1.required] },
1028
1029
  // But it does have an op key
1029
1030
  op: { type: "value" },
1030
1031
  },
@@ -1050,14 +1051,14 @@ describe("formState", () => {
1050
1051
  });
1051
1052
  it("defaults the incremental op key to included when not set", () => {
1052
1053
  // Given an author
1053
- const formState = formState_1.createObjectState({
1054
+ const formState = objectField_1.createObjectState({
1054
1055
  id: { type: "value" },
1055
1056
  // And the books collection is not explicitly marked as incremental
1056
1057
  books: {
1057
1058
  type: "list",
1058
1059
  config: {
1059
1060
  id: { type: "value" },
1060
- title: { type: "value", rules: [formState_1.required] },
1061
+ title: { type: "value", rules: [rules_1.required] },
1061
1062
  // But it does have an op key
1062
1063
  op: { type: "value" },
1063
1064
  },
@@ -1091,7 +1092,7 @@ describe("formState", () => {
1091
1092
  });
1092
1093
  });
1093
1094
  it("can observe value changes", () => {
1094
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f" });
1095
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f" });
1095
1096
  let ticks = 0;
1096
1097
  mobx_1.reaction(() => formState.value, () => ticks++, { equals: () => false });
1097
1098
  expect(ticks).toEqual(0);
@@ -1101,7 +1102,7 @@ describe("formState", () => {
1101
1102
  expect(ticks).toEqual(1);
1102
1103
  });
1103
1104
  it("does not override a focused, changed field", () => {
1104
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1105
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1105
1106
  // Given first name is focused
1106
1107
  formState.firstName.focus();
1107
1108
  // And they have wip edits
@@ -1116,7 +1117,7 @@ describe("formState", () => {
1116
1117
  expect(formState.firstName.value).toEqual("fff");
1117
1118
  });
1118
1119
  it("does update a focused, unchanged field", () => {
1119
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1120
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1120
1121
  // Given first name is focused, but has not changed
1121
1122
  formState.firstName.focus();
1122
1123
  // When a mutation result sets both firstName and lastName
@@ -1125,7 +1126,7 @@ describe("formState", () => {
1125
1126
  expect(formState.firstName.value).toEqual("f2");
1126
1127
  });
1127
1128
  it("trims string values on blur", () => {
1128
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1129
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1129
1130
  // Given the user is typing with spaces
1130
1131
  formState.firstName.focus();
1131
1132
  formState.firstName.set("f ");
@@ -1137,7 +1138,7 @@ describe("formState", () => {
1137
1138
  expect(formState.firstName.value).toEqual("f");
1138
1139
  });
1139
1140
  it("trims string values on maybeAutoSave", () => {
1140
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1141
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1141
1142
  // Given the user is typing with spaces
1142
1143
  formState.firstName.focus();
1143
1144
  formState.firstName.set("f ");
@@ -1151,7 +1152,7 @@ describe("formState", () => {
1151
1152
  expect(formState.firstName.touched).toEqual(true);
1152
1153
  });
1153
1154
  it("trims empty values to undefined on blur", () => {
1154
- const formState = formState_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1155
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, { firstName: "f", lastName: "l" });
1155
1156
  // Given the user is typing with only spaces
1156
1157
  formState.firstName.focus();
1157
1158
  formState.firstName.set(" ");
@@ -1169,7 +1170,7 @@ describe("formState", () => {
1169
1170
  });
1170
1171
  it("can work with both required inputs and optional fields", () => {
1171
1172
  // And we drop it in a form.
1172
- const form = formState_1.createObjectState({
1173
+ const form = objectField_1.createObjectState({
1173
1174
  id: { type: "value" },
1174
1175
  firstName: { type: "value" },
1175
1176
  }, { id: "a:1" });
@@ -1190,7 +1191,7 @@ describe("formState", () => {
1190
1191
  });
1191
1192
  it("can have child object states passed in as field states", () => {
1192
1193
  // Given an author
1193
- const a = formState_1.createObjectState({
1194
+ const a = objectField_1.createObjectState({
1194
1195
  id: { type: "value" },
1195
1196
  // And address is modeled as a value object
1196
1197
  address: { type: "value" },
@@ -1207,7 +1208,7 @@ describe("formState", () => {
1207
1208
  });
1208
1209
  it("provides isNewEntity", () => {
1209
1210
  // Given an author without an id
1210
- const formState = formState_1.createObjectState(authorWithAddressConfig, {
1211
+ const formState = objectField_1.createObjectState(authorWithAddressConfig, {
1211
1212
  firstName: "f",
1212
1213
  address: { city: "c1", street: "s1" },
1213
1214
  });
@@ -1222,7 +1223,7 @@ describe("formState", () => {
1222
1223
  });
1223
1224
  it("isNewEntity is false if there is no id field", () => {
1224
1225
  // Given an author without an id field defined
1225
- const formState = formState_1.createObjectState({
1226
+ const formState = objectField_1.createObjectState({
1226
1227
  firstName: { type: "value" },
1227
1228
  address: { type: "object", config: { street: { type: "value" } } },
1228
1229
  }, {});
@@ -1232,7 +1233,7 @@ describe("formState", () => {
1232
1233
  });
1233
1234
  it("provides isNewEntity for lists", () => {
1234
1235
  // Given an author & books without an id
1235
- const formState = formState_1.createObjectState(authorWithBooksConfig, {
1236
+ const formState = objectField_1.createObjectState(authorWithBooksConfig, {
1236
1237
  books: [{ title: "t1" }, { title: "t2" }],
1237
1238
  });
1238
1239
  // Then both books know they are new
@@ -1300,13 +1301,13 @@ const authorWithBooksConfig = {
1300
1301
  type: "list",
1301
1302
  config: {
1302
1303
  id: { type: "value" },
1303
- title: { type: "value", rules: [formState_1.required] },
1304
+ title: { type: "value", rules: [rules_1.required] },
1304
1305
  classification: { type: "value" },
1305
1306
  },
1306
1307
  },
1307
1308
  };
1308
1309
  function createAuthorInputState(input, maybeAutoSave) {
1309
- return formState_1.createObjectState(authorWithBooksConfig, input, { maybeAutoSave });
1310
+ return objectField_1.createObjectState(authorWithBooksConfig, input, { maybeAutoSave });
1310
1311
  }
1311
1312
  const authorWithAddressConfig = {
1312
1313
  id: { type: "value" },
@@ -1315,7 +1316,7 @@ const authorWithAddressConfig = {
1315
1316
  address: {
1316
1317
  type: "object",
1317
1318
  config: {
1318
- street: { type: "value", rules: [formState_1.required] },
1319
+ street: { type: "value", rules: [rules_1.required] },
1319
1320
  city: { type: "value" },
1320
1321
  },
1321
1322
  },
@@ -1328,7 +1329,7 @@ const authorWithAddressAndBooksConfig = {
1328
1329
  type: "object",
1329
1330
  config: {
1330
1331
  id: { type: "value" },
1331
- street: { type: "value", rules: [formState_1.required] },
1332
+ street: { type: "value", rules: [rules_1.required] },
1332
1333
  city: { type: "value" },
1333
1334
  },
1334
1335
  },
@@ -1336,11 +1337,11 @@ const authorWithAddressAndBooksConfig = {
1336
1337
  type: "list",
1337
1338
  config: {
1338
1339
  id: { type: "value" },
1339
- title: { type: "value", rules: [formState_1.required] },
1340
+ title: { type: "value", rules: [rules_1.required] },
1340
1341
  classification: { type: "value" },
1341
1342
  },
1342
1343
  },
1343
1344
  };
1344
1345
  function createAuthorWithAddressInputState(input) {
1345
- return formState_1.createObjectState(authorWithAddressConfig, input);
1346
+ return objectField_1.createObjectState(authorWithAddressConfig, input);
1346
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";
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 {};