vanilla-jui 1.5.9 → 1.5.11
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/index.d.ts +4 -1
- package/dist/index.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/flow.ts +181 -77
- package/src/components/form.ts +103 -60
- package/src/css/form.css +33 -23
- package/src/primitives/toast.ts +3 -2
- package/src/validation/validator.ts +104 -12
|
@@ -44,17 +44,20 @@ interface ValidatorRule extends Record<string, unknown> {
|
|
|
44
44
|
interface ValidatorProps extends Record<string, unknown> {
|
|
45
45
|
rules?: Record<string, ValidatorRule>;
|
|
46
46
|
messages?: ValidatorMessageMap;
|
|
47
|
+
vanilla?: boolean;
|
|
47
48
|
onSubmit?: ((validator: ValidatorInstance) => void) | null;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
interface ResolvedValidatorProps extends Record<string, unknown> {
|
|
51
52
|
rules: Record<string, ValidatorRule>;
|
|
52
53
|
messages: ValidatorMessageMap;
|
|
54
|
+
vanilla: boolean;
|
|
53
55
|
onSubmit: ((validator: ValidatorInstance) => void) | null;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
interface ValidatorRuntime {
|
|
57
59
|
valid: boolean;
|
|
60
|
+
error: boolean;
|
|
58
61
|
message: string;
|
|
59
62
|
destroyed: boolean;
|
|
60
63
|
}
|
|
@@ -75,6 +78,7 @@ export interface ValidatorInstance {
|
|
|
75
78
|
const VALIDATOR_PROPS_SCHEMA = {
|
|
76
79
|
rules: { default: {}, type: 'object' },
|
|
77
80
|
messages: { default: {}, type: 'object' },
|
|
81
|
+
vanilla: { default: false, type: 'boolean' },
|
|
78
82
|
onSubmit: { default: null, types: ['function', 'null'] },
|
|
79
83
|
} satisfies ResolveSchema<ValidatorProps>;
|
|
80
84
|
|
|
@@ -83,6 +87,7 @@ function normalizeProps(input: ValidatorProps): ResolvedValidatorProps {
|
|
|
83
87
|
return {
|
|
84
88
|
rules: props.rules as Record<string, ValidatorRule>,
|
|
85
89
|
messages: props.messages as ValidatorMessageMap,
|
|
90
|
+
vanilla: props.vanilla as boolean,
|
|
86
91
|
onSubmit: props.onSubmit as ResolvedValidatorProps['onSubmit'],
|
|
87
92
|
};
|
|
88
93
|
}
|
|
@@ -111,6 +116,7 @@ function toValidatorElement(
|
|
|
111
116
|
function createRuntime(): ValidatorRuntime {
|
|
112
117
|
return {
|
|
113
118
|
valid: true,
|
|
119
|
+
error: false,
|
|
114
120
|
message: '',
|
|
115
121
|
destroyed: false,
|
|
116
122
|
};
|
|
@@ -118,7 +124,8 @@ function createRuntime(): ValidatorRuntime {
|
|
|
118
124
|
|
|
119
125
|
function getControlContainer(element: ValidatorElement): HTMLElement | null {
|
|
120
126
|
return (
|
|
121
|
-
element.closest<HTMLElement>('[data-
|
|
127
|
+
element.closest<HTMLElement>('[data-field-control]') ||
|
|
128
|
+
element.parentElement
|
|
122
129
|
);
|
|
123
130
|
}
|
|
124
131
|
|
|
@@ -218,18 +225,65 @@ function validateMultiple(
|
|
|
218
225
|
return element.selectedOptions.length > 0;
|
|
219
226
|
}
|
|
220
227
|
|
|
221
|
-
function
|
|
222
|
-
|
|
223
|
-
|
|
228
|
+
function checkboxGroup(
|
|
229
|
+
root: HTMLFormElement | null,
|
|
230
|
+
element: ValidatorElement
|
|
231
|
+
): HTMLInputElement[] {
|
|
232
|
+
if (
|
|
233
|
+
!root ||
|
|
234
|
+
!(element instanceof HTMLInputElement) ||
|
|
235
|
+
element.type !== 'checkbox'
|
|
236
|
+
) {
|
|
237
|
+
return [];
|
|
224
238
|
}
|
|
225
|
-
|
|
239
|
+
const controls = Array.from(root.elements).filter(
|
|
240
|
+
(control): control is HTMLInputElement =>
|
|
241
|
+
control instanceof HTMLInputElement &&
|
|
242
|
+
control.type === 'checkbox' &&
|
|
243
|
+
control.name === element.name &&
|
|
244
|
+
!control.closest('[data-field-switch]')
|
|
245
|
+
);
|
|
246
|
+
const isGrouped =
|
|
247
|
+
controls.length > 1 ||
|
|
248
|
+
!!element.closest(
|
|
249
|
+
'[data-choice-type="checkbox"], [data-field-choice-group]'
|
|
250
|
+
);
|
|
251
|
+
return isGrouped ? controls : [];
|
|
226
252
|
}
|
|
227
253
|
|
|
228
|
-
function
|
|
229
|
-
|
|
230
|
-
|
|
254
|
+
function checkedCount(
|
|
255
|
+
root: HTMLFormElement | null,
|
|
256
|
+
element: ValidatorElement
|
|
257
|
+
): number | null {
|
|
258
|
+
const controls = checkboxGroup(root, element);
|
|
259
|
+
if (controls.length === 0) return null;
|
|
260
|
+
return controls.filter((control) => control.checked).length;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function validateChoiceMin(
|
|
264
|
+
root: HTMLFormElement | null,
|
|
265
|
+
element: ValidatorElement,
|
|
266
|
+
min: unknown
|
|
267
|
+
): boolean {
|
|
268
|
+
if (typeof min !== 'number') return true;
|
|
269
|
+
if (element instanceof HTMLSelectElement) {
|
|
270
|
+
return element.selectedOptions.length >= min;
|
|
231
271
|
}
|
|
232
|
-
|
|
272
|
+
const count = checkedCount(root, element);
|
|
273
|
+
return count === null ? true : count >= min;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function validateChoiceMax(
|
|
277
|
+
root: HTMLFormElement | null,
|
|
278
|
+
element: ValidatorElement,
|
|
279
|
+
max: unknown
|
|
280
|
+
): boolean {
|
|
281
|
+
if (typeof max !== 'number') return true;
|
|
282
|
+
if (element instanceof HTMLSelectElement) {
|
|
283
|
+
return element.selectedOptions.length <= max;
|
|
284
|
+
}
|
|
285
|
+
const count = checkedCount(root, element);
|
|
286
|
+
return count === null ? true : count <= max;
|
|
233
287
|
}
|
|
234
288
|
|
|
235
289
|
function validateNoSpace(element: ValidatorElement, noSpace: unknown): boolean {
|
|
@@ -336,7 +390,6 @@ function showSuccess(element: ValidatorElement): void {
|
|
|
336
390
|
|
|
337
391
|
if (!(element instanceof HTMLInputElement) || element.type !== 'checkbox') {
|
|
338
392
|
element.classList.remove('is-invalid');
|
|
339
|
-
element.classList.add('is-valid');
|
|
340
393
|
}
|
|
341
394
|
}
|
|
342
395
|
|
|
@@ -353,10 +406,17 @@ export function createValidator(
|
|
|
353
406
|
|
|
354
407
|
let root: HTMLFormElement | null = resolved;
|
|
355
408
|
let options: ResolvedValidatorProps | null = normalizeProps(props);
|
|
409
|
+
const initialNoValidate = root.noValidate;
|
|
356
410
|
const runtime = createRuntime();
|
|
357
411
|
const events = createEventManager();
|
|
412
|
+
const errorNames = new Set<string>();
|
|
358
413
|
let validator: ValidatorInstance;
|
|
359
414
|
|
|
415
|
+
const syncNativeValidation = (): void => {
|
|
416
|
+
if (!root) return;
|
|
417
|
+
root.noValidate = options?.vanilla === false ? true : false;
|
|
418
|
+
};
|
|
419
|
+
|
|
360
420
|
const validateCustom = (
|
|
361
421
|
control: ValidatorElement,
|
|
362
422
|
rule: unknown
|
|
@@ -398,10 +458,10 @@ export function createValidator(
|
|
|
398
458
|
runtime.valid = validateMultiple(control, value);
|
|
399
459
|
break;
|
|
400
460
|
case 'min':
|
|
401
|
-
runtime.valid =
|
|
461
|
+
runtime.valid = validateChoiceMin(root, control, value);
|
|
402
462
|
break;
|
|
403
463
|
case 'max':
|
|
404
|
-
runtime.valid =
|
|
464
|
+
runtime.valid = validateChoiceMax(root, control, value);
|
|
405
465
|
break;
|
|
406
466
|
case 'noSpace':
|
|
407
467
|
runtime.valid = validateNoSpace(control, value);
|
|
@@ -435,17 +495,33 @@ export function createValidator(
|
|
|
435
495
|
}
|
|
436
496
|
}
|
|
437
497
|
if (!runtime.valid) {
|
|
498
|
+
runtime.error = true;
|
|
499
|
+
errorNames.add(name);
|
|
438
500
|
showError(options, runtime, control, name, rule, customMessage);
|
|
439
501
|
break;
|
|
440
502
|
}
|
|
441
503
|
showSuccess(control);
|
|
442
504
|
}
|
|
505
|
+
if (runtime.valid) errorNames.delete(name);
|
|
443
506
|
return runtime.valid;
|
|
444
507
|
};
|
|
508
|
+
const validateControl = (control: ValidatorElement): boolean => {
|
|
509
|
+
if (!options || runtime.destroyed || !control.name) return true;
|
|
510
|
+
if (!options.rules[control.name]) return true;
|
|
511
|
+
const valid = validateRule(control, control.name);
|
|
512
|
+
if (valid && errorNames.size === 0) {
|
|
513
|
+
runtime.valid = true;
|
|
514
|
+
runtime.error = false;
|
|
515
|
+
runtime.message = '';
|
|
516
|
+
}
|
|
517
|
+
return valid;
|
|
518
|
+
};
|
|
445
519
|
const validate = (): boolean => {
|
|
446
520
|
if (!root || !options || runtime.destroyed) return false;
|
|
447
521
|
runtime.valid = true;
|
|
522
|
+
runtime.error = false;
|
|
448
523
|
runtime.message = '';
|
|
524
|
+
errorNames.clear();
|
|
449
525
|
for (const control of Array.from(root.elements)) {
|
|
450
526
|
if (!isValidatorElement(control) || !control.name) continue;
|
|
451
527
|
if (!options.rules[control.name]) continue;
|
|
@@ -455,6 +531,13 @@ export function createValidator(
|
|
|
455
531
|
if (runtime.valid) options.onSubmit?.(validator);
|
|
456
532
|
return runtime.valid;
|
|
457
533
|
};
|
|
534
|
+
const handleFieldInput = (event: Event): void => {
|
|
535
|
+
if (!runtime.error) return;
|
|
536
|
+
const target = event.target;
|
|
537
|
+
if (!(target instanceof Element) || !isValidatorElement(target)) return;
|
|
538
|
+
if (!target.name || !errorNames.has(target.name)) return;
|
|
539
|
+
validateControl(target);
|
|
540
|
+
};
|
|
458
541
|
const reset = ({ native = true }: ResetOptions = {}): void => {
|
|
459
542
|
if (!root) return;
|
|
460
543
|
if (native) root.reset();
|
|
@@ -467,17 +550,22 @@ export function createValidator(
|
|
|
467
550
|
help.remove();
|
|
468
551
|
}
|
|
469
552
|
runtime.valid = true;
|
|
553
|
+
runtime.error = false;
|
|
470
554
|
runtime.message = '';
|
|
555
|
+
errorNames.clear();
|
|
471
556
|
};
|
|
472
557
|
const destroy = (): void => {
|
|
473
558
|
if (runtime.destroyed) return;
|
|
474
559
|
runtime.destroyed = true;
|
|
475
560
|
events.clear();
|
|
476
561
|
reset({ native: false });
|
|
562
|
+
if (root) root.noValidate = initialNoValidate;
|
|
477
563
|
root = null;
|
|
478
564
|
options = null;
|
|
479
565
|
runtime.valid = false;
|
|
566
|
+
runtime.error = false;
|
|
480
567
|
runtime.message = '';
|
|
568
|
+
errorNames.clear();
|
|
481
569
|
};
|
|
482
570
|
|
|
483
571
|
validator = {
|
|
@@ -489,6 +577,7 @@ export function createValidator(
|
|
|
489
577
|
},
|
|
490
578
|
set props(value) {
|
|
491
579
|
options = value;
|
|
580
|
+
syncNativeValidation();
|
|
492
581
|
},
|
|
493
582
|
runtime,
|
|
494
583
|
validate,
|
|
@@ -503,5 +592,8 @@ export function createValidator(
|
|
|
503
592
|
});
|
|
504
593
|
events.on('reset', root, 'reset', () => reset({ native: false }));
|
|
505
594
|
}
|
|
595
|
+
events.on('input', root, 'input', handleFieldInput);
|
|
596
|
+
events.on('change', root, 'change', handleFieldInput);
|
|
597
|
+
syncNativeValidation();
|
|
506
598
|
return validator;
|
|
507
599
|
}
|