@homebound/form-state 2.9.1 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +66 -0
- package/dist/config.js +2 -0
- package/dist/fields/listField.d.ts +11 -0
- package/dist/fields/listField.js +212 -0
- package/dist/fields/objectField.d.ts +54 -0
- package/dist/fields/objectField.js +187 -0
- package/dist/fields/valueField.d.ts +59 -0
- package/dist/fields/valueField.js +158 -0
- package/dist/formState.test.js +131 -60
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -3
- package/dist/rules.d.ts +14 -0
- package/dist/rules.js +11 -0
- package/dist/useFormState.d.ts +2 -1
- package/dist/useFormState.js +8 -2
- package/dist/useFormState.test.js +21 -6
- package/dist/useFormStates.d.ts +11 -3
- package/dist/useFormStates.js +8 -4
- package/dist/useFormStates.test.js +48 -9
- package/dist/utils.d.ts +24 -1
- package/dist/utils.js +41 -1
- package/dist/utils.test.js +3 -3
- package/package.json +1 -1
- package/dist/formState.d.ts +0 -192
- package/dist/formState.js +0 -573
|
@@ -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;
|