@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.
@@ -0,0 +1,158 @@
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
+ _loading: false,
22
+ _focused: false,
23
+ _isIdKey: isIdKey,
24
+ _isDeleteKey: isDeleteKey,
25
+ _isReadOnlyKey: isReadOnlyKey,
26
+ rules,
27
+ get value() {
28
+ const value = _tick.value > 0 ? parentInstance[key] : utils_1.fail();
29
+ // Re-create the `keepNull` logic on sets but for our initial read where our
30
+ // originalValue is null (empty) but we want to expose it as undefined for
31
+ // consistency of "empty-ness" to our UI components.
32
+ return value === null && utils_1.isEmpty(_originalValue) ? undefined : value;
33
+ },
34
+ set value(v) {
35
+ this.set(v);
36
+ },
37
+ get dirty() {
38
+ return !utils_1.areEqual(this.originalValue, this.value);
39
+ },
40
+ /** Returns whether this field is readOnly, although if our parent is readOnly then it trumps. */
41
+ get readOnly() {
42
+ return parentState().readOnly || this._readOnly;
43
+ },
44
+ /** Sets the field readOnly, but `loading` will still be or-d with the parent's readOnly state. */
45
+ set readOnly(readOnly) {
46
+ this._readOnly = readOnly;
47
+ },
48
+ /** Returns whether this field is loading, or if our parent is loading. */
49
+ get loading() {
50
+ return parentState().loading || this._loading;
51
+ },
52
+ /** Sets the field loading, but `loading` will still be or-d with the parent's loading state. */
53
+ set loading(loading) {
54
+ this._loading = loading;
55
+ },
56
+ // For primitive fields, the changed value is just the value.
57
+ get changedValue() {
58
+ return this.value;
59
+ },
60
+ get valid() {
61
+ const opts = { value: this.value, key: key, originalValue: this.originalValue, object: parentState() };
62
+ return this.rules.every((r) => r(opts) === undefined);
63
+ },
64
+ get errors() {
65
+ const opts = { value: this.value, key: key, originalValue: this.originalValue, object: parentState() };
66
+ return this.rules.map((r) => r(opts)).filter(utils_1.isNotUndefined);
67
+ },
68
+ get required() {
69
+ return this.rules.some((rule) => rule === rules_1.required);
70
+ },
71
+ get isNewEntity() {
72
+ return parentState().isNewEntity;
73
+ },
74
+ focus() {
75
+ this._focused = true;
76
+ },
77
+ blur() {
78
+ this.maybeAutoSave();
79
+ this._focused = false;
80
+ },
81
+ maybeAutoSave() {
82
+ // 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
83
+ this.maybeTrim();
84
+ // touched is readonly, but we're allowed to change it
85
+ this.touched = true;
86
+ maybeAutoSave();
87
+ },
88
+ set(value, opts = {}) {
89
+ if (this.readOnly && !opts.resetting && !opts.refreshing) {
90
+ throw new Error(`${key} is currently readOnly`);
91
+ }
92
+ if (opts.refreshing && this.dirty && this.value !== value) {
93
+ // Ignore incoming values if we have changes (this.dirty) unless our latest change (this.value)
94
+ // matches the incoming value (value), b/c if it does we should accept it and reset originalValue
95
+ // so that we're not longer dirty.
96
+ return;
97
+ }
98
+ else if (computed && (opts.resetting || opts.refreshing)) {
99
+ // Computeds can't be either reset or refreshed
100
+ return;
101
+ }
102
+ // If the user has deleted/emptied a value that was originally set, keep it as `null`
103
+ // so that our partial update to the backend correctly unsets it.
104
+ const keepNull = !utils_1.isEmpty(this.originalValue) && utils_1.isEmpty(value);
105
+ // If a list of primitives was originally undefined, coerce `[]` to `undefined`
106
+ const coerceEmptyList = value && value instanceof Array && value.length === 0 && utils_1.isEmpty(this.originalValue);
107
+ const newValue = keepNull ? null : utils_1.isEmpty(value) || coerceEmptyList ? undefined : value;
108
+ // Set the value on our parent object
109
+ const changed = !utils_1.areEqual(newValue, this.value);
110
+ parentInstance[key] = newValue;
111
+ _tick.value++;
112
+ if (opts.refreshing) {
113
+ this.originalValue = newValue;
114
+ }
115
+ // If we're being set programmatically, i.e. we don't currently have focus,
116
+ // call blur to trigger any auto-saves.
117
+ if (!this._focused && !opts.refreshing && !opts.resetting && this.dirty && changed && opts.autoSave !== false) {
118
+ this.maybeAutoSave();
119
+ }
120
+ },
121
+ reset() {
122
+ if (!computed) {
123
+ this.set(this.originalValue, { resetting: true });
124
+ }
125
+ this.touched = false;
126
+ },
127
+ save() {
128
+ if (is_plain_object_1.isPlainObject(this.originalValue)) {
129
+ this.originalValue = mobx_1.toJS(this.value);
130
+ }
131
+ else {
132
+ this.originalValue = this.value;
133
+ }
134
+ this.touched = false;
135
+ },
136
+ get originalValue() {
137
+ // A dummy check to for reactivity around our non-proxy value
138
+ return _originalValueTick.value > -1 ? _originalValue : _originalValue;
139
+ },
140
+ set originalValue(v) {
141
+ _originalValue = v;
142
+ _originalValueTick.value++;
143
+ },
144
+ maybeTrim() {
145
+ if (typeof this.value === "string") {
146
+ let newValue = this.value.trim();
147
+ if (newValue === "") {
148
+ newValue = undefined;
149
+ }
150
+ if (newValue !== this.value) {
151
+ this.set(newValue);
152
+ }
153
+ }
154
+ },
155
+ };
156
+ return field;
157
+ }
158
+ exports.newValueFieldState = newValueFieldState;