@blueprint-ts/core 4.1.0-beta.3 → 4.1.0-beta.5
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/requests/BaseRequest.d.ts +9 -0
- package/dist/requests/BaseRequest.js +22 -7
- package/dist/requests/contracts/BaseRequestContract.d.ts +10 -0
- package/dist/vue/forms/BaseForm.d.ts +87 -21
- package/dist/vue/forms/BaseForm.js +803 -234
- package/dist/vue/forms/PropertyAwareArray.d.ts +1 -0
- package/dist/vue/forms/PropertyAwareObject.d.ts +13 -0
- package/dist/vue/forms/PropertyAwareObject.js +18 -0
- package/dist/vue/forms/index.d.ts +5 -2
- package/dist/vue/forms/index.js +3 -1
- package/dist/vue/forms/persistence/StrictPersistenceRestorePolicy.d.ts +4 -0
- package/dist/vue/forms/persistence/StrictPersistenceRestorePolicy.js +42 -0
- package/dist/vue/forms/persistence/index.d.ts +3 -0
- package/dist/vue/forms/persistence/index.js +2 -0
- package/dist/vue/forms/persistence/types.d.ts +23 -0
- package/dist/vue/forms/persistence/types.js +1 -0
- package/dist/vue/forms/persistence/utils.d.ts +2 -0
- package/dist/vue/forms/persistence/utils.js +77 -0
- package/dist/vue/forms/validation/index.d.ts +4 -3
- package/dist/vue/forms/validation/index.js +2 -1
- package/dist/vue/forms/validation/rules/BaseRule.d.ts +7 -0
- package/dist/vue/forms/validation/rules/BaseRule.js +19 -0
- package/dist/vue/forms/validation/rules/PrecognitiveRule.d.ts +22 -0
- package/dist/vue/forms/validation/rules/PrecognitiveRule.js +58 -0
- package/dist/vue/forms/validation/types/ValidationRules.d.ts +3 -0
- package/package.json +1 -1
|
@@ -1,8 +1,19 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
import { reactive, computed, toRaw, watch } from 'vue';
|
|
2
|
-
import { camelCase, upperFirst, cloneDeep, isEqual } from 'lodash-es';
|
|
3
|
-
import isEqualWith from 'lodash-es/isEqualWith';
|
|
11
|
+
import { camelCase, upperFirst, cloneDeep, debounce, isEqual } from 'lodash-es';
|
|
4
12
|
import { NonPersistentDriver } from '../../persistenceDrivers/NonPersistentDriver';
|
|
5
13
|
import { PropertyAwareArray } from './PropertyAwareArray';
|
|
14
|
+
import { PropertyAwareObject, PROPERTY_AWARE_OBJECT_MARKER } from './PropertyAwareObject';
|
|
15
|
+
import { StrictPersistenceRestorePolicy } from './persistence/StrictPersistenceRestorePolicy';
|
|
16
|
+
import { BaseRule } from './validation/rules/BaseRule';
|
|
6
17
|
import { ValidationMode } from './validation';
|
|
7
18
|
function isRecord(value) {
|
|
8
19
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -11,11 +22,40 @@ function isErrorMessages(value) {
|
|
|
11
22
|
return Array.isArray(value) && (value.length === 0 || typeof value[0] === 'string');
|
|
12
23
|
}
|
|
13
24
|
function isErrorArray(value) {
|
|
14
|
-
return Array.isArray(value) && value.
|
|
25
|
+
return Array.isArray(value) && value.some((item) => isRecord(item));
|
|
15
26
|
}
|
|
16
27
|
function isErrorObject(value) {
|
|
17
28
|
return isRecord(value);
|
|
18
29
|
}
|
|
30
|
+
function isPropertyAwareObject(value) {
|
|
31
|
+
return value instanceof PropertyAwareObject;
|
|
32
|
+
}
|
|
33
|
+
function isSerializedPropertyAwareObject(value) {
|
|
34
|
+
return isRecord(value) && value[PROPERTY_AWARE_OBJECT_MARKER] === true;
|
|
35
|
+
}
|
|
36
|
+
function restoreSerializedPropertyAwareValue(value) {
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
return value.map((item) => restoreSerializedPropertyAwareValue(item));
|
|
39
|
+
}
|
|
40
|
+
if (isSerializedPropertyAwareObject(value)) {
|
|
41
|
+
const restored = {};
|
|
42
|
+
for (const [key, child] of Object.entries(value)) {
|
|
43
|
+
if (key === PROPERTY_AWARE_OBJECT_MARKER) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
restored[key] = restoreSerializedPropertyAwareValue(child);
|
|
47
|
+
}
|
|
48
|
+
return new PropertyAwareObject(restored);
|
|
49
|
+
}
|
|
50
|
+
if (isRecord(value)) {
|
|
51
|
+
const restored = {};
|
|
52
|
+
for (const [key, child] of Object.entries(value)) {
|
|
53
|
+
restored[key] = restoreSerializedPropertyAwareValue(child);
|
|
54
|
+
}
|
|
55
|
+
return restored;
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
19
59
|
export function propertyAwareToRaw(propertyAwareObject) {
|
|
20
60
|
var _a;
|
|
21
61
|
if (Array.isArray(propertyAwareObject)) {
|
|
@@ -65,40 +105,41 @@ function deepMergeArrays(target, source) {
|
|
|
65
105
|
return s;
|
|
66
106
|
});
|
|
67
107
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
|
|
78
|
-
|
|
108
|
+
function restorePropertyAwareStructure(defaults, value) {
|
|
109
|
+
if (defaults instanceof PropertyAwareArray) {
|
|
110
|
+
const restored = value instanceof PropertyAwareArray ? value : new PropertyAwareArray(Array.isArray(value) ? Array.from(value) : []);
|
|
111
|
+
const defaultItemTemplate = defaults[0];
|
|
112
|
+
for (let index = 0; index < restored.length; index++) {
|
|
113
|
+
if (index < defaults.length) {
|
|
114
|
+
restored[index] = restorePropertyAwareStructure(defaults[index], restored[index]);
|
|
115
|
+
}
|
|
116
|
+
else if (defaultItemTemplate !== undefined) {
|
|
117
|
+
restored[index] = restorePropertyAwareStructure(defaultItemTemplate, restored[index]);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
restored[index] = restoreSerializedPropertyAwareValue(restored[index]);
|
|
79
121
|
}
|
|
80
122
|
}
|
|
123
|
+
return restored;
|
|
81
124
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return undefined; // use default comparison
|
|
101
|
-
});
|
|
125
|
+
if (defaults instanceof PropertyAwareObject) {
|
|
126
|
+
const restored = value instanceof PropertyAwareObject
|
|
127
|
+
? value
|
|
128
|
+
: new PropertyAwareObject(isRecord(value) ? value : {});
|
|
129
|
+
const restoredRecord = restored;
|
|
130
|
+
const defaultRecord = defaults;
|
|
131
|
+
for (const key of Object.keys(defaults)) {
|
|
132
|
+
restoredRecord[key] = restorePropertyAwareStructure(defaultRecord[key], restoredRecord[key]);
|
|
133
|
+
}
|
|
134
|
+
return restored;
|
|
135
|
+
}
|
|
136
|
+
if (isRecord(defaults) && isRecord(value)) {
|
|
137
|
+
for (const key of Object.keys(defaults)) {
|
|
138
|
+
value[key] = restorePropertyAwareStructure(defaults[key], value[key]);
|
|
139
|
+
}
|
|
140
|
+
return value;
|
|
141
|
+
}
|
|
142
|
+
return restoreSerializedPropertyAwareValue(value);
|
|
102
143
|
}
|
|
103
144
|
/**
|
|
104
145
|
* A generic base class for forms.
|
|
@@ -118,6 +159,20 @@ export class BaseForm {
|
|
|
118
159
|
getPersistenceDriver(_suffix) {
|
|
119
160
|
return new NonPersistentDriver();
|
|
120
161
|
}
|
|
162
|
+
getPersistenceRestorePolicy() {
|
|
163
|
+
return new StrictPersistenceRestorePolicy();
|
|
164
|
+
}
|
|
165
|
+
shouldLogPersistenceDebug() {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
logPersistenceDebug(event) {
|
|
169
|
+
if (!this.shouldLogPersistenceDebug()) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const suffixLabel = event.persistSuffix ? ` (${event.persistSuffix})` : '';
|
|
173
|
+
const details = event.details ? ` ${JSON.stringify(event.details)}` : '';
|
|
174
|
+
console.debug(`[BaseForm persistence] ${event.formName}${suffixLabel}: ${event.action} (${event.reason})${details}`);
|
|
175
|
+
}
|
|
121
176
|
/**
|
|
122
177
|
* Helper: recursively computes the dirty state for a value based on the original.
|
|
123
178
|
* For plain arrays we compare the entire array (a single flag), not each element.
|
|
@@ -130,7 +185,7 @@ export class BaseForm {
|
|
|
130
185
|
const dirty = {};
|
|
131
186
|
for (const key in current) {
|
|
132
187
|
if (Object.prototype.hasOwnProperty.call(current, key)) {
|
|
133
|
-
dirty[key] =
|
|
188
|
+
dirty[key] = this.computeDirtyState(current[key], original[key]);
|
|
134
189
|
}
|
|
135
190
|
}
|
|
136
191
|
return dirty;
|
|
@@ -251,44 +306,65 @@ export class BaseForm {
|
|
|
251
306
|
}
|
|
252
307
|
}
|
|
253
308
|
constructor(defaults, options) {
|
|
309
|
+
var _a;
|
|
254
310
|
this.options = options;
|
|
255
311
|
this._errors = reactive({});
|
|
312
|
+
this._asyncErrors = reactive({});
|
|
256
313
|
this.append = [];
|
|
257
314
|
this.ignore = [];
|
|
258
315
|
this.errorMap = {};
|
|
259
316
|
this.rules = {};
|
|
317
|
+
this.validationGroups = {};
|
|
260
318
|
this.fieldDependencies = new Map();
|
|
319
|
+
this.arrayWrapperCache = new Map();
|
|
320
|
+
this.arrayItemWrapperCache = new Map();
|
|
321
|
+
this.asyncValidationDebouncers = new Map();
|
|
322
|
+
this.pendingAsyncValidationContexts = new Map();
|
|
323
|
+
this.asyncValidationTokens = reactive({});
|
|
261
324
|
const persist = (options === null || options === void 0 ? void 0 : options.persist) !== false;
|
|
262
325
|
let initialData;
|
|
263
326
|
const driver = this.getPersistenceDriver(options === null || options === void 0 ? void 0 : options.persistSuffix);
|
|
264
327
|
if (persist) {
|
|
265
|
-
const persisted = driver.get(this.constructor.name);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
328
|
+
const persisted = (_a = driver.get(this.constructor.name)) !== null && _a !== void 0 ? _a : null;
|
|
329
|
+
const restoreDecision = this.getPersistenceRestorePolicy().resolve({
|
|
330
|
+
formName: this.constructor.name,
|
|
331
|
+
persistSuffix: options === null || options === void 0 ? void 0 : options.persistSuffix,
|
|
332
|
+
defaults,
|
|
333
|
+
persisted
|
|
334
|
+
});
|
|
335
|
+
this.logPersistenceDebug({
|
|
336
|
+
formName: this.constructor.name,
|
|
337
|
+
persistSuffix: options === null || options === void 0 ? void 0 : options.persistSuffix,
|
|
338
|
+
action: restoreDecision.action,
|
|
339
|
+
reason: restoreDecision.reason,
|
|
340
|
+
details: restoreDecision.details
|
|
341
|
+
});
|
|
342
|
+
if (restoreDecision.action === 'restore' && restoreDecision.persisted) {
|
|
343
|
+
initialData = restorePropertyAwareStructure(defaults, restoreDecision.persisted.state);
|
|
344
|
+
this.original = restorePropertyAwareStructure(defaults, cloneDeep(restoreDecision.persisted.original));
|
|
345
|
+
this.dirty = reactive(restoreDecision.persisted.dirty);
|
|
346
|
+
this.touched = reactive(restoreDecision.persisted.touched || {});
|
|
273
347
|
}
|
|
274
348
|
else {
|
|
275
|
-
console.log('Discarding persisted data for ' + this.constructor.name + " because it doesn't match the defaults.");
|
|
276
349
|
initialData = defaults;
|
|
277
|
-
this.original = cloneDeep(defaults);
|
|
350
|
+
this.original = restorePropertyAwareStructure(defaults, cloneDeep(defaults));
|
|
278
351
|
const init = this.initDirtyTouched(defaults);
|
|
279
352
|
this.dirty = init.dirty;
|
|
280
353
|
this.touched = init.touched;
|
|
281
|
-
|
|
354
|
+
if (restoreDecision.action === 'discard') {
|
|
355
|
+
driver.remove(this.constructor.name);
|
|
356
|
+
}
|
|
282
357
|
}
|
|
283
358
|
}
|
|
284
359
|
else {
|
|
285
360
|
initialData = defaults;
|
|
286
|
-
this.original = cloneDeep(defaults);
|
|
361
|
+
this.original = restorePropertyAwareStructure(defaults, cloneDeep(defaults));
|
|
287
362
|
const init = this.initDirtyTouched(defaults);
|
|
288
363
|
this.dirty = init.dirty;
|
|
289
364
|
this.touched = init.touched;
|
|
290
365
|
}
|
|
291
366
|
this.rules = this.defineRules();
|
|
367
|
+
this.validationGroups = this.defineValidationGroups();
|
|
292
368
|
this.buildFieldDependencies();
|
|
293
369
|
this.state = reactive(initialData);
|
|
294
370
|
this._model = {};
|
|
@@ -300,7 +376,7 @@ export class BaseForm {
|
|
|
300
376
|
set: (newVal) => {
|
|
301
377
|
const next = Array.isArray(newVal) ? Array.from(newVal) : [];
|
|
302
378
|
this.replacePropertyAwareArray(key, next);
|
|
303
|
-
this.dirty[key] =
|
|
379
|
+
this.dirty[key] = this.computeDirtyState(this.state[key], this.original[key]);
|
|
304
380
|
this.markFieldUpdated(key, driver);
|
|
305
381
|
}
|
|
306
382
|
});
|
|
@@ -323,29 +399,7 @@ export class BaseForm {
|
|
|
323
399
|
}, { deep: true });
|
|
324
400
|
}
|
|
325
401
|
}
|
|
326
|
-
this._hasErrors = computed(() =>
|
|
327
|
-
for (const field in this._errors) {
|
|
328
|
-
if (Object.prototype.hasOwnProperty.call(this._errors, field)) {
|
|
329
|
-
const fieldErrors = this._errors[field];
|
|
330
|
-
if (Array.isArray(fieldErrors) && fieldErrors.length > 0) {
|
|
331
|
-
return true;
|
|
332
|
-
}
|
|
333
|
-
if (fieldErrors && typeof fieldErrors === 'object') {
|
|
334
|
-
if (Array.isArray(fieldErrors)) {
|
|
335
|
-
for (const item of fieldErrors) {
|
|
336
|
-
if (item && typeof item === 'object' && Object.keys(item).length > 0) {
|
|
337
|
-
return true;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
else if (Object.keys(fieldErrors).length > 0) {
|
|
342
|
-
return true;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
return false;
|
|
348
|
-
});
|
|
402
|
+
this._hasErrors = computed(() => Object.keys(this.flattenErrors()).length > 0);
|
|
349
403
|
if (persist) {
|
|
350
404
|
watch(() => this.state, () => this.persistState(driver), { deep: true, immediate: true });
|
|
351
405
|
}
|
|
@@ -354,18 +408,275 @@ export class BaseForm {
|
|
|
354
408
|
defineRules() {
|
|
355
409
|
return {};
|
|
356
410
|
}
|
|
411
|
+
defineValidationGroups() {
|
|
412
|
+
return {};
|
|
413
|
+
}
|
|
414
|
+
clearErrorBag(errorBag) {
|
|
415
|
+
for (const key in errorBag) {
|
|
416
|
+
delete errorBag[key];
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
clearSyncErrors() {
|
|
420
|
+
this.clearErrorBag(this._errors);
|
|
421
|
+
}
|
|
422
|
+
clearAsyncErrors() {
|
|
423
|
+
this.clearErrorBag(this._asyncErrors);
|
|
424
|
+
}
|
|
357
425
|
clearErrors() {
|
|
358
|
-
|
|
359
|
-
|
|
426
|
+
this.cancelPendingAsyncValidations();
|
|
427
|
+
this.clearSyncErrors();
|
|
428
|
+
this.clearAsyncErrors();
|
|
429
|
+
}
|
|
430
|
+
hasAsyncRules(field) {
|
|
431
|
+
const fieldConfig = this.rules[field];
|
|
432
|
+
if (!(fieldConfig === null || fieldConfig === void 0 ? void 0 : fieldConfig.rules) || fieldConfig.rules.length === 0) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
return fieldConfig.rules.some((rule) => rule.validateAsync !== BaseRule.prototype.validateAsync);
|
|
436
|
+
}
|
|
437
|
+
bumpAsyncValidationToken(field) {
|
|
438
|
+
var _a;
|
|
439
|
+
const fieldKey = String(field);
|
|
440
|
+
const nextToken = ((_a = this.asyncValidationTokens[fieldKey]) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
441
|
+
this.asyncValidationTokens[fieldKey] = nextToken;
|
|
442
|
+
return nextToken;
|
|
443
|
+
}
|
|
444
|
+
cancelPendingAsyncValidations() {
|
|
445
|
+
var _a;
|
|
446
|
+
for (const debouncer of this.asyncValidationDebouncers.values()) {
|
|
447
|
+
debouncer.cancel();
|
|
448
|
+
}
|
|
449
|
+
this.pendingAsyncValidationContexts.clear();
|
|
450
|
+
for (const fieldKey of Object.keys(this.asyncValidationTokens)) {
|
|
451
|
+
this.asyncValidationTokens[fieldKey] = ((_a = this.asyncValidationTokens[fieldKey]) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
scheduleAsyncValidation(field, context) {
|
|
455
|
+
var _a, _b, _c;
|
|
456
|
+
if (!this.hasAsyncRules(field)) {
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
const token = this.bumpAsyncValidationToken(field);
|
|
460
|
+
const debounceMs = (_c = (_b = (_a = this.rules[field]) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.asyncDebounceMs) !== null && _c !== void 0 ? _c : 0;
|
|
461
|
+
this.pendingAsyncValidationContexts.set(field, { token, context });
|
|
462
|
+
if (debounceMs <= 0) {
|
|
463
|
+
void this.executeScheduledAsyncValidation(field).catch(() => undefined);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
let debouncer = this.asyncValidationDebouncers.get(field);
|
|
467
|
+
if (debouncer === undefined) {
|
|
468
|
+
debouncer = debounce(() => {
|
|
469
|
+
void this.executeScheduledAsyncValidation(field).catch(() => undefined);
|
|
470
|
+
}, debounceMs);
|
|
471
|
+
this.asyncValidationDebouncers.set(field, debouncer);
|
|
360
472
|
}
|
|
473
|
+
debouncer();
|
|
361
474
|
}
|
|
362
|
-
|
|
363
|
-
|
|
475
|
+
executeScheduledAsyncValidation(field) {
|
|
476
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
477
|
+
const pending = this.pendingAsyncValidationContexts.get(field);
|
|
478
|
+
if (pending === undefined) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
yield this.runFieldAsyncValidation(field, Object.assign(Object.assign({}, pending.context), { skipSyncValidation: true, skipAsyncValidation: true }), pending.token);
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
flattenErrorValue(value, path, flattened) {
|
|
485
|
+
if (value === undefined) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (isErrorMessages(value)) {
|
|
489
|
+
if (value.length > 0) {
|
|
490
|
+
flattened[path] = cloneDeep(value);
|
|
491
|
+
}
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (isErrorArray(value)) {
|
|
495
|
+
value.forEach((item, index) => {
|
|
496
|
+
const itemPath = `${path}.${index}`;
|
|
497
|
+
if (isErrorMessages(item)) {
|
|
498
|
+
if (item.length > 0) {
|
|
499
|
+
flattened[itemPath] = cloneDeep(item);
|
|
500
|
+
}
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
this.flattenErrorValue(item, itemPath, flattened);
|
|
504
|
+
});
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (!isErrorObject(value)) {
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const rootErrors = value[''];
|
|
511
|
+
if (isErrorMessages(rootErrors) && rootErrors.length > 0) {
|
|
512
|
+
flattened[path] = cloneDeep(rootErrors);
|
|
513
|
+
}
|
|
514
|
+
for (const key of Object.keys(value)) {
|
|
515
|
+
if (key === '') {
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
const nestedPath = path.length > 0 ? `${path}.${key}` : key;
|
|
519
|
+
this.flattenErrorValue(value[key], nestedPath, flattened);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
flattenErrorsFromBag(errorBag) {
|
|
523
|
+
const flattened = {};
|
|
524
|
+
for (const key of Object.keys(errorBag)) {
|
|
525
|
+
this.flattenErrorValue(errorBag[key], key, flattened);
|
|
526
|
+
}
|
|
527
|
+
return flattened;
|
|
528
|
+
}
|
|
529
|
+
mergeErrorMessages(...messageSets) {
|
|
530
|
+
const merged = [];
|
|
531
|
+
for (const messages of messageSets) {
|
|
532
|
+
for (const message of messages) {
|
|
533
|
+
if (!merged.includes(message)) {
|
|
534
|
+
merged.push(message);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return merged;
|
|
539
|
+
}
|
|
540
|
+
flattenErrors() {
|
|
541
|
+
var _a;
|
|
542
|
+
const flattened = this.flattenErrorsFromBag(this._errors);
|
|
543
|
+
for (const [key, messages] of Object.entries(this.flattenErrorsFromBag(this._asyncErrors))) {
|
|
544
|
+
flattened[key] = this.mergeErrorMessages((_a = flattened[key]) !== null && _a !== void 0 ? _a : [], messages);
|
|
545
|
+
}
|
|
546
|
+
return flattened;
|
|
547
|
+
}
|
|
548
|
+
applyErrors(errorsData, useErrorMap, targetBag = this._errors) {
|
|
549
|
+
var _a, _b, _c;
|
|
550
|
+
for (const serverKey in errorsData) {
|
|
551
|
+
if (!Object.prototype.hasOwnProperty.call(errorsData, serverKey)) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
const errorMessage = errorsData[serverKey];
|
|
555
|
+
if (errorMessage === undefined) {
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
let targetKeys = [serverKey];
|
|
559
|
+
if (useErrorMap) {
|
|
560
|
+
const mapping = (_a = this.errorMap) === null || _a === void 0 ? void 0 : _a[serverKey];
|
|
561
|
+
if (mapping) {
|
|
562
|
+
targetKeys = Array.isArray(mapping) ? mapping : [mapping];
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
for (const targetKey of targetKeys) {
|
|
566
|
+
const parts = targetKey.split('.');
|
|
567
|
+
if (parts.length > 1) {
|
|
568
|
+
const topKey = (_b = parts[0]) !== null && _b !== void 0 ? _b : '';
|
|
569
|
+
const indexPart = (_c = parts[1]) !== null && _c !== void 0 ? _c : '';
|
|
570
|
+
const index = Number.parseInt(indexPart, 10);
|
|
571
|
+
if (!topKey) {
|
|
572
|
+
targetBag[targetKey] = errorMessage;
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
if (!Number.isFinite(index)) {
|
|
576
|
+
const current = isErrorObject(targetBag[topKey]) ? targetBag[topKey] : {};
|
|
577
|
+
targetBag[topKey] = current;
|
|
578
|
+
this.setNestedError(current, parts.slice(1), errorMessage);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
const errorSubKey = parts.slice(2).join('.');
|
|
582
|
+
const errors = this.getOrCreateErrorArray(topKey, targetBag);
|
|
583
|
+
const errorObject = this.getOrCreateErrorObject(errors, index);
|
|
584
|
+
if (errorSubKey.length === 0) {
|
|
585
|
+
errorObject[''] = errorMessage;
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
this.setNestedError(errorObject, errorSubKey.split('.'), errorMessage);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
targetBag[targetKey] = errorMessage;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
getValidationGroupPaths(group) {
|
|
598
|
+
const paths = this.validationGroups[group];
|
|
599
|
+
if (!paths || paths.length === 0) {
|
|
600
|
+
return [];
|
|
601
|
+
}
|
|
602
|
+
return [...paths];
|
|
603
|
+
}
|
|
604
|
+
matchesValidationGroupPath(errorKey, groupPath) {
|
|
605
|
+
return errorKey === groupPath || errorKey.startsWith(`${groupPath}.`);
|
|
606
|
+
}
|
|
607
|
+
errorKeyBelongsToGroup(errorKey, group) {
|
|
608
|
+
return this.getValidationGroupPaths(group).some((groupPath) => this.matchesValidationGroupPath(errorKey, groupPath));
|
|
609
|
+
}
|
|
610
|
+
getValidationGroupFields(group) {
|
|
611
|
+
const fields = [];
|
|
612
|
+
for (const path of this.getValidationGroupPaths(group)) {
|
|
613
|
+
const [topLevelField] = path.split('.');
|
|
614
|
+
if (!topLevelField || !(topLevelField in this.state)) {
|
|
615
|
+
continue;
|
|
616
|
+
}
|
|
617
|
+
const typedField = topLevelField;
|
|
618
|
+
if (!fields.includes(typedField)) {
|
|
619
|
+
fields.push(typedField);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return fields;
|
|
623
|
+
}
|
|
624
|
+
clearGroupErrors(group) {
|
|
625
|
+
const groupPaths = this.getValidationGroupPaths(group);
|
|
626
|
+
if (groupPaths.length === 0) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const preservedErrors = {};
|
|
630
|
+
for (const [errorKey, errorValue] of Object.entries(this.flattenErrors())) {
|
|
631
|
+
if (!this.errorKeyBelongsToGroup(errorKey, group)) {
|
|
632
|
+
preservedErrors[errorKey] = cloneDeep(errorValue);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
this.clearErrors();
|
|
636
|
+
this.applyErrors(preservedErrors, false);
|
|
637
|
+
}
|
|
638
|
+
collectSiblingNestedErrors(field, path) {
|
|
639
|
+
if (path.length === 0) {
|
|
640
|
+
return {};
|
|
641
|
+
}
|
|
642
|
+
const fieldKey = String(field);
|
|
643
|
+
const clearedPath = `${fieldKey}.${path.join('.')}`;
|
|
644
|
+
const preservedErrors = {};
|
|
645
|
+
for (const [errorKey, errorValue] of Object.entries(this.flattenErrors())) {
|
|
646
|
+
if (!this.matchesValidationGroupPath(errorKey, fieldKey)) {
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (errorKey === clearedPath || errorKey.startsWith(`${clearedPath}.`)) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
preservedErrors[errorKey] = cloneDeep(errorValue);
|
|
653
|
+
}
|
|
654
|
+
return preservedErrors;
|
|
655
|
+
}
|
|
656
|
+
validateFieldPreservingNestedErrors(field, path) {
|
|
657
|
+
const preservedErrors = this.collectSiblingNestedErrors(field, path);
|
|
658
|
+
this.validateField(field);
|
|
659
|
+
if (Object.keys(preservedErrors).length > 0) {
|
|
660
|
+
this.applyErrors(preservedErrors, false);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
clearErrorBagPaths(errorBag, paths) {
|
|
664
|
+
const preservedErrors = {};
|
|
665
|
+
for (const [errorKey, errorValue] of Object.entries(this.flattenErrorsFromBag(errorBag))) {
|
|
666
|
+
if (!paths.some((path) => this.matchesValidationGroupPath(errorKey, path))) {
|
|
667
|
+
preservedErrors[errorKey] = cloneDeep(errorValue);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
this.clearErrorBag(errorBag);
|
|
671
|
+
this.applyErrors(preservedErrors, false, errorBag);
|
|
672
|
+
}
|
|
673
|
+
getOrCreateErrorArray(key, errorBag = this._errors) {
|
|
674
|
+
const existing = errorBag[key];
|
|
364
675
|
if (isErrorArray(existing)) {
|
|
365
676
|
return existing;
|
|
366
677
|
}
|
|
367
678
|
const next = [];
|
|
368
|
-
|
|
679
|
+
errorBag[key] = next;
|
|
369
680
|
return next;
|
|
370
681
|
}
|
|
371
682
|
getOrCreateErrorObject(errors, index) {
|
|
@@ -378,8 +689,9 @@ export class BaseForm {
|
|
|
378
689
|
return next;
|
|
379
690
|
}
|
|
380
691
|
getFieldErrors(field) {
|
|
381
|
-
const
|
|
382
|
-
|
|
692
|
+
const syncErrors = this._errors[field];
|
|
693
|
+
const asyncErrors = this._asyncErrors[field];
|
|
694
|
+
return this.mergeErrorMessages(isErrorMessages(syncErrors) ? syncErrors : [], isErrorMessages(asyncErrors) ? asyncErrors : []);
|
|
383
695
|
}
|
|
384
696
|
getOrCreateFieldErrors(field) {
|
|
385
697
|
const existing = this._errors[field];
|
|
@@ -399,22 +711,24 @@ export class BaseForm {
|
|
|
399
711
|
return isErrorObject(item) ? item : undefined;
|
|
400
712
|
}
|
|
401
713
|
getArrayItemFieldErrors(field, index, innerKey) {
|
|
402
|
-
|
|
403
|
-
if (!itemErrors) {
|
|
404
|
-
return [];
|
|
405
|
-
}
|
|
406
|
-
const fieldErrors = itemErrors[innerKey];
|
|
407
|
-
if (isErrorMessages(fieldErrors)) {
|
|
408
|
-
return fieldErrors;
|
|
409
|
-
}
|
|
410
|
-
if (isErrorObject(fieldErrors)) {
|
|
411
|
-
const nestedErrors = fieldErrors[''];
|
|
412
|
-
return isErrorMessages(nestedErrors) ? nestedErrors : [];
|
|
413
|
-
}
|
|
414
|
-
return [];
|
|
714
|
+
return this.mergeErrorMessages(this.getNestedErrorMessagesFromValue(this.getArrayItemErrors(field, index), innerKey.split('.')), this.getNestedErrorMessagesFromValue(this.getArrayItemErrorsFromBag(this._asyncErrors, field, index), innerKey.split('.')));
|
|
415
715
|
}
|
|
416
716
|
getArrayItemErrorMessages(field, index) {
|
|
417
|
-
|
|
717
|
+
return this.mergeErrorMessages(this.getArrayItemErrorMessagesFromBag(this._errors, field, index), this.getArrayItemErrorMessagesFromBag(this._asyncErrors, field, index));
|
|
718
|
+
}
|
|
719
|
+
getObjectFieldErrors(field, path) {
|
|
720
|
+
return this.mergeErrorMessages(this.getNestedErrorMessagesFromValue(this._errors[field], path), this.getNestedErrorMessagesFromValue(this._asyncErrors[field], path));
|
|
721
|
+
}
|
|
722
|
+
getArrayItemErrorsFromBag(errorBag, field, index) {
|
|
723
|
+
const errors = errorBag[field];
|
|
724
|
+
if (!isErrorArray(errors)) {
|
|
725
|
+
return undefined;
|
|
726
|
+
}
|
|
727
|
+
const item = errors[index];
|
|
728
|
+
return isErrorObject(item) ? item : undefined;
|
|
729
|
+
}
|
|
730
|
+
getArrayItemErrorMessagesFromBag(errorBag, field, index) {
|
|
731
|
+
const errors = errorBag[field];
|
|
418
732
|
if (!Array.isArray(errors)) {
|
|
419
733
|
return [];
|
|
420
734
|
}
|
|
@@ -457,13 +771,7 @@ export class BaseForm {
|
|
|
457
771
|
return false;
|
|
458
772
|
}
|
|
459
773
|
const entry = dirtyState[index];
|
|
460
|
-
|
|
461
|
-
return entry;
|
|
462
|
-
}
|
|
463
|
-
if (isRecord(entry)) {
|
|
464
|
-
return entry[innerKey] === true;
|
|
465
|
-
}
|
|
466
|
-
return false;
|
|
774
|
+
return this.getNestedDirtyValue(entry, innerKey.split('.'));
|
|
467
775
|
}
|
|
468
776
|
getArrayItemDirtyValue(field, index) {
|
|
469
777
|
const dirtyState = this.dirty[field];
|
|
@@ -473,49 +781,229 @@ export class BaseForm {
|
|
|
473
781
|
const entry = dirtyState[index];
|
|
474
782
|
return typeof entry === 'boolean' ? entry : false;
|
|
475
783
|
}
|
|
784
|
+
getNestedErrorMessagesFromValue(value, path) {
|
|
785
|
+
if (path.length === 0) {
|
|
786
|
+
if (isErrorMessages(value)) {
|
|
787
|
+
return value;
|
|
788
|
+
}
|
|
789
|
+
if (isErrorObject(value)) {
|
|
790
|
+
const nestedErrors = value[''];
|
|
791
|
+
return isErrorMessages(nestedErrors) ? nestedErrors : [];
|
|
792
|
+
}
|
|
793
|
+
return [];
|
|
794
|
+
}
|
|
795
|
+
if (!isErrorObject(value)) {
|
|
796
|
+
return [];
|
|
797
|
+
}
|
|
798
|
+
const [segment, ...rest] = path;
|
|
799
|
+
if (segment === undefined) {
|
|
800
|
+
return [];
|
|
801
|
+
}
|
|
802
|
+
return this.getNestedErrorMessagesFromValue(value[segment], rest);
|
|
803
|
+
}
|
|
804
|
+
setNestedError(target, path, errorMessage) {
|
|
805
|
+
if (path.length === 0) {
|
|
806
|
+
target[''] = errorMessage;
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const [segment, ...rest] = path;
|
|
810
|
+
if (segment === undefined) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (rest.length === 0) {
|
|
814
|
+
target[segment] = errorMessage;
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
const next = isErrorObject(target[segment]) ? target[segment] : {};
|
|
818
|
+
target[segment] = next;
|
|
819
|
+
this.setNestedError(next, rest, errorMessage);
|
|
820
|
+
}
|
|
821
|
+
getNestedDirtyValue(value, path) {
|
|
822
|
+
if (path.length === 0) {
|
|
823
|
+
if (typeof value === 'boolean') {
|
|
824
|
+
return value;
|
|
825
|
+
}
|
|
826
|
+
if (Array.isArray(value)) {
|
|
827
|
+
return value.some((entry) => this.getNestedDirtyValue(entry, []));
|
|
828
|
+
}
|
|
829
|
+
if (isRecord(value)) {
|
|
830
|
+
return Object.values(value).some((item) => this.getNestedDirtyValue(item, []));
|
|
831
|
+
}
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
if (!isRecord(value)) {
|
|
835
|
+
return false;
|
|
836
|
+
}
|
|
837
|
+
const [segment, ...rest] = path;
|
|
838
|
+
if (segment === undefined) {
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
return this.getNestedDirtyValue(value[segment], rest);
|
|
842
|
+
}
|
|
843
|
+
createFieldProperty(getValue, setValue, getErrors, getDirty, getTouched) {
|
|
844
|
+
const field = {
|
|
845
|
+
model: computed({
|
|
846
|
+
get: getValue,
|
|
847
|
+
set: setValue
|
|
848
|
+
})
|
|
849
|
+
};
|
|
850
|
+
Object.defineProperties(field, {
|
|
851
|
+
errors: {
|
|
852
|
+
enumerable: true,
|
|
853
|
+
get: getErrors
|
|
854
|
+
},
|
|
855
|
+
dirty: {
|
|
856
|
+
enumerable: true,
|
|
857
|
+
get: getDirty
|
|
858
|
+
},
|
|
859
|
+
touched: {
|
|
860
|
+
enumerable: true,
|
|
861
|
+
get: getTouched
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
return field;
|
|
865
|
+
}
|
|
866
|
+
resolveArrayItemIndex(field, item) {
|
|
867
|
+
const value = this.state[field];
|
|
868
|
+
if (!(value instanceof PropertyAwareArray)) {
|
|
869
|
+
return -1;
|
|
870
|
+
}
|
|
871
|
+
return value.indexOf(item);
|
|
872
|
+
}
|
|
873
|
+
getArrayItemValueByPath(field, item, path) {
|
|
874
|
+
const index = this.resolveArrayItemIndex(field, item);
|
|
875
|
+
if (index < 0) {
|
|
876
|
+
return undefined;
|
|
877
|
+
}
|
|
878
|
+
let current = this.state[field][index];
|
|
879
|
+
for (const segment of path) {
|
|
880
|
+
if (!isRecord(current)) {
|
|
881
|
+
return undefined;
|
|
882
|
+
}
|
|
883
|
+
current = current[segment];
|
|
884
|
+
}
|
|
885
|
+
return current;
|
|
886
|
+
}
|
|
887
|
+
setArrayItemValueByPath(field, item, path, value) {
|
|
888
|
+
const index = this.resolveArrayItemIndex(field, item);
|
|
889
|
+
if (index < 0) {
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (path.length === 0) {
|
|
893
|
+
;
|
|
894
|
+
this.state[field][index] = value;
|
|
895
|
+
const updatedElement = this.state[field][index];
|
|
896
|
+
const originalElement = this.original[field][index];
|
|
897
|
+
this.setArrayDirty(field, index, this.computeDirtyState(updatedElement, originalElement));
|
|
898
|
+
this.touched[field] = true;
|
|
899
|
+
this.validateField(field);
|
|
900
|
+
this.validateDependentFields(field);
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const currentItem = this.state[field][index];
|
|
904
|
+
if (!isRecord(currentItem)) {
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
let current = currentItem;
|
|
908
|
+
const segments = [...path];
|
|
909
|
+
const last = segments.pop();
|
|
910
|
+
if (last === undefined) {
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
for (const segment of segments) {
|
|
914
|
+
const next = current[segment];
|
|
915
|
+
if (!isRecord(next)) {
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
current = next;
|
|
919
|
+
}
|
|
920
|
+
current[last] = value;
|
|
921
|
+
const updatedElement = this.state[field][index];
|
|
922
|
+
const originalElement = this.original[field][index];
|
|
923
|
+
this.setArrayDirty(field, index, this.computeDirtyState(updatedElement, originalElement));
|
|
924
|
+
this.touched[field] = true;
|
|
925
|
+
this.validateFieldPreservingNestedErrors(field, [String(index), ...path]);
|
|
926
|
+
this.validateDependentFields(field);
|
|
927
|
+
}
|
|
928
|
+
createObjectWrapperFromShape(field, shape, getValueByPath, setValueByPath, getErrorsByPath, getDirtyByPath, getTouched) {
|
|
929
|
+
const wrapper = {};
|
|
930
|
+
const shapeRecord = shape;
|
|
931
|
+
for (const innerKey of Object.keys(shape)) {
|
|
932
|
+
const child = shapeRecord[innerKey];
|
|
933
|
+
if (isPropertyAwareObject(child)) {
|
|
934
|
+
wrapper[innerKey] = this.createObjectWrapperFromShape(field, child, (path) => getValueByPath([innerKey, ...path]), (path, value) => setValueByPath([innerKey, ...path], value), (path) => getErrorsByPath([innerKey, ...path]), (path) => getDirtyByPath([innerKey, ...path]), getTouched);
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
wrapper[innerKey] = this.createFieldProperty(() => getValueByPath([innerKey]), (newValue) => setValueByPath([innerKey], newValue), () => getErrorsByPath([innerKey]), () => getDirtyByPath([innerKey]), getTouched);
|
|
938
|
+
}
|
|
939
|
+
return wrapper;
|
|
940
|
+
}
|
|
941
|
+
getOrCreateArrayItemWrapper(field, item) {
|
|
942
|
+
let fieldCache = this.arrayItemWrapperCache.get(field);
|
|
943
|
+
if (!fieldCache) {
|
|
944
|
+
fieldCache = new WeakMap();
|
|
945
|
+
this.arrayItemWrapperCache.set(field, fieldCache);
|
|
946
|
+
}
|
|
947
|
+
const existing = fieldCache.get(item);
|
|
948
|
+
if (existing) {
|
|
949
|
+
return existing;
|
|
950
|
+
}
|
|
951
|
+
const wrapper = {};
|
|
952
|
+
if (isRecord(item)) {
|
|
953
|
+
for (const innerKey of Object.keys(item)) {
|
|
954
|
+
const child = item[innerKey];
|
|
955
|
+
if (isPropertyAwareObject(child)) {
|
|
956
|
+
wrapper[innerKey] = this.createObjectWrapperFromShape(field, child, (path) => this.getArrayItemValueByPath(field, item, [innerKey, ...path]), (path, value) => this.setArrayItemValueByPath(field, item, [innerKey, ...path], value), (path) => {
|
|
957
|
+
const index = this.resolveArrayItemIndex(field, item);
|
|
958
|
+
return index < 0 ? [] : this.getArrayItemFieldErrors(String(field), index, [innerKey, ...path].join('.'));
|
|
959
|
+
}, (path) => {
|
|
960
|
+
var _a;
|
|
961
|
+
const index = this.resolveArrayItemIndex(field, item);
|
|
962
|
+
return index < 0
|
|
963
|
+
? false
|
|
964
|
+
: this.getNestedDirtyValue((_a = this.dirty[field]) === null || _a === void 0 ? void 0 : _a[index], [innerKey, ...path]);
|
|
965
|
+
}, () => this.touched[field] || false);
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
968
|
+
wrapper[innerKey] = this.createFieldProperty(() => this.getArrayItemValueByPath(field, item, [innerKey]), (newValue) => this.setArrayItemValueByPath(field, item, [innerKey], newValue), () => {
|
|
969
|
+
const index = this.resolveArrayItemIndex(field, item);
|
|
970
|
+
return index < 0 ? [] : this.getArrayItemFieldErrors(String(field), index, innerKey);
|
|
971
|
+
}, () => {
|
|
972
|
+
const index = this.resolveArrayItemIndex(field, item);
|
|
973
|
+
return index < 0 ? false : this.getArrayItemDirty(field, index, innerKey);
|
|
974
|
+
}, () => this.touched[field] || false);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
wrapper['value'] = this.createFieldProperty(() => this.getArrayItemValueByPath(field, item, []), (newValue) => this.setArrayItemValueByPath(field, item, [], newValue), () => {
|
|
979
|
+
const index = this.resolveArrayItemIndex(field, item);
|
|
980
|
+
return index < 0 ? [] : this.getArrayItemErrorMessages(String(field), index);
|
|
981
|
+
}, () => {
|
|
982
|
+
const index = this.resolveArrayItemIndex(field, item);
|
|
983
|
+
return index < 0 ? false : this.getArrayItemDirtyValue(field, index);
|
|
984
|
+
}, () => this.touched[field] || false);
|
|
985
|
+
}
|
|
986
|
+
fieldCache.set(item, wrapper);
|
|
987
|
+
return wrapper;
|
|
988
|
+
}
|
|
989
|
+
getOrCreateArrayWrappers(field, value) {
|
|
990
|
+
let wrappers = this.arrayWrapperCache.get(field);
|
|
991
|
+
if (!wrappers) {
|
|
992
|
+
wrappers = [];
|
|
993
|
+
this.arrayWrapperCache.set(field, wrappers);
|
|
994
|
+
}
|
|
995
|
+
wrappers.length = 0;
|
|
996
|
+
value.forEach((item) => {
|
|
997
|
+
wrappers.push(this.getOrCreateArrayItemWrapper(field, item));
|
|
998
|
+
});
|
|
999
|
+
return wrappers;
|
|
1000
|
+
}
|
|
476
1001
|
/**
|
|
477
1002
|
* Map server-side errors (including dot-notation paths) into the form error bag.
|
|
478
1003
|
*/
|
|
479
1004
|
fillErrors(errorsData) {
|
|
480
|
-
var _a, _b, _c;
|
|
481
1005
|
this.clearErrors();
|
|
482
|
-
|
|
483
|
-
if (Object.prototype.hasOwnProperty.call(errorsData, serverKey)) {
|
|
484
|
-
const errorMessage = errorsData[serverKey];
|
|
485
|
-
if (errorMessage === undefined) {
|
|
486
|
-
continue;
|
|
487
|
-
}
|
|
488
|
-
let targetKeys = [serverKey];
|
|
489
|
-
const mapping = (_a = this.errorMap) === null || _a === void 0 ? void 0 : _a[serverKey];
|
|
490
|
-
if (mapping) {
|
|
491
|
-
targetKeys = Array.isArray(mapping) ? mapping : [mapping];
|
|
492
|
-
}
|
|
493
|
-
for (const targetKey of targetKeys) {
|
|
494
|
-
const parts = targetKey.split('.');
|
|
495
|
-
if (parts.length > 1) {
|
|
496
|
-
const topKey = (_b = parts[0]) !== null && _b !== void 0 ? _b : '';
|
|
497
|
-
const indexPart = (_c = parts[1]) !== null && _c !== void 0 ? _c : '';
|
|
498
|
-
const index = Number.parseInt(indexPart, 10);
|
|
499
|
-
if (!topKey || !Number.isFinite(index)) {
|
|
500
|
-
this._errors[targetKey] = errorMessage;
|
|
501
|
-
continue;
|
|
502
|
-
}
|
|
503
|
-
const errorSubKey = parts.slice(2).join('.');
|
|
504
|
-
const errors = this.getOrCreateErrorArray(topKey);
|
|
505
|
-
const errorObject = this.getOrCreateErrorObject(errors, index);
|
|
506
|
-
if (errorSubKey.length === 0) {
|
|
507
|
-
errorObject[''] = errorMessage;
|
|
508
|
-
}
|
|
509
|
-
else {
|
|
510
|
-
errorObject[errorSubKey] = errorMessage;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
else {
|
|
514
|
-
this._errors[targetKey] = errorMessage;
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
}
|
|
1006
|
+
this.applyErrors(errorsData, true);
|
|
519
1007
|
}
|
|
520
1008
|
/**
|
|
521
1009
|
* Mark a field as touched, which indicates user interaction
|
|
@@ -572,19 +1060,22 @@ export class BaseForm {
|
|
|
572
1060
|
this.getOrCreateFieldErrors(errorKey).push(rule.getMessage());
|
|
573
1061
|
}
|
|
574
1062
|
}
|
|
1063
|
+
if (!context.skipAsyncValidation) {
|
|
1064
|
+
this.scheduleAsyncValidation(field, context);
|
|
1065
|
+
}
|
|
575
1066
|
}
|
|
576
1067
|
}
|
|
577
|
-
validate(isSubmitting = false) {
|
|
1068
|
+
validate(isSubmitting = false, options = {}) {
|
|
1069
|
+
var _a;
|
|
578
1070
|
let isValid = true;
|
|
579
|
-
|
|
580
|
-
delete this._errors[key];
|
|
581
|
-
}
|
|
1071
|
+
this.clearSyncErrors();
|
|
582
1072
|
for (const field in this.rules) {
|
|
583
1073
|
if (Object.prototype.hasOwnProperty.call(this.rules, field)) {
|
|
584
1074
|
this.validateField(field, {
|
|
585
1075
|
isSubmitting,
|
|
586
1076
|
isDependentChange: false,
|
|
587
|
-
isTouched: this.isTouched(field)
|
|
1077
|
+
isTouched: this.isTouched(field),
|
|
1078
|
+
skipAsyncValidation: (_a = options.skipAsyncValidation) !== null && _a !== void 0 ? _a : false
|
|
588
1079
|
});
|
|
589
1080
|
const fieldErrors = this._errors[String(field)];
|
|
590
1081
|
if (isErrorMessages(fieldErrors) && fieldErrors.length > 0) {
|
|
@@ -594,6 +1085,99 @@ export class BaseForm {
|
|
|
594
1085
|
}
|
|
595
1086
|
return isValid;
|
|
596
1087
|
}
|
|
1088
|
+
validateGroup(group, isSubmitting = false, options = {}) {
|
|
1089
|
+
var _a;
|
|
1090
|
+
const fields = this.getValidationGroupFields(group);
|
|
1091
|
+
if (fields.length === 0) {
|
|
1092
|
+
return true;
|
|
1093
|
+
}
|
|
1094
|
+
this.clearGroupErrors(group);
|
|
1095
|
+
for (const field of fields) {
|
|
1096
|
+
this.validateField(field, {
|
|
1097
|
+
isSubmitting,
|
|
1098
|
+
isDependentChange: false,
|
|
1099
|
+
isTouched: this.isTouched(field),
|
|
1100
|
+
skipAsyncValidation: (_a = options.skipAsyncValidation) !== null && _a !== void 0 ? _a : false
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
return !this.hasErrorsInGroup(group);
|
|
1104
|
+
}
|
|
1105
|
+
runFieldAsyncValidation(field_1) {
|
|
1106
|
+
return __awaiter(this, arguments, void 0, function* (field, context = {}, expectedToken) {
|
|
1107
|
+
var _a;
|
|
1108
|
+
if (!context.skipSyncValidation) {
|
|
1109
|
+
this.validateField(field, Object.assign(Object.assign({}, context), { skipAsyncValidation: true }));
|
|
1110
|
+
}
|
|
1111
|
+
const fieldConfig = this.rules[field];
|
|
1112
|
+
if (!(fieldConfig === null || fieldConfig === void 0 ? void 0 : fieldConfig.rules) || fieldConfig.rules.length === 0) {
|
|
1113
|
+
return !Object.keys(this.flattenErrors()).some((errorKey) => this.matchesValidationGroupPath(errorKey, String(field)));
|
|
1114
|
+
}
|
|
1115
|
+
const asyncPaths = fieldConfig.rules.flatMap((rule) => rule.getAsyncValidationPaths(field, this.state));
|
|
1116
|
+
const payload = this.buildPayload();
|
|
1117
|
+
const nextAsyncErrors = {};
|
|
1118
|
+
for (const rule of fieldConfig.rules) {
|
|
1119
|
+
const errors = yield rule.validateAsync(this.state[field], this.state, {
|
|
1120
|
+
field: String(field),
|
|
1121
|
+
payload,
|
|
1122
|
+
isSubmitting: (_a = context.isSubmitting) !== null && _a !== void 0 ? _a : false
|
|
1123
|
+
});
|
|
1124
|
+
if (errors && Object.keys(errors).length > 0) {
|
|
1125
|
+
this.applyErrors(errors, false, nextAsyncErrors);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (expectedToken !== undefined && this.asyncValidationTokens[String(field)] !== expectedToken) {
|
|
1129
|
+
return !Object.keys(this.flattenErrors()).some((errorKey) => this.matchesValidationGroupPath(errorKey, String(field)));
|
|
1130
|
+
}
|
|
1131
|
+
this.clearErrorBagPaths(this._asyncErrors, asyncPaths);
|
|
1132
|
+
for (const [key, messages] of Object.entries(this.flattenErrorsFromBag(nextAsyncErrors))) {
|
|
1133
|
+
this.applyErrors({ [key]: messages }, false, this._asyncErrors);
|
|
1134
|
+
}
|
|
1135
|
+
return !Object.keys(this.flattenErrors()).some((errorKey) => this.matchesValidationGroupPath(errorKey, String(field)));
|
|
1136
|
+
});
|
|
1137
|
+
}
|
|
1138
|
+
validateFieldAsync(field_1) {
|
|
1139
|
+
return __awaiter(this, arguments, void 0, function* (field, context = {}) {
|
|
1140
|
+
const token = this.bumpAsyncValidationToken(field);
|
|
1141
|
+
return yield this.runFieldAsyncValidation(field, Object.assign(Object.assign({}, context), { skipAsyncValidation: true }), token);
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
validateAsync() {
|
|
1145
|
+
return __awaiter(this, arguments, void 0, function* (isSubmitting = false) {
|
|
1146
|
+
const isSyncValid = this.validate(isSubmitting, { skipAsyncValidation: true });
|
|
1147
|
+
for (const field in this.rules) {
|
|
1148
|
+
if (Object.prototype.hasOwnProperty.call(this.rules, field)) {
|
|
1149
|
+
yield this.validateFieldAsync(field, {
|
|
1150
|
+
isSubmitting,
|
|
1151
|
+
isTouched: this.isTouched(field),
|
|
1152
|
+
skipSyncValidation: true
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
return isSyncValid && !this.hasErrors();
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
validateGroupAsync(group_1) {
|
|
1160
|
+
return __awaiter(this, arguments, void 0, function* (group, isSubmitting = false) {
|
|
1161
|
+
const fields = this.getValidationGroupFields(group);
|
|
1162
|
+
if (fields.length === 0) {
|
|
1163
|
+
return true;
|
|
1164
|
+
}
|
|
1165
|
+
const isSyncValid = this.validateGroup(group, isSubmitting, { skipAsyncValidation: true });
|
|
1166
|
+
for (const field of fields) {
|
|
1167
|
+
yield this.validateFieldAsync(field, {
|
|
1168
|
+
isSubmitting,
|
|
1169
|
+
isTouched: this.isTouched(field),
|
|
1170
|
+
skipSyncValidation: true
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
return isSyncValid && !this.hasErrorsInGroup(group);
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
touchGroup(group) {
|
|
1177
|
+
for (const field of this.getValidationGroupFields(group)) {
|
|
1178
|
+
this.touch(field);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
597
1181
|
fillState(data) {
|
|
598
1182
|
var _a;
|
|
599
1183
|
const driver = this.getPersistenceDriver((_a = this.options) === null || _a === void 0 ? void 0 : _a.persistSuffix);
|
|
@@ -606,7 +1190,13 @@ export class BaseForm {
|
|
|
606
1190
|
if (currentVal instanceof PropertyAwareArray) {
|
|
607
1191
|
const values = newVal instanceof PropertyAwareArray || Array.isArray(newVal) ? Array.from(newVal) : [];
|
|
608
1192
|
this.replacePropertyAwareArray(key, values);
|
|
609
|
-
this.dirty[key] =
|
|
1193
|
+
this.dirty[key] = this.computeDirtyState(this.state[key], this.original[key]);
|
|
1194
|
+
this.touched[key] = true;
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
if (isPropertyAwareObject(currentVal)) {
|
|
1198
|
+
this.state[key] = restorePropertyAwareStructure(currentVal, newVal);
|
|
1199
|
+
this.dirty[key] = this.computeDirtyState(this.state[key], this.original[key]);
|
|
610
1200
|
this.touched[key] = true;
|
|
611
1201
|
continue;
|
|
612
1202
|
}
|
|
@@ -659,6 +1249,17 @@ export class BaseForm {
|
|
|
659
1249
|
if (value instanceof PropertyAwareArray) {
|
|
660
1250
|
return [...value].map((item) => this.transformValue(item, parentKey));
|
|
661
1251
|
}
|
|
1252
|
+
if (isPropertyAwareObject(value)) {
|
|
1253
|
+
const result = {};
|
|
1254
|
+
const valueRecord = value;
|
|
1255
|
+
for (const prop in valueRecord) {
|
|
1256
|
+
const transformed = this.transformValue(valueRecord[prop], parentKey);
|
|
1257
|
+
if (transformed !== undefined) {
|
|
1258
|
+
result[prop] = transformed;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
return result;
|
|
1262
|
+
}
|
|
662
1263
|
if (Array.isArray(value)) {
|
|
663
1264
|
return value.map((item) => this.transformValue(item, parentKey));
|
|
664
1265
|
}
|
|
@@ -742,6 +1343,11 @@ export class BaseForm {
|
|
|
742
1343
|
this.dirty[typedKey] = values.map(() => false);
|
|
743
1344
|
this.touched[typedKey] = false;
|
|
744
1345
|
}
|
|
1346
|
+
else if (isPropertyAwareObject(this.state[key])) {
|
|
1347
|
+
this.state[key] = restorePropertyAwareStructure(this.original[key], cloneDeep(this.original[key]));
|
|
1348
|
+
this.dirty[key] = false;
|
|
1349
|
+
this.touched[key] = false;
|
|
1350
|
+
}
|
|
745
1351
|
else if (Array.isArray(this.original[key])) {
|
|
746
1352
|
this.state[key] = cloneDeep(this.original[key]);
|
|
747
1353
|
this.dirty[key] = this.computeDirtyState(this.state[key], this.original[key]);
|
|
@@ -753,9 +1359,7 @@ export class BaseForm {
|
|
|
753
1359
|
this.touched[key] = false;
|
|
754
1360
|
}
|
|
755
1361
|
}
|
|
756
|
-
|
|
757
|
-
delete this._errors[key];
|
|
758
|
-
}
|
|
1362
|
+
this.clearErrors();
|
|
759
1363
|
this.persistState(driver);
|
|
760
1364
|
this.validate();
|
|
761
1365
|
}
|
|
@@ -820,65 +1424,47 @@ export class BaseForm {
|
|
|
820
1424
|
for (const key of Object.keys(this.state)) {
|
|
821
1425
|
const value = this.state[key];
|
|
822
1426
|
if (value instanceof PropertyAwareArray) {
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
},
|
|
833
|
-
set: (newVal) => {
|
|
834
|
-
const current = value[index];
|
|
835
|
-
if (isRecord(current)) {
|
|
836
|
-
current[innerKey] = newVal;
|
|
837
|
-
}
|
|
838
|
-
const updatedElement = value[index];
|
|
839
|
-
const originalElement = this.original[key][index];
|
|
840
|
-
this.setArrayDirty(key, index, this.computeDirtyState(updatedElement, originalElement));
|
|
841
|
-
this.touched[key] = true;
|
|
842
|
-
this.validateField(key);
|
|
843
|
-
this.validateDependentFields(key);
|
|
844
|
-
}
|
|
845
|
-
}),
|
|
846
|
-
errors: this.getArrayItemFieldErrors(key, index, innerKey),
|
|
847
|
-
dirty: this.getArrayItemDirty(key, index, innerKey),
|
|
848
|
-
touched: this.touched[key] || false
|
|
849
|
-
};
|
|
1427
|
+
props[key] = this.getOrCreateArrayWrappers(key, value);
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
if (isPropertyAwareObject(value)) {
|
|
1431
|
+
props[key] = this.createObjectWrapperFromShape(key, value, (path) => {
|
|
1432
|
+
let current = this.state[key];
|
|
1433
|
+
for (const segment of path) {
|
|
1434
|
+
if (!isRecord(current)) {
|
|
1435
|
+
return undefined;
|
|
850
1436
|
}
|
|
851
|
-
|
|
1437
|
+
current = current[segment];
|
|
1438
|
+
}
|
|
1439
|
+
return current;
|
|
1440
|
+
}, (path, newValue) => {
|
|
1441
|
+
const current = this.state[key];
|
|
1442
|
+
if (!isRecord(current)) {
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
const segments = [...path];
|
|
1446
|
+
const last = segments.pop();
|
|
1447
|
+
if (last === undefined) {
|
|
1448
|
+
return;
|
|
852
1449
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
set: (newVal) => {
|
|
858
|
-
value[index] = newVal;
|
|
859
|
-
const updatedValue = value[index];
|
|
860
|
-
const originalValue = this.original[key][index];
|
|
861
|
-
this.setArrayDirty(key, index, this.computeDirtyState(updatedValue, originalValue));
|
|
862
|
-
this.touched[key] = true;
|
|
863
|
-
this.validateField(key);
|
|
864
|
-
this.validateDependentFields(key);
|
|
865
|
-
}
|
|
866
|
-
}),
|
|
867
|
-
errors: this.getArrayItemErrorMessages(key, index),
|
|
868
|
-
dirty: this.getArrayItemDirtyValue(key, index),
|
|
869
|
-
touched: this.touched[key] || false
|
|
1450
|
+
let record = current;
|
|
1451
|
+
for (const segment of segments) {
|
|
1452
|
+
if (!isRecord(record[segment])) {
|
|
1453
|
+
return;
|
|
870
1454
|
}
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1455
|
+
record = record[segment];
|
|
1456
|
+
}
|
|
1457
|
+
record[last] = newValue;
|
|
1458
|
+
this.dirty[key] = this.computeDirtyState(this.state[key], this.original[key]);
|
|
1459
|
+
this.touched[key] = true;
|
|
1460
|
+
this.validateFieldPreservingNestedErrors(key, path);
|
|
1461
|
+
this.validateDependentFields(key);
|
|
1462
|
+
}, (path) => this.getObjectFieldErrors(String(key), path), (path) => this.getNestedDirtyValue(this.dirty[key], path), () => this.touched[key] || false);
|
|
874
1463
|
continue;
|
|
875
1464
|
}
|
|
876
|
-
props[key] = {
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
dirty: this.dirty[key] || false,
|
|
880
|
-
touched: this.touched[key] || false
|
|
881
|
-
};
|
|
1465
|
+
props[key] = this.createFieldProperty(() => this._model[key].value, (newValue) => {
|
|
1466
|
+
this._model[key].value = newValue;
|
|
1467
|
+
}, () => this.getFieldErrors(key), () => this.dirty[key] || false, () => this.touched[key] || false);
|
|
882
1468
|
}
|
|
883
1469
|
return props;
|
|
884
1470
|
}
|
|
@@ -889,42 +1475,10 @@ export class BaseForm {
|
|
|
889
1475
|
*/
|
|
890
1476
|
isDirty(field) {
|
|
891
1477
|
if (field !== undefined) {
|
|
892
|
-
|
|
893
|
-
if (typeof dirtyState === 'boolean') {
|
|
894
|
-
return dirtyState;
|
|
895
|
-
}
|
|
896
|
-
if (Array.isArray(dirtyState)) {
|
|
897
|
-
return dirtyState.some((item) => {
|
|
898
|
-
if (typeof item === 'boolean') {
|
|
899
|
-
return item;
|
|
900
|
-
}
|
|
901
|
-
if (item && typeof item === 'object') {
|
|
902
|
-
return Object.values(item).some((v) => v === true);
|
|
903
|
-
}
|
|
904
|
-
return false;
|
|
905
|
-
});
|
|
906
|
-
}
|
|
907
|
-
if (dirtyState && typeof dirtyState === 'object') {
|
|
908
|
-
return Object.values(dirtyState).some((v) => v === true);
|
|
909
|
-
}
|
|
910
|
-
return false;
|
|
1478
|
+
return this.getNestedDirtyValue(this.dirty[field], []);
|
|
911
1479
|
}
|
|
912
1480
|
for (const key in this.dirty) {
|
|
913
|
-
|
|
914
|
-
if (typeof dirtyState === 'boolean' && dirtyState) {
|
|
915
|
-
return true;
|
|
916
|
-
}
|
|
917
|
-
if (Array.isArray(dirtyState)) {
|
|
918
|
-
for (const item of dirtyState) {
|
|
919
|
-
if (typeof item === 'boolean' && item) {
|
|
920
|
-
return true;
|
|
921
|
-
}
|
|
922
|
-
if (item && typeof item === 'object' && Object.values(item).some((v) => v === true)) {
|
|
923
|
-
return true;
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
if (dirtyState && typeof dirtyState === 'object' && Object.values(dirtyState).some((v) => v === true)) {
|
|
1481
|
+
if (this.getNestedDirtyValue(this.dirty[key], [])) {
|
|
928
1482
|
return true;
|
|
929
1483
|
}
|
|
930
1484
|
}
|
|
@@ -937,6 +1491,21 @@ export class BaseForm {
|
|
|
937
1491
|
hasErrors() {
|
|
938
1492
|
return this._hasErrors.value;
|
|
939
1493
|
}
|
|
1494
|
+
getErrors() {
|
|
1495
|
+
return this.flattenErrors();
|
|
1496
|
+
}
|
|
1497
|
+
hasErrorsInGroup(group) {
|
|
1498
|
+
return Object.keys(this.flattenErrors()).some((errorKey) => this.errorKeyBelongsToGroup(errorKey, group));
|
|
1499
|
+
}
|
|
1500
|
+
getErrorsInGroup(group) {
|
|
1501
|
+
const groupErrors = {};
|
|
1502
|
+
for (const [errorKey, errorMessages] of Object.entries(this.flattenErrors())) {
|
|
1503
|
+
if (this.errorKeyBelongsToGroup(errorKey, group)) {
|
|
1504
|
+
groupErrors[errorKey] = cloneDeep(errorMessages);
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
return groupErrors;
|
|
1508
|
+
}
|
|
940
1509
|
/**
|
|
941
1510
|
* Updates both the state and original value for a given property,
|
|
942
1511
|
* keeping the field in a clean (not dirty) state.
|