@lion/ui 0.4.0-prerelease-bypass-export-map.0 → 0.4.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/components/accordion/src/LionAccordion.js +20 -3
- package/components/accordion/test/lion-accordion.test.js +19 -0
- package/components/calendar/src/LionCalendar.js +5 -2
- package/components/calendar/src/utils/createMonth.js +1 -2
- package/components/calendar/test/lion-calendar.test.js +83 -4
- package/components/calendar/test/utils/createMonth.test.js +15 -0
- package/components/form-core/src/form-group/FormGroupMixin.js +7 -0
- package/components/form-core/src/validate/ValidateMixin.js +197 -153
- package/components/form-core/test-suites/ValidateMixin.suite.js +4 -4
- package/components/form-core/test-suites/form-group/FormGroupMixin-input.suite.js +1 -1
- package/components/form-core/test-suites/form-group/FormGroupMixin.suite.js +22 -7
- package/components/input-datepicker/src/LionInputDatepicker.js +4 -4
- package/components/input-datepicker/test/lion-input-datepicker.test.js +23 -0
- package/components/localize/types/LocalizeMixinTypes.ts +1 -1
- package/components/overlays/src/OverlayController.js +2 -4
- package/components/progress-indicator/src/LionProgressIndicator.js +1 -1
- package/components/progress-indicator/test/lion-progress-indicator.test.js +18 -1
- package/custom-elements.json +442 -140
- package/dist-types/components/accordion/src/LionAccordion.d.ts +10 -0
- package/dist-types/components/accordion/src/LionAccordion.d.ts.map +1 -1
- package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
- package/dist-types/components/calendar/src/utils/createMonth.d.ts.map +1 -1
- package/dist-types/components/form-core/src/form-group/FormGroupMixin.d.ts.map +1 -1
- package/dist-types/components/form-core/src/validate/ValidateMixin.d.ts +1 -0
- package/dist-types/components/form-core/src/validate/ValidateMixin.d.ts.map +1 -1
- package/dist-types/components/form-core/test-suites/form-group/FormGroupMixin.suite.d.ts.map +1 -1
- package/dist-types/components/input-file/src/LionInputFile.d.ts +1 -1
- package/dist-types/components/input-file/src/LionInputFile.d.ts.map +1 -1
- package/dist-types/components/input-file/src/LionSelectedFileList.d.ts +1 -1
- package/dist-types/components/input-file/src/LionSelectedFileList.d.ts.map +1 -1
- package/dist-types/components/input-tel/src/LionInputTel.d.ts +1 -1
- package/dist-types/components/input-tel/src/LionInputTel.d.ts.map +1 -1
- package/dist-types/components/localize/types/LocalizeMixinTypes.d.ts +1 -1
- package/dist-types/components/localize/types/LocalizeMixinTypes.d.ts.map +1 -1
- package/dist-types/components/overlays/src/OverlayController.d.ts.map +1 -1
- package/dist-types/exports/types/calendar.d.ts +2 -0
- package/dist-types/exports/types/calendar.d.ts.map +1 -0
- package/dist-types/exports/types/combobox.d.ts +2 -0
- package/dist-types/exports/types/combobox.d.ts.map +1 -0
- package/dist-types/exports/types/listbox.d.ts +3 -0
- package/dist-types/exports/types/listbox.d.ts.map +1 -0
- package/dist-types/exports/types/localize.d.ts +1 -4
- package/dist-types/exports/types/localize.d.ts.map +1 -1
- package/dist-types/tsconfig.tsbuildinfo +1 -1
- package/docs/components/checkbox-group/overview.md +2 -2
- package/docs/components/collapsible/overview.md +1 -1
- package/docs/components/combobox/overview.md +1 -1
- package/docs/components/combobox/use-cases.md +1 -1
- package/docs/components/dialog/overview.md +1 -1
- package/docs/components/fieldset/overview.md +5 -5
- package/docs/components/form/overview.md +3 -3
- package/docs/components/icon/use-cases.md +1 -1
- package/docs/components/input/overview.md +1 -1
- package/docs/components/input-amount/overview.md +3 -3
- package/docs/components/input-date/overview.md +3 -3
- package/docs/components/input-datepicker/overview.md +5 -5
- package/docs/components/input-email/overview.md +2 -2
- package/docs/components/input-file/overview.md +1 -1
- package/docs/components/input-iban/overview.md +2 -2
- package/docs/components/input-range/overview.md +2 -2
- package/docs/components/input-stepper/overview.md +1 -1
- package/docs/components/input-tel/overview.md +1 -1
- package/docs/components/input-tel/use-cases.md +6 -6
- package/docs/components/input-tel-dropdown/overview.md +1 -1
- package/docs/components/radio-group/overview.md +3 -3
- package/docs/components/select-rich/use-cases.md +1 -1
- package/docs/components/tabs/examples.md +1 -1
- package/docs/fundamentals/index.md +4 -4
- package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
- package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
- package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
- package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
- package/docs/fundamentals/rationales/accessibility.md +1 -1
- package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
- package/docs/fundamentals/systems/core/overview.md +1 -1
- package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
- package/docs/fundamentals/systems/form/model-value.md +1 -1
- package/docs/fundamentals/systems/form/overview.md +28 -28
- package/docs/fundamentals/systems/form/validate.md +1 -1
- package/docs/fundamentals/systems/localize/overview.md +4 -4
- package/docs/fundamentals/systems/overlays/configuration.md +2 -2
- package/docs/fundamentals/systems/overlays/overview.md +5 -5
- package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
- package/docs/fundamentals/tools/helpers/overview.md +1 -1
- package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
- package/docs/guides/how-to/create-a-custom-field.md +5 -5
- package/docs/guides/how-to/create-a-form.md +4 -4
- package/docs/guides/how-to/extend-a-native-input.md +2 -2
- package/docs/guides/index.md +8 -8
- package/docs/guides/principles/index.md +5 -5
- package/docs/guides/principles/styling.md +1 -1
- package/exports/types/calendar.ts +1 -0
- package/exports/types/combobox.ts +1 -0
- package/exports/types/listbox.ts +2 -0
- package/exports/types/localize.ts +13 -4
- package/package.json +1 -1
|
@@ -8,7 +8,7 @@ import { AsyncQueue } from '../utils/AsyncQueue.js';
|
|
|
8
8
|
import { pascalCase } from '../utils/pascalCase.js';
|
|
9
9
|
import { SyncUpdatableMixin } from '../utils/SyncUpdatableMixin.js';
|
|
10
10
|
import { LionValidationFeedback } from './LionValidationFeedback.js';
|
|
11
|
-
import { ResultValidator } from './ResultValidator.js';
|
|
11
|
+
import { ResultValidator as MetaValidator } from './ResultValidator.js';
|
|
12
12
|
import { Unparseable } from './Unparseable.js';
|
|
13
13
|
import { Validator } from './Validator.js';
|
|
14
14
|
import { Required } from './validators/Required.js';
|
|
@@ -20,6 +20,7 @@ import { FormControlMixin } from '../FormControlMixin.js';
|
|
|
20
20
|
* @typedef {import('../../types/validate/ValidateMixinTypes.js').ValidateMixin} ValidateMixin
|
|
21
21
|
* @typedef {import('../../types/validate/ValidateMixinTypes.js').ValidationType} ValidationType
|
|
22
22
|
* @typedef {import('../../types/validate/ValidateMixinTypes.js').ValidateHost} ValidateHost
|
|
23
|
+
* @typedef {import('../../types/validate/index.js').ValidatorOutcome} ValidatorOutcome
|
|
23
24
|
* @typedef {typeof import('../../types/validate/ValidateMixinTypes.js').ValidateHost} ValidateHostConstructor
|
|
24
25
|
* @typedef {{validator:Validator; outcome:boolean|string}} ValidationResultEntry
|
|
25
26
|
* @typedef {{[type:string]: {[validatorName:string]:boolean|string}}} ValidationStates
|
|
@@ -33,6 +34,15 @@ function arrayDiff(array1 = [], array2 = []) {
|
|
|
33
34
|
return array1.filter(x => !array2.includes(x)).concat(array2.filter(x => !array1.includes(x)));
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* When the modelValue can't be created by FormatMixin.parser, still allow all validators
|
|
39
|
+
* to give valuable feedback to the user based on the current viewValue.
|
|
40
|
+
* @param {any} modelValue
|
|
41
|
+
*/
|
|
42
|
+
function getValueForValidators(modelValue) {
|
|
43
|
+
return modelValue instanceof Unparseable ? modelValue.viewValue : modelValue;
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
/**
|
|
37
47
|
* Handles all validation, based on modelValue changes. It has no knowledge about dom and
|
|
38
48
|
* UI. All error visibility, dom interaction and accessibility are handled in FeedbackMixin.
|
|
@@ -46,17 +56,13 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
46
56
|
SyncUpdatableMixin(DisabledMixin(SlotMixin(ScopedElementsMixin(superclass)))),
|
|
47
57
|
) {
|
|
48
58
|
static get scopedElements() {
|
|
49
|
-
const scopedElementsCtor =
|
|
50
|
-
/** @type {typeof import('@open-wc/scoped-elements/types.js').ScopedElementsHost} */ (
|
|
51
|
-
super.constructor
|
|
52
|
-
);
|
|
53
59
|
return {
|
|
54
|
-
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
...super.scopedElements,
|
|
55
62
|
'lion-validation-feedback': LionValidationFeedback,
|
|
56
63
|
};
|
|
57
64
|
}
|
|
58
65
|
|
|
59
|
-
/** @type {any} */
|
|
60
66
|
static get properties() {
|
|
61
67
|
return {
|
|
62
68
|
validators: { attribute: false },
|
|
@@ -93,8 +99,8 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
93
99
|
}
|
|
94
100
|
|
|
95
101
|
/**
|
|
96
|
-
* @overridable
|
|
97
102
|
* Adds "._feedbackNode" as described below
|
|
103
|
+
* @public
|
|
98
104
|
*/
|
|
99
105
|
get slots() {
|
|
100
106
|
/**
|
|
@@ -229,7 +235,7 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
229
235
|
this.__asyncValidationResult = [];
|
|
230
236
|
|
|
231
237
|
/**
|
|
232
|
-
* Aggregated result from sync Validators, async Validators and
|
|
238
|
+
* Aggregated result from sync Validators, async Validators and MetaValidators
|
|
233
239
|
* @type {ValidationResultEntry[]}
|
|
234
240
|
* @private
|
|
235
241
|
*/
|
|
@@ -242,6 +248,7 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
242
248
|
this.__prevValidationResult = [];
|
|
243
249
|
|
|
244
250
|
/**
|
|
251
|
+
* The shown validation result depends on the visibility of the feedback messages
|
|
245
252
|
* @type {ValidationResultEntry[]}
|
|
246
253
|
* @private
|
|
247
254
|
*/
|
|
@@ -279,7 +286,7 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
279
286
|
*/
|
|
280
287
|
firstUpdated(changedProperties) {
|
|
281
288
|
super.firstUpdated(changedProperties);
|
|
282
|
-
this.
|
|
289
|
+
this.__isValidateInitialized = true;
|
|
283
290
|
this.validate();
|
|
284
291
|
if (this._repropagationRole !== 'child') {
|
|
285
292
|
this.addEventListener('model-value-changed', () => {
|
|
@@ -360,8 +367,8 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
360
367
|
* Executions are scheduled and awaited and the 'async results' are merged with the
|
|
361
368
|
* 'sync results'.
|
|
362
369
|
*
|
|
363
|
-
* - b) there are
|
|
364
|
-
*
|
|
370
|
+
* - b) there are MetaValidators. After steps a1, a2, or a3 are finished, the holistic
|
|
371
|
+
* MetaValidators (evaluating the total result of the 'regular' (a1, a2 and a3) validators)
|
|
365
372
|
* will be run...
|
|
366
373
|
*
|
|
367
374
|
* Situations a2 and a3 are not mutually exclusive and can be triggered within one `validate()`
|
|
@@ -369,14 +376,27 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
369
376
|
*
|
|
370
377
|
* @param {{ clearCurrentResult?: boolean }} opts
|
|
371
378
|
*/
|
|
372
|
-
async validate({ clearCurrentResult } = {}) {
|
|
379
|
+
async validate({ clearCurrentResult = false } = {}) {
|
|
380
|
+
/**
|
|
381
|
+
* Allows Application Developer to wait for (async) validation
|
|
382
|
+
* @example
|
|
383
|
+
* ```js
|
|
384
|
+
* await el.validateComplete;
|
|
385
|
+
* ```
|
|
386
|
+
* @type {Promise<boolean>}
|
|
387
|
+
*/
|
|
388
|
+
this.validateComplete = new Promise(resolve => {
|
|
389
|
+
this.__validateCompleteResolve = resolve;
|
|
390
|
+
});
|
|
391
|
+
|
|
373
392
|
if (this.disabled) {
|
|
374
393
|
this.__clearValidationResults();
|
|
375
|
-
this.
|
|
394
|
+
this.__finishValidationPass();
|
|
376
395
|
this._updateFeedbackComponent();
|
|
377
396
|
return;
|
|
378
397
|
}
|
|
379
|
-
|
|
398
|
+
// We don't validate before firstUpdated has run
|
|
399
|
+
if (!this.__isValidateInitialized) {
|
|
380
400
|
return;
|
|
381
401
|
}
|
|
382
402
|
|
|
@@ -394,131 +414,151 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
394
414
|
* @desc step a1-3 + b (as explained in `validate()`)
|
|
395
415
|
*/
|
|
396
416
|
async __executeValidators() {
|
|
397
|
-
|
|
398
|
-
* Allows Application Developer to wait for (async) validation
|
|
399
|
-
* @example
|
|
400
|
-
* ```js
|
|
401
|
-
* await el.validateComplete;
|
|
402
|
-
* ```
|
|
403
|
-
* @type {Promise<boolean>}
|
|
404
|
-
*/
|
|
405
|
-
this.validateComplete = new Promise(resolve => {
|
|
406
|
-
this.__validateCompleteResolve = resolve;
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
// When the modelValue can't be created by FormatMixin.parser, still allow all validators
|
|
410
|
-
// to give valuable feedback to the user based on the current viewValue.
|
|
411
|
-
const value =
|
|
412
|
-
this.modelValue instanceof Unparseable ? this.modelValue.viewValue : this.modelValue;
|
|
413
|
-
|
|
414
|
-
/** @type {Validator | undefined} */
|
|
415
|
-
const requiredValidator = this._allValidators.find(v => v instanceof Required);
|
|
417
|
+
const value = getValueForValidators(this.modelValue);
|
|
416
418
|
|
|
417
419
|
/**
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
420
|
+
* Handle the 'mutually exclusive' Required validator:
|
|
421
|
+
*
|
|
422
|
+
* About the Required Validator:
|
|
423
|
+
* - the validity is dependent on the formControls' modelValue and therefore determined
|
|
424
|
+
* by the formControl._isEmpty method. Basically, the Required Validator is a means
|
|
425
|
+
* to trigger formControl._isEmpty().
|
|
426
|
+
*
|
|
427
|
+
* About the empty state:
|
|
428
|
+
* - when _isEmpty returns true, the modelValue is considered empty. This means we need to stop
|
|
423
429
|
* validation here, because all other Validators' execute functions assume the
|
|
424
430
|
* value is not empty (there would be nothing to validate).
|
|
431
|
+
*
|
|
425
432
|
*/
|
|
426
|
-
|
|
427
|
-
// depend on FormControlMixin directly, and _isEmpty will always be an existing method on the prototype then
|
|
433
|
+
|
|
428
434
|
const isEmpty = this.__isEmpty(value);
|
|
435
|
+
this.__syncValidationResult = [];
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* So we can have the following scenarios:
|
|
439
|
+
* - we're empty
|
|
440
|
+
* - we have a single value (we are an 'endpoint') => we want to halt execution, because validation only makes sense when we have a value
|
|
441
|
+
* - 1. we have Required => we fill .__syncValidationResult and finish validation, because we have a value to validate
|
|
442
|
+
* - 2. we don't have Required => we stop validation
|
|
443
|
+
* - we have a group of values (object/array) => we want to continue execution, because the constellation of the object (even though the individual endpoints are empty) might be interesting for validation.
|
|
444
|
+
* - 3. we have Required => we fill .__syncValidationResult and continue validation (so we can add more to .__syncValidationResult)
|
|
445
|
+
* - 4. we don't have Required => we continue validation
|
|
446
|
+
* - we're not empty
|
|
447
|
+
* - we have a single value or group of values
|
|
448
|
+
* - 5. we may have Required, but if we have it will not be 'active' => we continue execution, because we have a value to validate
|
|
449
|
+
*/
|
|
450
|
+
|
|
429
451
|
if (isEmpty) {
|
|
452
|
+
const hasSingleValue = !(/** @type {* & ValidateHost} */ (this)._isFormOrFieldset);
|
|
453
|
+
const requiredValidator = this._allValidators.find(v => v instanceof Required);
|
|
430
454
|
if (requiredValidator) {
|
|
431
455
|
this.__syncValidationResult = [{ validator: requiredValidator, outcome: true }];
|
|
432
456
|
}
|
|
433
|
-
|
|
434
|
-
|
|
457
|
+
|
|
458
|
+
if (hasSingleValue) {
|
|
459
|
+
this.__finishValidationPass({
|
|
460
|
+
syncValidationResult: this.__syncValidationResult,
|
|
461
|
+
});
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
435
464
|
}
|
|
436
465
|
|
|
437
|
-
|
|
438
|
-
const /** @type {Validator[]} */
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
})
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
}
|
|
466
|
+
const metaValidators = /** @type {MetaValidator[]} */ [];
|
|
467
|
+
const syncValidators = /** @type {Validator[]} */ [];
|
|
468
|
+
const asyncValidators = /** @type {Validator[]} */ [];
|
|
469
|
+
|
|
470
|
+
for (const v of this._allValidators) {
|
|
471
|
+
if (v instanceof MetaValidator) {
|
|
472
|
+
metaValidators.push(v);
|
|
473
|
+
} else if (v instanceof Required) {
|
|
474
|
+
// Required validator was already handled
|
|
475
|
+
} else if (/** @type {typeof Validator} */ (v.constructor).async) {
|
|
476
|
+
asyncValidators.push(v);
|
|
477
|
+
} else {
|
|
478
|
+
syncValidators.push(v);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const hasAsync = Boolean(asyncValidators.length);
|
|
449
483
|
|
|
450
484
|
/**
|
|
451
485
|
* 2. Synchronous validators
|
|
452
486
|
*/
|
|
453
|
-
|
|
454
|
-
|
|
487
|
+
|
|
488
|
+
this.__syncValidationResult = [
|
|
489
|
+
// This could eventually contain the Required Validator
|
|
490
|
+
...this.__syncValidationResult,
|
|
491
|
+
...this.__executeSyncValidators(syncValidators, value),
|
|
492
|
+
];
|
|
493
|
+
// Finish the first (synchronous) pass
|
|
494
|
+
this.__finishValidationPass({
|
|
495
|
+
syncValidationResult: this.__syncValidationResult,
|
|
496
|
+
metaValidators,
|
|
455
497
|
});
|
|
456
498
|
|
|
457
499
|
/**
|
|
458
500
|
* 3. Asynchronous validators
|
|
459
501
|
*/
|
|
460
|
-
|
|
502
|
+
if (hasAsync) {
|
|
503
|
+
// Add a hint in the ui that we're waiting for async validation
|
|
504
|
+
this.isPending = true;
|
|
505
|
+
this.__asyncValidationResult = await this.__executeAsyncValidators(asyncValidators, value);
|
|
506
|
+
this.isPending = false;
|
|
507
|
+
// Now finish the second (asynchronous) pass (including both sync and async results)
|
|
508
|
+
this.__finishValidationPass({
|
|
509
|
+
syncValidationResult: this.__syncValidationResult,
|
|
510
|
+
asyncValidationResult: this.__asyncValidationResult,
|
|
511
|
+
metaValidators,
|
|
512
|
+
});
|
|
513
|
+
this.__validateCompleteResolve?.(true);
|
|
514
|
+
} else {
|
|
515
|
+
this.__validateCompleteResolve?.(true);
|
|
516
|
+
}
|
|
461
517
|
}
|
|
462
518
|
|
|
463
519
|
/**
|
|
464
|
-
* step a2 (as explained in `validate()`): calls `
|
|
520
|
+
* step a2 (as explained in `validate()`): calls `__finishValidationPass`
|
|
465
521
|
* @param {Validator[]} syncValidators
|
|
466
522
|
* @param {unknown} value
|
|
467
|
-
* @param {{ hasAsync: boolean }} opts
|
|
468
523
|
* @private
|
|
469
524
|
*/
|
|
470
|
-
__executeSyncValidators(syncValidators, value
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
.filter(v => Boolean(v.outcome));
|
|
480
|
-
}
|
|
481
|
-
this.__finishValidation({ source: 'sync', hasAsync });
|
|
525
|
+
__executeSyncValidators(syncValidators, value) {
|
|
526
|
+
return syncValidators
|
|
527
|
+
.map(v => ({
|
|
528
|
+
validator: v,
|
|
529
|
+
// TODO: fix this type - ts things this is not a FormControlHost?
|
|
530
|
+
// @ts-ignore
|
|
531
|
+
outcome: /** @type {boolean|string} */ (v.execute(value, v.param, { node: this })),
|
|
532
|
+
}))
|
|
533
|
+
.filter(v => Boolean(v.outcome));
|
|
482
534
|
}
|
|
483
535
|
|
|
484
536
|
/**
|
|
485
|
-
* step a3 (as explained in `validate()`), calls
|
|
486
|
-
* @param {Validator[]} asyncValidators all Validators except required and
|
|
537
|
+
* step a3 (as explained in `validate()`), calls __finishValidationPass
|
|
538
|
+
* @param {Validator[]} asyncValidators all Validators except required and MetaValidators
|
|
487
539
|
* @param {?} value
|
|
488
540
|
* @private
|
|
489
541
|
*/
|
|
490
542
|
async __executeAsyncValidators(asyncValidators, value) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const resultPromises = asyncValidators.map(v => v.execute(value, v.param, { node: this }));
|
|
494
|
-
const asyncExecutionResults = await Promise.all(resultPromises);
|
|
543
|
+
const outcomePromises = asyncValidators.map(v => v.execute(value, v.param, { node: this }));
|
|
544
|
+
const asyncExecutionResults = await Promise.all(outcomePromises);
|
|
495
545
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
this.__finishValidation({ source: 'async' });
|
|
504
|
-
this.isPending = false;
|
|
505
|
-
}
|
|
546
|
+
return asyncExecutionResults
|
|
547
|
+
.map((r, i) => ({
|
|
548
|
+
validator: asyncValidators[i],
|
|
549
|
+
outcome: /** @type {boolean|string} */ (asyncExecutionResults[i]),
|
|
550
|
+
}))
|
|
551
|
+
.filter(v => Boolean(v.outcome));
|
|
506
552
|
}
|
|
507
553
|
|
|
508
554
|
/**
|
|
509
|
-
* step b (as explained in `validate()`), called by
|
|
555
|
+
* step b (as explained in `validate()`), called by __finishValidationPass
|
|
510
556
|
* @param {{validator: Validator;outcome: boolean | string;}[]} regularValidationResult result of steps 1-3
|
|
557
|
+
* @param {MetaValidator[]} metaValidators
|
|
511
558
|
* @private
|
|
512
559
|
*/
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
this._allValidators.filter(v => {
|
|
516
|
-
const vCtor = /** @type {typeof Validator} */ (v.constructor);
|
|
517
|
-
return !vCtor.async && v instanceof ResultValidator;
|
|
518
|
-
})
|
|
519
|
-
);
|
|
520
|
-
|
|
521
|
-
if (!resultValidators.length) {
|
|
560
|
+
__executeMetaValidators(regularValidationResult, metaValidators) {
|
|
561
|
+
if (!metaValidators.length) {
|
|
522
562
|
return [];
|
|
523
563
|
}
|
|
524
564
|
|
|
@@ -529,7 +569,7 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
529
569
|
}
|
|
530
570
|
|
|
531
571
|
// Map everything to Validator[] for backwards compatibility
|
|
532
|
-
return
|
|
572
|
+
return metaValidators
|
|
533
573
|
.map(v => ({
|
|
534
574
|
validator: v,
|
|
535
575
|
outcome: /** @type {boolean|string} */ (
|
|
@@ -546,19 +586,35 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
546
586
|
}
|
|
547
587
|
|
|
548
588
|
/**
|
|
589
|
+
* A 'pass' is a single run of the validation process, which will be triggered in these cases:
|
|
590
|
+
* - on clear or disable
|
|
591
|
+
* - on sync validation
|
|
592
|
+
* - on async validation (can depend on server response)
|
|
593
|
+
*
|
|
594
|
+
* This method inishes a pass by adding the properties to the instance:
|
|
595
|
+
* - validationStates
|
|
596
|
+
* - hasFeedbackFor
|
|
597
|
+
*
|
|
598
|
+
* It sends a private event validate-performed, which is received by parent Formgroups.
|
|
599
|
+
*
|
|
549
600
|
* @param {object} options
|
|
550
|
-
* @param {
|
|
551
|
-
* @param {
|
|
601
|
+
* @param {ValidationResultEntry[]} [options.syncValidationResult]
|
|
602
|
+
* @param {ValidationResultEntry[]} [options.asyncValidationResult]
|
|
603
|
+
* @param {MetaValidator[]} [options.metaValidators] MetaValidators to be executed
|
|
552
604
|
* @private
|
|
553
605
|
* If not, we have nothing left to wait for.
|
|
554
606
|
*/
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
607
|
+
__finishValidationPass({
|
|
608
|
+
syncValidationResult = [],
|
|
609
|
+
asyncValidationResult = [],
|
|
610
|
+
metaValidators = [],
|
|
611
|
+
} = {}) {
|
|
612
|
+
const syncAndAsyncOutcome = [...syncValidationResult, ...asyncValidationResult];
|
|
613
|
+
// if we have any MetaValidators left, now is the time to run them...
|
|
614
|
+
const metaOutCome = /** @type {ValidationResultEntry[]} */ (
|
|
615
|
+
this.__executeMetaValidators(syncAndAsyncOutcome, metaValidators)
|
|
560
616
|
);
|
|
561
|
-
this.__validationResult = [...
|
|
617
|
+
this.__validationResult = [...metaOutCome, ...syncAndAsyncOutcome];
|
|
562
618
|
|
|
563
619
|
const ctor = /** @type {ValidateHostConstructor} */ (this.constructor);
|
|
564
620
|
|
|
@@ -567,13 +623,13 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
567
623
|
(acc, type) => ({ ...acc, [type]: {} }),
|
|
568
624
|
{},
|
|
569
625
|
);
|
|
570
|
-
|
|
626
|
+
for (const { validator, outcome } of this.__validationResult) {
|
|
571
627
|
if (!validationStates[validator.type]) {
|
|
572
628
|
validationStates[validator.type] = {};
|
|
573
629
|
}
|
|
574
630
|
const vCtor = /** @type {typeof Validator} */ (validator.constructor);
|
|
575
631
|
validationStates[validator.type][vCtor.validatorName] = outcome;
|
|
576
|
-
}
|
|
632
|
+
}
|
|
577
633
|
this.validationStates = validationStates;
|
|
578
634
|
|
|
579
635
|
this.hasFeedbackFor = [
|
|
@@ -581,11 +637,6 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
581
637
|
];
|
|
582
638
|
/** private event that should be listened to by LionFieldSet */
|
|
583
639
|
this.dispatchEvent(new Event('validate-performed', { bubbles: true }));
|
|
584
|
-
if (source === 'async' || !hasAsync) {
|
|
585
|
-
if (this.__validateCompleteResolve) {
|
|
586
|
-
this.__validateCompleteResolve(true);
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
640
|
}
|
|
590
641
|
|
|
591
642
|
/**
|
|
@@ -611,51 +662,49 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
611
662
|
*/
|
|
612
663
|
__setupValidators() {
|
|
613
664
|
const events = ['param-changed', 'config-changed'];
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
),
|
|
625
|
-
);
|
|
626
|
-
});
|
|
665
|
+
|
|
666
|
+
for (const validatorToCleanup of this.__prevValidators || []) {
|
|
667
|
+
for (const eventToCleanup of events) {
|
|
668
|
+
validatorToCleanup.removeEventListener?.(eventToCleanup, this._onValidatorUpdated);
|
|
669
|
+
}
|
|
670
|
+
validatorToCleanup.onFormControlDisconnect(
|
|
671
|
+
/** @type {import('../../types/FormControlMixinTypes.js').FormControlHost} */ (
|
|
672
|
+
/** @type {unknown} */ (this)
|
|
673
|
+
),
|
|
674
|
+
);
|
|
627
675
|
}
|
|
628
|
-
|
|
629
|
-
|
|
676
|
+
|
|
677
|
+
for (const validatorToSetup of this._allValidators) {
|
|
678
|
+
if (!(validatorToSetup instanceof Validator)) {
|
|
630
679
|
// throws in constructor are not visible to end user so we do both
|
|
631
|
-
const errorType = Array.isArray(
|
|
632
|
-
const errorMessage = `Validators array only accepts class instances of Validator. Type "${errorType}" found. This may be caused by having multiple installations of @lion/form-core.`;
|
|
680
|
+
const errorType = Array.isArray(validatorToSetup) ? 'array' : typeof validatorToSetup;
|
|
681
|
+
const errorMessage = `Validators array only accepts class instances of Validator. Type "${errorType}" found. This may be caused by having multiple installations of "@lion/ui/form-core.js".`;
|
|
633
682
|
// eslint-disable-next-line no-console
|
|
634
683
|
console.error(errorMessage, this);
|
|
635
684
|
throw new Error(errorMessage);
|
|
636
685
|
}
|
|
637
686
|
const ctor = /** @type {ValidateHostConstructor} */ (this.constructor);
|
|
638
|
-
|
|
639
|
-
|
|
687
|
+
const vCtor = /** @type {typeof Validator} */ (validatorToSetup.constructor);
|
|
688
|
+
if (ctor.validationTypes.indexOf(validatorToSetup.type) === -1) {
|
|
640
689
|
// throws in constructor are not visible to end user so we do both
|
|
641
|
-
const errorMessage = `This component does not support the validator type "${
|
|
690
|
+
const errorMessage = `This component does not support the validator type "${validatorToSetup.type}" used in "${vCtor.validatorName}". You may change your validators type or add it to the components "static get validationTypes() {}".`;
|
|
642
691
|
// eslint-disable-next-line no-console
|
|
643
692
|
console.error(errorMessage, this);
|
|
644
693
|
throw new Error(errorMessage);
|
|
645
694
|
}
|
|
646
|
-
|
|
647
|
-
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Updated the code to fix issue #1607 to sync the calendar date with validators params
|
|
698
|
+
* Here _onValidatorUpdated is responsible for responding to the event
|
|
648
699
|
*/
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
v.onFormControlConnect(this);
|
|
658
|
-
});
|
|
700
|
+
for (const eventToSetup of events) {
|
|
701
|
+
validatorToSetup.addEventListener?.(eventToSetup, e => {
|
|
702
|
+
// @ts-ignore for making validator param dynamic
|
|
703
|
+
this._onValidatorUpdated(e, { validator: validatorToSetup });
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
validatorToSetup.onFormControlConnect(this);
|
|
707
|
+
}
|
|
659
708
|
this.__prevValidators = this._allValidators;
|
|
660
709
|
}
|
|
661
710
|
|
|
@@ -759,7 +808,7 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
759
808
|
}
|
|
760
809
|
|
|
761
810
|
const messageMap = await this.__getFeedbackMessages(this.__prioritizedResult);
|
|
762
|
-
_feedbackNode.feedbackData = messageMap
|
|
811
|
+
_feedbackNode.feedbackData = messageMap || [];
|
|
763
812
|
});
|
|
764
813
|
} else {
|
|
765
814
|
this.__feedbackQueue.add(async () => {
|
|
@@ -827,12 +876,7 @@ export const ValidateMixinImplementation = superclass =>
|
|
|
827
876
|
* @protected
|
|
828
877
|
*/
|
|
829
878
|
_hasFeedbackVisibleFor(type) {
|
|
830
|
-
return (
|
|
831
|
-
this.hasFeedbackFor &&
|
|
832
|
-
this.hasFeedbackFor.includes(type) &&
|
|
833
|
-
this.shouldShowFeedbackFor &&
|
|
834
|
-
this.shouldShowFeedbackFor.includes(type)
|
|
835
|
-
);
|
|
879
|
+
return this.hasFeedbackFor?.includes(type) && this.shouldShowFeedbackFor?.includes(type);
|
|
836
880
|
}
|
|
837
881
|
|
|
838
882
|
/**
|
|
@@ -77,7 +77,7 @@ export function runValidateMixinSuite(customConfig) {
|
|
|
77
77
|
const stub = sinon.stub(console, 'error');
|
|
78
78
|
const el = /** @type {ValidateElement} */ (await fixture(html`<${tag}></${tag}>`));
|
|
79
79
|
const errorMessage =
|
|
80
|
-
'Validators array only accepts class instances of Validator. Type "array" found. This may be caused by having multiple installations of @lion/form-core.';
|
|
80
|
+
'Validators array only accepts class instances of Validator. Type "array" found. This may be caused by having multiple installations of "@lion/ui/form-core.js".';
|
|
81
81
|
expect(() => {
|
|
82
82
|
// @ts-expect-error putting the wrong value on purpose
|
|
83
83
|
el.validators = [[new Required()]];
|
|
@@ -85,7 +85,7 @@ export function runValidateMixinSuite(customConfig) {
|
|
|
85
85
|
expect(stub.args[0][0]).to.equal(errorMessage);
|
|
86
86
|
|
|
87
87
|
const errorMessage2 =
|
|
88
|
-
'Validators array only accepts class instances of Validator. Type "string" found. This may be caused by having multiple installations of @lion/form-core.';
|
|
88
|
+
'Validators array only accepts class instances of Validator. Type "string" found. This may be caused by having multiple installations of "@lion/ui/form-core.js".';
|
|
89
89
|
expect(() => {
|
|
90
90
|
// @ts-expect-error because we purposely put a wrong type
|
|
91
91
|
el.validators = ['required'];
|
|
@@ -322,7 +322,7 @@ export function runValidateMixinSuite(customConfig) {
|
|
|
322
322
|
// @ts-ignore [allow-private] in test
|
|
323
323
|
const syncSpy = sinon.spy(el, '__executeSyncValidators');
|
|
324
324
|
// @ts-ignore [allow-private] in test
|
|
325
|
-
const resultSpy2 = sinon.spy(el, '
|
|
325
|
+
const resultSpy2 = sinon.spy(el, '__executeMetaValidators');
|
|
326
326
|
|
|
327
327
|
el.modelValue = 'nonEmpty';
|
|
328
328
|
expect(syncSpy.calledBefore(resultSpy2)).to.be.true;
|
|
@@ -337,7 +337,7 @@ export function runValidateMixinSuite(customConfig) {
|
|
|
337
337
|
// @ts-ignore [allow-private] in test
|
|
338
338
|
const asyncSpy = sinon.spy(el, '__executeAsyncValidators');
|
|
339
339
|
// @ts-ignore [allow-private] in test
|
|
340
|
-
const resultSpy = sinon.spy(el, '
|
|
340
|
+
const resultSpy = sinon.spy(el, '__executeMetaValidators');
|
|
341
341
|
|
|
342
342
|
el.modelValue = 'nonEmpty';
|
|
343
343
|
expect(resultSpy.callCount).to.equal(1);
|
|
@@ -313,7 +313,7 @@ export function runFormGroupMixinInputSuite(cfg = {}) {
|
|
|
313
313
|
});
|
|
314
314
|
|
|
315
315
|
// TODO: wait for updated on disconnected to be fixed: https://github.com/lit/lit/issues/1901
|
|
316
|
-
it.skip(`cleans up feedback message belonging to fieldset
|
|
316
|
+
it.skip(`cleans up feedback message belonging to fieldset on disconnect`, async () => {
|
|
317
317
|
const el = await childAriaFixture('feedback');
|
|
318
318
|
await childAriaTest(el, { cleanupPhase: true });
|
|
319
319
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LitElement } from 'lit';
|
|
2
|
-
import { IsNumber, LionField, Validator, FormGroupMixin } from '@lion/ui/form-core.js';
|
|
2
|
+
import { IsNumber, Required, LionField, Validator, FormGroupMixin } from '@lion/ui/form-core.js';
|
|
3
3
|
import '@lion/ui/define/lion-field.js';
|
|
4
4
|
import '@lion/ui/define/lion-validation-feedback.js';
|
|
5
5
|
|
|
@@ -577,6 +577,25 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|
|
577
577
|
expect(el.validationStates.error).to.deep.equal({});
|
|
578
578
|
});
|
|
579
579
|
|
|
580
|
+
it('works with Required', async () => {
|
|
581
|
+
const el = /** @type {FormGroup} */ (
|
|
582
|
+
await fixture(html`
|
|
583
|
+
<${tag} name="myGroup" .validators="${[new Required()]}">
|
|
584
|
+
<${childTag} name="fieldA"></${childTag}>
|
|
585
|
+
<${childTag} name="fieldB"></${childTag}>
|
|
586
|
+
</${tag}>
|
|
587
|
+
`)
|
|
588
|
+
);
|
|
589
|
+
// initially the group is invalid
|
|
590
|
+
expect(el.validationStates.error.Required).to.be.true;
|
|
591
|
+
el.formElements.fieldA.modelValue = 'foo';
|
|
592
|
+
// if at least one child is filled, the group is valid
|
|
593
|
+
expect(el.validationStates.error.Required).to.be.undefined;
|
|
594
|
+
// make Required trigger error state again
|
|
595
|
+
el.formElements.fieldA.modelValue = '';
|
|
596
|
+
expect(el.validationStates.error.Required).to.be.true;
|
|
597
|
+
});
|
|
598
|
+
|
|
580
599
|
it('validates on children (de)registration', async () => {
|
|
581
600
|
class HasEvenNumberOfChildren extends Validator {
|
|
582
601
|
static get validatorName() {
|
|
@@ -1129,9 +1148,7 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|
|
1129
1148
|
|
|
1130
1149
|
it('has correct validation afterwards', async () => {
|
|
1131
1150
|
class IsCat extends Validator {
|
|
1132
|
-
static
|
|
1133
|
-
return 'IsCat';
|
|
1134
|
-
}
|
|
1151
|
+
static validatorName = 'IsCat';
|
|
1135
1152
|
|
|
1136
1153
|
/**
|
|
1137
1154
|
* @param {string} value
|
|
@@ -1142,9 +1159,7 @@ export function runFormGroupMixinSuite(cfg = {}) {
|
|
|
1142
1159
|
}
|
|
1143
1160
|
}
|
|
1144
1161
|
class ColorContainsA extends Validator {
|
|
1145
|
-
static
|
|
1146
|
-
return 'ColorContainsA';
|
|
1147
|
-
}
|
|
1162
|
+
static validatorName = 'ColorContainsA';
|
|
1148
1163
|
|
|
1149
1164
|
/**
|
|
1150
1165
|
* @param {{ [x:string]:any }} value
|