@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.
- 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 +205 -0
- package/dist/fields/objectField.d.ts +54 -0
- package/dist/fields/objectField.js +180 -0
- package/dist/fields/valueField.d.ts +58 -0
- package/dist/fields/valueField.js +149 -0
- package/dist/formState.test.js +61 -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 +2 -2
- package/dist/useFormState.test.js +4 -4
- package/dist/useFormStates.d.ts +2 -1
- package/dist/useFormStates.js +2 -2
- package/dist/useFormStates.test.js +3 -3
- 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 -559
package/dist/formState.js
DELETED
|
@@ -1,559 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.createObjectState = exports.required = void 0;
|
|
7
|
-
const fast_deep_equal_1 = __importDefault(require("fast-deep-equal"));
|
|
8
|
-
const is_plain_object_1 = require("is-plain-object");
|
|
9
|
-
const mobx_1 = require("mobx");
|
|
10
|
-
const utils_1 = require("./utils");
|
|
11
|
-
/** A rule that validates `value` is not `undefined`, `null`, or empty string. */
|
|
12
|
-
// We pre-emptively make this a mobx action so that it's identity doesn't change when proxy-ified
|
|
13
|
-
// and breaks our ability to do `rules.some(r => r === required)`.
|
|
14
|
-
exports.required = mobx_1.action(({ value: v }) => {
|
|
15
|
-
const isEmptyString = typeof v === "string" ? v.trim() === "" : false;
|
|
16
|
-
return v !== undefined && v !== null && !isEmptyString ? undefined : "Required";
|
|
17
|
-
});
|
|
18
|
-
/**
|
|
19
|
-
* Creates a new `ObjectState` for a given form object `T` given config rules in `config`.
|
|
20
|
-
*
|
|
21
|
-
* The returned `ObjectState` can be used in a mobx `useLocalObservable` to driven an
|
|
22
|
-
* interactive form that tracks the current valid/touched/etc. state of both each
|
|
23
|
-
* individual fields as well as the top-level form/object itself.
|
|
24
|
-
*/
|
|
25
|
-
function createObjectState(config, instance, opts = {}) {
|
|
26
|
-
const noop = () => { };
|
|
27
|
-
return newObjectState(config, undefined, undefined, instance, undefined, opts.maybeAutoSave || noop);
|
|
28
|
-
}
|
|
29
|
-
exports.createObjectState = createObjectState;
|
|
30
|
-
function newObjectState(config, parentState, parentListState, instance, key, maybeAutoSave) {
|
|
31
|
-
// This is what we return, but we only know it's value until we call `observable`, so
|
|
32
|
-
// we create a mutable variable to capture it so that we can create fields/call their
|
|
33
|
-
// constructors and give them a way to access it later.
|
|
34
|
-
let proxy = undefined;
|
|
35
|
-
function getObjectState() {
|
|
36
|
-
if (!proxy) {
|
|
37
|
-
throw new Error("Race condition");
|
|
38
|
-
}
|
|
39
|
-
return proxy;
|
|
40
|
-
}
|
|
41
|
-
const fieldStates = Object.entries(config).map(([_key, _config]) => {
|
|
42
|
-
const key = _key;
|
|
43
|
-
const config = _config;
|
|
44
|
-
let field;
|
|
45
|
-
if (config.type === "value") {
|
|
46
|
-
field = newValueFieldState(instance, getObjectState, key, config.rules || [], config.isIdKey || key === "id", config.isDeleteKey || false, config.isReadOnlyKey || false, config.computed || false, config.readOnly || false, maybeAutoSave);
|
|
47
|
-
}
|
|
48
|
-
else if (config.type === "list") {
|
|
49
|
-
field = newListFieldState(instance, getObjectState, key, config.rules || [], config, config.config, maybeAutoSave);
|
|
50
|
-
}
|
|
51
|
-
else if (config.type === "object") {
|
|
52
|
-
if (!instance[key]) {
|
|
53
|
-
instance[key] = {};
|
|
54
|
-
}
|
|
55
|
-
field = newObjectState(config.config, getObjectState, undefined, instance[key], key, maybeAutoSave);
|
|
56
|
-
}
|
|
57
|
-
else {
|
|
58
|
-
throw new Error("Unsupported");
|
|
59
|
-
}
|
|
60
|
-
return [key, field];
|
|
61
|
-
});
|
|
62
|
-
// We always return the same `instance` field from our `value` method, but
|
|
63
|
-
// we want to pretend that it's observable, so use a tick to force it.
|
|
64
|
-
const _tick = mobx_1.observable({ value: 1 });
|
|
65
|
-
const fieldNames = Object.keys(config);
|
|
66
|
-
function getFields(proxyThis) {
|
|
67
|
-
return fieldNames.map((name) => proxyThis[name]);
|
|
68
|
-
}
|
|
69
|
-
const obj = {
|
|
70
|
-
...Object.fromEntries(fieldStates),
|
|
71
|
-
key,
|
|
72
|
-
get value() {
|
|
73
|
-
_tick.value > 0 || utils_1.fail();
|
|
74
|
-
return instance;
|
|
75
|
-
},
|
|
76
|
-
set value(value) {
|
|
77
|
-
this.set(value);
|
|
78
|
-
},
|
|
79
|
-
// private
|
|
80
|
-
_readOnly: false,
|
|
81
|
-
_considerDeleted() {
|
|
82
|
-
const deleteField = getFields(this).find((f) => f._isDeleteKey);
|
|
83
|
-
return !!(deleteField === null || deleteField === void 0 ? void 0 : deleteField.value);
|
|
84
|
-
},
|
|
85
|
-
_considerReadOnly() {
|
|
86
|
-
const readOnlyField = getFields(this).find((f) => f._isReadOnlyKey);
|
|
87
|
-
return !!(readOnlyField === null || readOnlyField === void 0 ? void 0 : readOnlyField.value);
|
|
88
|
-
},
|
|
89
|
-
get touched() {
|
|
90
|
-
return getFields(this).some((f) => f.touched);
|
|
91
|
-
},
|
|
92
|
-
set touched(touched) {
|
|
93
|
-
getFields(this).forEach((f) => (f.touched = touched));
|
|
94
|
-
},
|
|
95
|
-
get readOnly() {
|
|
96
|
-
return (this._readOnly ||
|
|
97
|
-
this._considerReadOnly() ||
|
|
98
|
-
(parentState && parentState().readOnly) ||
|
|
99
|
-
!!(parentListState === null || parentListState === void 0 ? void 0 : parentListState.readOnly));
|
|
100
|
-
},
|
|
101
|
-
set readOnly(readOnly) {
|
|
102
|
-
this._readOnly = readOnly;
|
|
103
|
-
},
|
|
104
|
-
get valid() {
|
|
105
|
-
return getFields(this).every((f) => f.valid);
|
|
106
|
-
},
|
|
107
|
-
get errors() {
|
|
108
|
-
return getFields(this).flatMap((f) => f.errors.map((e) => `${f.key}: ${e}`));
|
|
109
|
-
},
|
|
110
|
-
get dirty() {
|
|
111
|
-
return getFields(this).some((f) => f.dirty);
|
|
112
|
-
},
|
|
113
|
-
get isNewEntity() {
|
|
114
|
-
const idField = getFields(this).find((f) => f._isIdKey);
|
|
115
|
-
// If we're a line item w/o an immediate id field, look in our parent
|
|
116
|
-
if (!idField && parentState) {
|
|
117
|
-
return parentState().isNewEntity;
|
|
118
|
-
}
|
|
119
|
-
// If there is no id field, assume we're not new
|
|
120
|
-
return idField !== undefined && (idField.value === null || idField.value === undefined);
|
|
121
|
-
},
|
|
122
|
-
canSave() {
|
|
123
|
-
this.touched = true;
|
|
124
|
-
return this.valid;
|
|
125
|
-
},
|
|
126
|
-
// Accepts new values in bulk, i.e. when setting the form initial state from the backend.
|
|
127
|
-
set(value, opts = {}) {
|
|
128
|
-
if (this.readOnly && !opts.resetting && !opts.refreshing) {
|
|
129
|
-
throw new Error(`${key || "formState"} is currently readOnly`);
|
|
130
|
-
}
|
|
131
|
-
getFields(this).forEach((field) => {
|
|
132
|
-
if (field.key in value) {
|
|
133
|
-
field.set(value[field.key], opts);
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
},
|
|
137
|
-
// Resets all fields back to their original values
|
|
138
|
-
reset() {
|
|
139
|
-
getFields(this).forEach((f) => f.reset());
|
|
140
|
-
},
|
|
141
|
-
// Saves all current values into _originalValue
|
|
142
|
-
save() {
|
|
143
|
-
getFields(this).forEach((f) => f.save());
|
|
144
|
-
},
|
|
145
|
-
// Create a result that is only populated with changed keys
|
|
146
|
-
get changedValue() {
|
|
147
|
-
const result = {};
|
|
148
|
-
getFields(this).forEach((f) => {
|
|
149
|
-
if (f.dirty ||
|
|
150
|
-
// If the caller used useFormState.ifUndefined to provide some default values, then those keys may not
|
|
151
|
-
// look dirty, but if we're new we should include them anyway.
|
|
152
|
-
(this.isNewEntity &&
|
|
153
|
-
// Unless they're undefined anyway
|
|
154
|
-
f.value !== undefined &&
|
|
155
|
-
// And unless they're empty sub-objects
|
|
156
|
-
!(f.value instanceof Object && Object.entries(f.changedValue).length === 0))) {
|
|
157
|
-
result[f.key] = f.changedValue;
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
// Ensure we always have the id for updates to work
|
|
161
|
-
const idField = getFields(this).find((f) => f._isIdKey);
|
|
162
|
-
if (idField && idField.value !== undefined) {
|
|
163
|
-
result[idField.key] = idField.value;
|
|
164
|
-
}
|
|
165
|
-
return result;
|
|
166
|
-
},
|
|
167
|
-
get originalValue() {
|
|
168
|
-
return instance;
|
|
169
|
-
},
|
|
170
|
-
// An internal helper method to see if `other` is for "the same entity" as our current row
|
|
171
|
-
isSameEntity(other) {
|
|
172
|
-
const idField = getFields(this).find((f) => f._isIdKey);
|
|
173
|
-
if (!idField) {
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
return this[idField.key].value === other[idField.key];
|
|
177
|
-
},
|
|
178
|
-
};
|
|
179
|
-
proxy = mobx_1.makeAutoObservable(obj, {
|
|
180
|
-
// Use custom equality on `value` that is _never_ equals. This sounds weird, but
|
|
181
|
-
// because our `value` is always the same `instance` that was passed to `newObjectState`,
|
|
182
|
-
// to mobx this looks like the value never changes, and it will never invoke observers
|
|
183
|
-
// even with our tick-based hacks.
|
|
184
|
-
value: mobx_1.computed({ equals: () => false }),
|
|
185
|
-
});
|
|
186
|
-
// Any time a field changes, percolate that change up to us
|
|
187
|
-
mobx_1.reaction(() => getFields(proxy).map((f) => f.value), () => _tick.value++);
|
|
188
|
-
return proxy;
|
|
189
|
-
}
|
|
190
|
-
function newValueFieldState(parentInstance, parentState, key, rules, isIdKey, isDeleteKey, isReadOnlyKey, computed, readOnly, maybeAutoSave) {
|
|
191
|
-
// keep a copy here for reference equality
|
|
192
|
-
const value = parentInstance[key];
|
|
193
|
-
let _originalValue = value === null ? undefined : is_plain_object_1.isPlainObject(value) ? mobx_1.toJS(value) : value;
|
|
194
|
-
// Because we read/write the value directly back into parentInstance[key],
|
|
195
|
-
// which itself is not a proxy, we use this as our "value changed" trigger.
|
|
196
|
-
const _tick = mobx_1.observable({ value: 1 });
|
|
197
|
-
const _originalValueTick = mobx_1.observable({ value: 1 });
|
|
198
|
-
const field = {
|
|
199
|
-
key,
|
|
200
|
-
touched: false,
|
|
201
|
-
/** Current readOnly value. */
|
|
202
|
-
_readOnly: readOnly || false,
|
|
203
|
-
_focused: false,
|
|
204
|
-
_isIdKey: isIdKey,
|
|
205
|
-
_isDeleteKey: isDeleteKey,
|
|
206
|
-
_isReadOnlyKey: isReadOnlyKey,
|
|
207
|
-
rules,
|
|
208
|
-
get value() {
|
|
209
|
-
const value = _tick.value > 0 ? parentInstance[key] : utils_1.fail();
|
|
210
|
-
// Re-create the `keepNull` logic on sets but for our initial read where our
|
|
211
|
-
// originalValue is null (empty) but we want to expose it as undefined for
|
|
212
|
-
// consistency of "empty-ness" to our UI components.
|
|
213
|
-
return value === null && isEmpty(_originalValue) ? undefined : value;
|
|
214
|
-
},
|
|
215
|
-
set value(v) {
|
|
216
|
-
this.set(v);
|
|
217
|
-
},
|
|
218
|
-
get dirty() {
|
|
219
|
-
return !areEqual(this.originalValue, this.value);
|
|
220
|
-
},
|
|
221
|
-
/** Returns whether this field is readOnly, although if our parent is readOnly then it trumps. */
|
|
222
|
-
get readOnly() {
|
|
223
|
-
return parentState().readOnly || this._readOnly;
|
|
224
|
-
},
|
|
225
|
-
/** Sets the field readOnly, but `v` will still be or-d with the parent's readOnly state. */
|
|
226
|
-
set readOnly(v) {
|
|
227
|
-
this._readOnly = v;
|
|
228
|
-
},
|
|
229
|
-
// For primitive fields, the changed value is just the value.
|
|
230
|
-
get changedValue() {
|
|
231
|
-
return this.value;
|
|
232
|
-
},
|
|
233
|
-
get valid() {
|
|
234
|
-
const opts = { value: this.value, key: key, originalValue: this.originalValue, object: parentState() };
|
|
235
|
-
return this.rules.every((r) => r(opts) === undefined);
|
|
236
|
-
},
|
|
237
|
-
get errors() {
|
|
238
|
-
const opts = { value: this.value, key: key, originalValue: this.originalValue, object: parentState() };
|
|
239
|
-
return this.rules.map((r) => r(opts)).filter(isNotUndefined);
|
|
240
|
-
},
|
|
241
|
-
get required() {
|
|
242
|
-
return this.rules.some((rule) => rule === exports.required);
|
|
243
|
-
},
|
|
244
|
-
get isNewEntity() {
|
|
245
|
-
return parentState().isNewEntity;
|
|
246
|
-
},
|
|
247
|
-
focus() {
|
|
248
|
-
this._focused = true;
|
|
249
|
-
},
|
|
250
|
-
blur() {
|
|
251
|
-
this.maybeAutoSave();
|
|
252
|
-
this._focused = false;
|
|
253
|
-
},
|
|
254
|
-
maybeAutoSave() {
|
|
255
|
-
// 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
|
|
256
|
-
this.maybeTrim();
|
|
257
|
-
// touched is readonly, but we're allowed to change it
|
|
258
|
-
this.touched = true;
|
|
259
|
-
maybeAutoSave();
|
|
260
|
-
},
|
|
261
|
-
set(value, opts = {}) {
|
|
262
|
-
if (this.readOnly && !opts.resetting && !opts.refreshing) {
|
|
263
|
-
throw new Error(`${key} is currently readOnly`);
|
|
264
|
-
}
|
|
265
|
-
if (opts.refreshing && this.dirty && this.value !== value) {
|
|
266
|
-
// Ignore incoming values if we have changes (this.dirty) unless our latest change (this.value)
|
|
267
|
-
// matches the incoming value (value), b/c if it does we should accept it and reset originalValue
|
|
268
|
-
// so that we're not longer dirty.
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
else if (computed && (opts.resetting || opts.refreshing)) {
|
|
272
|
-
// Computeds can't be either reset or refreshed
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
// If the user has deleted/emptied a value that was originally set, keep it as `null`
|
|
276
|
-
// so that our partial update to the backend correctly unsets it.
|
|
277
|
-
const keepNull = !isEmpty(this.originalValue) && isEmpty(value);
|
|
278
|
-
// If a list of primitives was originally undefined, coerce `[]` to `undefined`
|
|
279
|
-
const coerceEmptyList = value && value instanceof Array && value.length === 0 && isEmpty(this.originalValue);
|
|
280
|
-
const newValue = keepNull ? null : isEmpty(value) || coerceEmptyList ? undefined : value;
|
|
281
|
-
// Set the value on our parent object
|
|
282
|
-
const changed = !areEqual(newValue, this.value);
|
|
283
|
-
parentInstance[key] = newValue;
|
|
284
|
-
_tick.value++;
|
|
285
|
-
if (opts.refreshing) {
|
|
286
|
-
this.originalValue = newValue;
|
|
287
|
-
}
|
|
288
|
-
// If we're being set programmatically, i.e. we don't currently have focus,
|
|
289
|
-
// call blur to trigger any auto-saves.
|
|
290
|
-
if (!this._focused && !opts.refreshing && !opts.resetting && this.dirty && changed && opts.autoSave !== false) {
|
|
291
|
-
this.maybeAutoSave();
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
reset() {
|
|
295
|
-
if (!computed) {
|
|
296
|
-
this.set(this.originalValue, { resetting: true });
|
|
297
|
-
}
|
|
298
|
-
this.touched = false;
|
|
299
|
-
},
|
|
300
|
-
save() {
|
|
301
|
-
if (is_plain_object_1.isPlainObject(this.originalValue)) {
|
|
302
|
-
this.originalValue = mobx_1.toJS(this.value);
|
|
303
|
-
}
|
|
304
|
-
else {
|
|
305
|
-
this.originalValue = this.value;
|
|
306
|
-
}
|
|
307
|
-
this.touched = false;
|
|
308
|
-
},
|
|
309
|
-
get originalValue() {
|
|
310
|
-
// A dummy check to for reactivity around our non-proxy value
|
|
311
|
-
return _originalValueTick.value > -1 ? _originalValue : _originalValue;
|
|
312
|
-
},
|
|
313
|
-
set originalValue(v) {
|
|
314
|
-
_originalValue = v;
|
|
315
|
-
_originalValueTick.value++;
|
|
316
|
-
},
|
|
317
|
-
maybeTrim() {
|
|
318
|
-
if (typeof this.value === "string") {
|
|
319
|
-
let newValue = this.value.trim();
|
|
320
|
-
if (newValue === "") {
|
|
321
|
-
newValue = undefined;
|
|
322
|
-
}
|
|
323
|
-
if (newValue !== this.value) {
|
|
324
|
-
this.set(newValue);
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
return field;
|
|
330
|
-
}
|
|
331
|
-
function newListFieldState(parentInstance, parentState, key, rules, listConfig, config, maybeAutoSave) {
|
|
332
|
-
// Keep a map of "item in the parent list" -> "that item's ObjectState"
|
|
333
|
-
const rowMap = new Map();
|
|
334
|
-
const _tick = mobx_1.observable({ value: 1 });
|
|
335
|
-
// this is for dirty checking, not object identity
|
|
336
|
-
let originalCopy = [...(parentInstance[key] || [])];
|
|
337
|
-
const list = {
|
|
338
|
-
key: key,
|
|
339
|
-
// Our fundamental state of wrapped Us
|
|
340
|
-
get value() {
|
|
341
|
-
return _tick.value > 0 ? parentInstance[key] : utils_1.fail();
|
|
342
|
-
},
|
|
343
|
-
_focused: false,
|
|
344
|
-
_readOnly: false,
|
|
345
|
-
get readOnly() {
|
|
346
|
-
return this._readOnly || parentState().readOnly;
|
|
347
|
-
},
|
|
348
|
-
set readOnly(readOnly) {
|
|
349
|
-
this._readOnly = readOnly;
|
|
350
|
-
},
|
|
351
|
-
set value(v) {
|
|
352
|
-
this.set(v);
|
|
353
|
-
},
|
|
354
|
-
get dirty() {
|
|
355
|
-
return this.rows.some((r) => r.dirty) || this.hasNewItems();
|
|
356
|
-
},
|
|
357
|
-
get required() {
|
|
358
|
-
return this.rules.some((rule) => rule === exports.required);
|
|
359
|
-
},
|
|
360
|
-
get isNewEntity() {
|
|
361
|
-
return parentState().isNewEntity;
|
|
362
|
-
},
|
|
363
|
-
// private
|
|
364
|
-
hasNewItems() {
|
|
365
|
-
const [current, original] = [this.value || [], originalCopy || []];
|
|
366
|
-
// Instead of relying on just object identities, we look up each child's state
|
|
367
|
-
// in rowMap, because we already dedup/check object identity (i.e. look for id fields)
|
|
368
|
-
// when create object states.
|
|
369
|
-
const a = current.every((e) => {
|
|
370
|
-
const state = rowMap.get(e);
|
|
371
|
-
return original.some((e) => rowMap.get(e) === state);
|
|
372
|
-
});
|
|
373
|
-
const b = original.every((e) => {
|
|
374
|
-
const state = rowMap.get(e);
|
|
375
|
-
return current.some((e) => rowMap.get(e) === state);
|
|
376
|
-
});
|
|
377
|
-
const isSame = a && b;
|
|
378
|
-
return !isSame;
|
|
379
|
-
},
|
|
380
|
-
// And we can derive each value's ObjectState wrapper as needed from the rowMap cache
|
|
381
|
-
get rows() {
|
|
382
|
-
// It's unclear why we need to access _tick.value here, b/c calling `this.value` should
|
|
383
|
-
// transitively register us as a dependency on it
|
|
384
|
-
if (_tick.value < 0)
|
|
385
|
-
utils_1.fail();
|
|
386
|
-
return (this.value || []).map((child) => {
|
|
387
|
-
// Because we're reading from this.value, child will be the proxy version
|
|
388
|
-
let childState = rowMap.get(child);
|
|
389
|
-
if (!childState) {
|
|
390
|
-
childState = newObjectState(config, parentState, list, child, undefined, maybeAutoSave);
|
|
391
|
-
rowMap.set(child, childState);
|
|
392
|
-
}
|
|
393
|
-
return childState;
|
|
394
|
-
});
|
|
395
|
-
},
|
|
396
|
-
// TODO Should this be true when all rows are touched?
|
|
397
|
-
get touched() {
|
|
398
|
-
return this.rows.some((r) => r.touched) || this.hasNewItems();
|
|
399
|
-
},
|
|
400
|
-
set touched(touched) {
|
|
401
|
-
this.rows.forEach((r) => (r.touched = touched));
|
|
402
|
-
},
|
|
403
|
-
rules,
|
|
404
|
-
get valid() {
|
|
405
|
-
const value = this.rows;
|
|
406
|
-
// TODO Passing `originalCopy || []` is probably not 100% right
|
|
407
|
-
const opts = { value, key: key, originalValue: originalCopy || [], object: parentState() };
|
|
408
|
-
const collectionValid = this.rules.every((r) => r(opts) === undefined);
|
|
409
|
-
const entriesValid = this.rows.filter((r) => !r._considerDeleted()).every((r) => r.valid);
|
|
410
|
-
return collectionValid && entriesValid;
|
|
411
|
-
},
|
|
412
|
-
get errors() {
|
|
413
|
-
if (_tick.value < 0)
|
|
414
|
-
utils_1.fail();
|
|
415
|
-
const opts = { value: this.rows, key: key, originalValue: originalCopy || [], object: parentState() };
|
|
416
|
-
return this.rules.map((r) => r(opts)).filter(isNotUndefined);
|
|
417
|
-
},
|
|
418
|
-
get changedValue() {
|
|
419
|
-
const result = [];
|
|
420
|
-
const hasOpKey = Object.keys(listConfig.config).includes("op");
|
|
421
|
-
const hasLegacyOpKey = Object.keys(listConfig.config).some((key) => key === "delete" || key === "remove");
|
|
422
|
-
const incremental = listConfig.update === "incremental" ||
|
|
423
|
-
// Implicitly enable incremental mode if we see an op key
|
|
424
|
-
(listConfig.update === undefined && (hasOpKey || hasLegacyOpKey));
|
|
425
|
-
const exhaustive = !incremental;
|
|
426
|
-
this.rows.forEach((r) => {
|
|
427
|
-
var _a;
|
|
428
|
-
var _b;
|
|
429
|
-
if (exhaustive || r.dirty || r.isNewEntity) {
|
|
430
|
-
const changed = r.changedValue;
|
|
431
|
-
// Ensure we have an `op: include` key, following https://joist-orm.io/docs/features/partial-update-apis
|
|
432
|
-
if (incremental && hasOpKey) {
|
|
433
|
-
(_a = (_b = changed).op) !== null && _a !== void 0 ? _a : (_b.op = "include");
|
|
434
|
-
}
|
|
435
|
-
result.push(changed);
|
|
436
|
-
}
|
|
437
|
-
});
|
|
438
|
-
return result;
|
|
439
|
-
},
|
|
440
|
-
focus() {
|
|
441
|
-
this._focused = true;
|
|
442
|
-
},
|
|
443
|
-
blur() {
|
|
444
|
-
this._focused = false;
|
|
445
|
-
this.maybeAutoSave();
|
|
446
|
-
},
|
|
447
|
-
maybeAutoSave() {
|
|
448
|
-
this.touched = true;
|
|
449
|
-
maybeAutoSave();
|
|
450
|
-
},
|
|
451
|
-
set(values, opts = {}) {
|
|
452
|
-
if (this.readOnly && !opts.resetting && !opts.refreshing) {
|
|
453
|
-
throw new Error(`${key} is currently readOnly`);
|
|
454
|
-
}
|
|
455
|
-
// We should be passed values that are non-proxies.
|
|
456
|
-
parentInstance[key] = values.map((value) => {
|
|
457
|
-
let childState = rowMap.get(value);
|
|
458
|
-
if (!childState) {
|
|
459
|
-
// Look for an existing child (requires having an id key configured)
|
|
460
|
-
for (const [, otherState] of rowMap.entries()) {
|
|
461
|
-
if (otherState.isSameEntity(value)) {
|
|
462
|
-
otherState.set(value, opts);
|
|
463
|
-
rowMap.set(value, otherState);
|
|
464
|
-
return otherState.value;
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
// If we didn't have an existing child, just make a new object state
|
|
468
|
-
childState = createObjectState(config, value, { maybeAutoSave });
|
|
469
|
-
rowMap.set(value, childState);
|
|
470
|
-
}
|
|
471
|
-
// Return the already-observable'd value so that our `parent.value[key] = values` doesn't re-proxy things
|
|
472
|
-
return childState.value;
|
|
473
|
-
});
|
|
474
|
-
// Reset originalCopy so that our dirty checks have the right # of rows.
|
|
475
|
-
if (opts.refreshing) {
|
|
476
|
-
originalCopy = [...(parentInstance[key] || [])];
|
|
477
|
-
}
|
|
478
|
-
_tick.value++;
|
|
479
|
-
},
|
|
480
|
-
add(value, spliceIndex) {
|
|
481
|
-
// This is called by the user, so value should be a non-proxy value we should keep
|
|
482
|
-
const childState = createObjectState(config, value, { maybeAutoSave });
|
|
483
|
-
rowMap.set(value, childState);
|
|
484
|
-
this.ensureSet();
|
|
485
|
-
this.value.splice(typeof spliceIndex === "number" ? spliceIndex : this.value.length, 0, childState.value);
|
|
486
|
-
_tick.value++;
|
|
487
|
-
maybeAutoSave();
|
|
488
|
-
},
|
|
489
|
-
remove(indexOrValue) {
|
|
490
|
-
this.ensureSet();
|
|
491
|
-
if (typeof indexOrValue === "number") {
|
|
492
|
-
this.value.splice(indexOrValue, 1);
|
|
493
|
-
}
|
|
494
|
-
else {
|
|
495
|
-
const index = this.value.findIndex((v) => v === indexOrValue);
|
|
496
|
-
if (index > -1) {
|
|
497
|
-
this.value.splice(index, 1);
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
_tick.value++;
|
|
501
|
-
maybeAutoSave();
|
|
502
|
-
},
|
|
503
|
-
reset() {
|
|
504
|
-
if (originalCopy) {
|
|
505
|
-
this.set(originalCopy, { resetting: true });
|
|
506
|
-
this.rows.forEach((r) => r.reset());
|
|
507
|
-
}
|
|
508
|
-
},
|
|
509
|
-
save() {
|
|
510
|
-
this.rows.forEach((r) => {
|
|
511
|
-
r.save();
|
|
512
|
-
});
|
|
513
|
-
originalCopy = [...(parentInstance[key] || [])];
|
|
514
|
-
_tick.value++;
|
|
515
|
-
},
|
|
516
|
-
ensureSet() {
|
|
517
|
-
if (!parentInstance[key]) {
|
|
518
|
-
parentInstance[key] = [];
|
|
519
|
-
}
|
|
520
|
-
_tick.value++;
|
|
521
|
-
},
|
|
522
|
-
};
|
|
523
|
-
return mobx_1.makeAutoObservable(list, {
|
|
524
|
-
// See other makeAutoObservable comment
|
|
525
|
-
value: mobx_1.computed({ equals: () => false }),
|
|
526
|
-
});
|
|
527
|
-
}
|
|
528
|
-
function isNotUndefined(value) {
|
|
529
|
-
return value !== undefined;
|
|
530
|
-
}
|
|
531
|
-
function isEmpty(value) {
|
|
532
|
-
return value === undefined || value === null || value === "";
|
|
533
|
-
}
|
|
534
|
-
/**
|
|
535
|
-
* An equals that does deep-ish equality.
|
|
536
|
-
*
|
|
537
|
-
* We only do non-identity equals for:
|
|
538
|
-
*
|
|
539
|
-
* - "plain" objects that have no custom prototype/i.e. are object literals
|
|
540
|
-
* - objects that implement `toJSON`
|
|
541
|
-
*
|
|
542
|
-
*/
|
|
543
|
-
function areEqual(a, b) {
|
|
544
|
-
if (is_plain_object_1.isPlainObject(a)) {
|
|
545
|
-
return fast_deep_equal_1.default(mobx_1.toJS(a), mobx_1.toJS(b));
|
|
546
|
-
}
|
|
547
|
-
if (hasToJSON(a) || hasToJSON(b)) {
|
|
548
|
-
const a1 = hasToJSON(a) ? a.toJSON() : a;
|
|
549
|
-
const b1 = hasToJSON(b) ? b.toJSON() : b;
|
|
550
|
-
return fast_deep_equal_1.default(a1, b1);
|
|
551
|
-
}
|
|
552
|
-
if (a && b && a instanceof Array && b instanceof Array) {
|
|
553
|
-
return fast_deep_equal_1.default(a, b);
|
|
554
|
-
}
|
|
555
|
-
return a === b;
|
|
556
|
-
}
|
|
557
|
-
function hasToJSON(o) {
|
|
558
|
-
return !!(o && typeof o === "object" && "toJSON" in o);
|
|
559
|
-
}
|