vanilla-jui 1.3.11 → 1.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.
Files changed (106) hide show
  1. package/dist/index.d.ts +2921 -0
  2. package/dist/index.js +1 -1
  3. package/dist/index.umd.js +1 -1
  4. package/dist/style.css +1 -1
  5. package/package.json +4 -3
  6. package/src/components/accordion.ts +522 -0
  7. package/src/components/drop.ts +497 -0
  8. package/src/components/flow.ts +1579 -0
  9. package/src/components/form.ts +953 -0
  10. package/src/components/{icons.js → icons.ts} +24 -11
  11. package/src/components/menu.ts +615 -0
  12. package/src/components/{modal.js → modal.ts} +560 -237
  13. package/src/components/offcanvas.ts +457 -0
  14. package/src/components/{pagination.js → pagination.ts} +251 -76
  15. package/src/components/parabola.ts +335 -0
  16. package/src/components/{sticky.js → sticky.ts} +123 -98
  17. package/src/components/{swiper.js → swiper.ts} +510 -232
  18. package/src/components/tabs.ts +984 -0
  19. package/src/components/theme.ts +437 -0
  20. package/src/components/toast.ts +371 -0
  21. package/src/components/toc.ts +385 -0
  22. package/src/components/tooltip.ts +179 -0
  23. package/src/components/validator.ts +596 -0
  24. package/src/core/{Component.js → Component.ts} +134 -27
  25. package/src/css/accordion.css +0 -45
  26. package/src/css/animation.css +31 -1
  27. package/src/css/avatar.css +7 -33
  28. package/src/css/badge.css +11 -59
  29. package/src/css/breadcrumb.css +9 -32
  30. package/src/css/button.css +23 -224
  31. package/src/css/card.css +1 -1
  32. package/src/css/color.css +863 -1013
  33. package/src/css/color_p3.css +822 -975
  34. package/src/css/components.css +1 -0
  35. package/src/css/drop.css +7 -7
  36. package/src/css/flow.css +1 -1
  37. package/src/css/form.css +1 -1
  38. package/src/css/icon.css +7 -12
  39. package/src/css/index.css +5 -2
  40. package/src/css/loading.css +36 -20
  41. package/src/css/menu.css +1 -1
  42. package/src/css/modal.css +15 -44
  43. package/src/css/offcanvas.css +14 -40
  44. package/src/css/popup.css +4 -4
  45. package/src/css/status.css +0 -2
  46. package/src/css/swiper.css +35 -50
  47. package/src/css/tabs.css +9 -31
  48. package/src/css/tag.css +16 -77
  49. package/src/css/themes/default/accordion.css +62 -0
  50. package/src/css/themes/default/avatar.css +39 -0
  51. package/src/css/themes/default/badge.css +63 -0
  52. package/src/css/themes/default/breadcrumb.css +28 -0
  53. package/src/css/themes/default/button.css +204 -0
  54. package/src/css/themes/default/drop.css +11 -0
  55. package/src/css/themes/default/index.css +16 -0
  56. package/src/css/themes/default/loading.css +23 -0
  57. package/src/css/themes/default/modal.css +40 -0
  58. package/src/css/themes/default/offcanvas.css +13 -0
  59. package/src/css/themes/default/popup.css +7 -0
  60. package/src/css/themes/default/swiper.css +35 -0
  61. package/src/css/themes/default/tabs.css +37 -0
  62. package/src/css/themes/default/tag.css +85 -0
  63. package/src/css/themes/default/tip.css +49 -0
  64. package/src/css/themes/default/toast.css +70 -0
  65. package/src/css/themes/default/tooltip.css +14 -0
  66. package/src/css/themes/index.css +1 -0
  67. package/src/css/tip.css +20 -39
  68. package/src/css/toast.css +18 -80
  69. package/src/css/tokens.css +8 -3
  70. package/src/css/tooltip.css +10 -10
  71. package/src/index.ts +37 -0
  72. package/src/locales/{index.js → index.ts} +1 -2
  73. package/src/types.d.ts +1 -0
  74. package/src/utilities/browser.ts +131 -0
  75. package/src/utilities/cache.ts +57 -0
  76. package/src/utilities/core.ts +614 -0
  77. package/src/utilities/dom.ts +468 -0
  78. package/src/utilities/events.ts +106 -0
  79. package/src/utilities/http.ts +35 -0
  80. package/src/utilities/storage.ts +46 -0
  81. package/src/components/accordion.js +0 -373
  82. package/src/components/components.js +0 -0
  83. package/src/components/drop.js +0 -437
  84. package/src/components/flow.js +0 -1133
  85. package/src/components/form.js +0 -585
  86. package/src/components/menu.js +0 -422
  87. package/src/components/offcanvas.js +0 -332
  88. package/src/components/parabola.js +0 -295
  89. package/src/components/tabs.js +0 -746
  90. package/src/components/theme.js +0 -353
  91. package/src/components/toast.js +0 -329
  92. package/src/components/toc.js +0 -226
  93. package/src/components/tooltip.js +0 -86
  94. package/src/components/validator.js +0 -586
  95. package/src/index.js +0 -45
  96. package/src/locales/index.min.js +0 -1
  97. package/src/utilities/browser.js +0 -111
  98. package/src/utilities/cache.js +0 -159
  99. package/src/utilities/core.js +0 -502
  100. package/src/utilities/debug.js +0 -103
  101. package/src/utilities/dom.js +0 -313
  102. package/src/utilities/events.js +0 -69
  103. package/src/utilities/http.js +0 -30
  104. package/src/utilities/legacy-dom.js +0 -179
  105. package/src/utilities/storage.js +0 -46
  106. /package/src/icons/{index.js → index.ts} +0 -0
@@ -0,0 +1,596 @@
1
+ import { jsx } from 'vanilla-signal';
2
+
3
+ import {
4
+ type ResolveSchema,
5
+ resolveProps,
6
+ validateParam,
7
+ } from '../utilities/core.ts';
8
+ import { type DOMReference, all, resolveElement } from '../utilities/dom.ts';
9
+ import { type IEventManager, createEventManager } from '../utilities/events.ts';
10
+
11
+ type ValidatorElement =
12
+ | HTMLInputElement
13
+ | HTMLSelectElement
14
+ | HTMLTextAreaElement;
15
+ type ValidatorRuleName = keyof ValidatorRule | string;
16
+ type ValidatorMessageMap = Record<
17
+ string,
18
+ Partial<Record<ValidatorRuleName, string>>
19
+ >;
20
+ type ValidatorCustomResult = boolean | string;
21
+ type ValidatorCustomRule = (
22
+ element: ValidatorElement,
23
+ validator: Validator
24
+ ) => ValidatorCustomResult;
25
+
26
+ export interface ValidatorRule extends Record<string, unknown> {
27
+ required?: boolean;
28
+ minLength?: number;
29
+ maxLength?: number;
30
+ equalTo?: string;
31
+ email?: boolean;
32
+ checked?: boolean;
33
+ selected?: boolean;
34
+ multiple?: boolean;
35
+ min?: number;
36
+ max?: number;
37
+ noSpace?: boolean;
38
+ noChinese?: boolean;
39
+ noSpecial?: boolean;
40
+ pattern?: string | RegExp;
41
+ file?: boolean;
42
+ minSize?: number;
43
+ maxSize?: number;
44
+ accept?: string;
45
+ validate?: ValidatorCustomRule;
46
+ }
47
+
48
+ export interface ValidatorOptions extends Record<string, unknown> {
49
+ rules?: Record<string, ValidatorRule>;
50
+ messages?: ValidatorMessageMap;
51
+ onSubmit?: ((validator: Validator) => void) | null;
52
+ }
53
+
54
+ interface ResolvedValidatorOptions extends Record<string, unknown> {
55
+ rules: Record<string, ValidatorRule>;
56
+ messages: ValidatorMessageMap;
57
+ onSubmit: ((validator: Validator) => void) | null;
58
+ }
59
+
60
+ interface ValidatorRuntime {
61
+ valid: boolean;
62
+ message: string;
63
+ destroyed: boolean;
64
+ }
65
+
66
+ interface ValidatorCleanup {
67
+ events: IEventManager;
68
+ }
69
+
70
+ interface ResetOptions {
71
+ native?: boolean;
72
+ }
73
+
74
+ const VALIDATOR_OPTIONS_SCHEMA = {
75
+ rules: { default: {}, type: 'object' },
76
+ messages: { default: {}, type: 'object' },
77
+ onSubmit: { default: null, types: ['function', 'null'] },
78
+ } satisfies ResolveSchema<ValidatorOptions>;
79
+
80
+ function normalizeOptions(input: ValidatorOptions): ResolvedValidatorOptions {
81
+ const options = resolveProps(
82
+ input,
83
+ VALIDATOR_OPTIONS_SCHEMA,
84
+ 'Validator.options'
85
+ );
86
+ return {
87
+ rules: options.rules as Record<string, ValidatorRule>,
88
+ messages: options.messages as ValidatorMessageMap,
89
+ onSubmit: options.onSubmit as ResolvedValidatorOptions['onSubmit'],
90
+ };
91
+ }
92
+
93
+ function isValidatorElement(element: Element): element is ValidatorElement {
94
+ return (
95
+ element instanceof HTMLInputElement ||
96
+ element instanceof HTMLSelectElement ||
97
+ element instanceof HTMLTextAreaElement
98
+ );
99
+ }
100
+
101
+ function toValidatorElement(
102
+ value: Element | RadioNodeList | null
103
+ ): ValidatorElement | null {
104
+ if (!value) return null;
105
+ if (value instanceof Element) {
106
+ return isValidatorElement(value) ? value : null;
107
+ }
108
+ for (const item of Array.from(value)) {
109
+ if (item instanceof Element && isValidatorElement(item)) return item;
110
+ }
111
+ return null;
112
+ }
113
+
114
+ function createRuntime(): ValidatorRuntime {
115
+ return {
116
+ valid: true,
117
+ message: '',
118
+ destroyed: false,
119
+ };
120
+ }
121
+
122
+ function getControlContainer(element: ValidatorElement): HTMLElement | null {
123
+ return (
124
+ element.closest<HTMLElement>('[data-form-control]') || element.parentElement
125
+ );
126
+ }
127
+
128
+ function selectedValues(element: HTMLSelectElement): string[] {
129
+ return Array.from(element.selectedOptions).map((option) =>
130
+ option.value.trim()
131
+ );
132
+ }
133
+
134
+ function isFileInput(element: ValidatorElement): element is HTMLInputElement {
135
+ return element instanceof HTMLInputElement && element.type === 'file';
136
+ }
137
+
138
+ function hasFiles(element: HTMLInputElement): element is HTMLInputElement & {
139
+ files: FileList;
140
+ } {
141
+ return !!element.files && element.files.length > 0;
142
+ }
143
+
144
+ /**
145
+ * 表单校验组件。
146
+ *
147
+ * 支持绑定表单 submit/reset 事件,也可以手动调用 validate/reset。
148
+ */
149
+ export class Validator {
150
+ root: HTMLFormElement | null;
151
+ options: ResolvedValidatorOptions | null;
152
+ runtime: ValidatorRuntime;
153
+ cleanup: ValidatorCleanup | null;
154
+
155
+ constructor(
156
+ element: DOMReference,
157
+ options: ValidatorOptions = {},
158
+ bindEvents = false
159
+ ) {
160
+ this.options = normalizeOptions(options);
161
+ this.root = this.resolveRoot(element);
162
+ this.runtime = createRuntime();
163
+ this.cleanup = {
164
+ events: createEventManager(),
165
+ };
166
+
167
+ this.validateOptions(bindEvents);
168
+
169
+ if (bindEvents) {
170
+ this.bindEvents();
171
+ }
172
+ }
173
+
174
+ private resolveRoot(element: DOMReference): HTMLFormElement {
175
+ const root = resolveElement(element);
176
+ if (!(root instanceof HTMLFormElement)) {
177
+ throw new Error('Validator.element expects a form element.');
178
+ }
179
+ return root;
180
+ }
181
+
182
+ private validateOptions(bindEvents: boolean): void {
183
+ validateParam('bindEvents', bindEvents, 'boolean', 'Validator');
184
+ }
185
+
186
+ private bindEvents(): void {
187
+ this.unbindEvents();
188
+ if (!this.root || !this.cleanup) return;
189
+
190
+ this.cleanup.events.on('submit', this.root, 'submit', (event) => {
191
+ event.preventDefault();
192
+ this.validate();
193
+ });
194
+ this.cleanup.events.on('reset', this.root, 'reset', () => {
195
+ this.reset({ native: false });
196
+ });
197
+ }
198
+
199
+ private unbindEvents(): void {
200
+ this.cleanup?.events.clear();
201
+ }
202
+
203
+ /**
204
+ * 执行表单校验。
205
+ */
206
+ validate(): boolean {
207
+ if (!this.root || !this.options) return false;
208
+
209
+ this.runtime.valid = true;
210
+ this.runtime.message = '';
211
+
212
+ for (const element of Array.from(this.root.elements)) {
213
+ if (!isValidatorElement(element) || !element.name) continue;
214
+ if (!this.options.rules[element.name]) continue;
215
+
216
+ this.runtime.valid = this.validateRule(element, element.name);
217
+ if (!this.runtime.valid) break;
218
+ }
219
+
220
+ if (this.runtime.valid && this.options.onSubmit) {
221
+ this.options.onSubmit(this);
222
+ }
223
+ return this.runtime.valid;
224
+ }
225
+
226
+ private validateRule(element: ValidatorElement, name: string): boolean {
227
+ if (!this.options) return false;
228
+ const rules = this.options.rules[name];
229
+ if (!rules) return true;
230
+
231
+ if (this.hasNativeValidationRule(element)) {
232
+ return this.runtime.valid;
233
+ }
234
+
235
+ for (const rule of Object.keys(rules)) {
236
+ const value = rules[rule];
237
+ let customMessage = '';
238
+
239
+ switch (rule) {
240
+ case 'required':
241
+ this.runtime.valid = this.validateRequired(element, value);
242
+ break;
243
+ case 'minLength':
244
+ this.runtime.valid = this.validateMinLength(element, value);
245
+ break;
246
+ case 'maxLength':
247
+ this.runtime.valid = this.validateMaxLength(element, value);
248
+ break;
249
+ case 'equalTo':
250
+ this.runtime.valid = this.validateEqualTo(element, value);
251
+ break;
252
+ case 'email':
253
+ this.runtime.valid = this.validateEmail(element);
254
+ break;
255
+ case 'checked':
256
+ this.runtime.valid = this.validateCheck(element, value);
257
+ break;
258
+ case 'selected':
259
+ this.runtime.valid = this.validateSelected(element, value);
260
+ break;
261
+ case 'multiple':
262
+ this.runtime.valid = this.validateMultiple(element, value);
263
+ break;
264
+ case 'min':
265
+ this.runtime.valid = this.validateSelectMin(element, value);
266
+ break;
267
+ case 'max':
268
+ this.runtime.valid = this.validateSelectMax(element, value);
269
+ break;
270
+ case 'noSpace':
271
+ this.runtime.valid = this.validateNoSpace(element, value);
272
+ break;
273
+ case 'noChinese':
274
+ this.runtime.valid = !/[\u4e00-\u9fa5]/.test(element.value);
275
+ break;
276
+ case 'noSpecial':
277
+ this.runtime.valid = !/[@#$%^&*]+/g.test(element.value);
278
+ break;
279
+ case 'pattern':
280
+ this.runtime.valid = this.validatePattern(element, value);
281
+ break;
282
+ case 'file':
283
+ this.runtime.valid = this.validateFile(element, value);
284
+ break;
285
+ case 'minSize':
286
+ this.runtime.valid = this.validateMinSize(element, value);
287
+ break;
288
+ case 'maxSize':
289
+ this.runtime.valid = this.validateMaxSize(element, value);
290
+ break;
291
+ case 'accept':
292
+ this.runtime.valid = this.validateAccept(element, value);
293
+ break;
294
+ case 'validate': {
295
+ const result = this.validateCustom(element, value);
296
+ this.runtime.valid = result === true;
297
+ customMessage = typeof result === 'string' ? result : '';
298
+ break;
299
+ }
300
+ }
301
+
302
+ if (!this.runtime.valid) {
303
+ this.showError(element, name, rule, customMessage);
304
+ break;
305
+ }
306
+
307
+ this.showSuccess(element);
308
+ }
309
+
310
+ return this.runtime.valid;
311
+ }
312
+
313
+ private hasNativeValidationRule(element: ValidatorElement): boolean {
314
+ return (
315
+ element.hasAttribute('required') ||
316
+ element.hasAttribute('minlength') ||
317
+ element.hasAttribute('maxlength') ||
318
+ element.hasAttribute('pattern') ||
319
+ element.hasAttribute('min') ||
320
+ element.hasAttribute('max') ||
321
+ element.hasAttribute('step')
322
+ );
323
+ }
324
+
325
+ private validateRequired(
326
+ element: ValidatorElement,
327
+ required: unknown
328
+ ): boolean {
329
+ if (required !== true) return true;
330
+ if (element instanceof HTMLSelectElement) {
331
+ return selectedValues(element).some((value) => value.length > 0);
332
+ }
333
+ return element.value.trim().length >= 1;
334
+ }
335
+
336
+ private validateMinLength(
337
+ element: ValidatorElement,
338
+ minLength: unknown
339
+ ): boolean {
340
+ if (typeof minLength !== 'number') return true;
341
+ return element.value.length >= minLength;
342
+ }
343
+
344
+ private validateMaxLength(
345
+ element: ValidatorElement,
346
+ maxLength: unknown
347
+ ): boolean {
348
+ if (typeof maxLength !== 'number') return true;
349
+ return element.value.length <= maxLength;
350
+ }
351
+
352
+ private validateEmail(element: ValidatorElement): boolean {
353
+ const emailPattern = /^([\w-.]+@([\w-]+\.)+[\w-]{2,4})?$/;
354
+ return emailPattern.test(element.value);
355
+ }
356
+
357
+ private validateEqualTo(
358
+ element: ValidatorElement,
359
+ targetName: unknown
360
+ ): boolean {
361
+ if (!this.root || typeof targetName !== 'string') return true;
362
+ const targetElement = toValidatorElement(
363
+ this.root.elements.namedItem(targetName)
364
+ );
365
+ if (!targetElement) {
366
+ throw new Error(`Validator: target element "${targetName}" not found.`);
367
+ }
368
+ return element.value === targetElement.value;
369
+ }
370
+
371
+ private validateCheck(element: ValidatorElement, checked: unknown): boolean {
372
+ if (!(element instanceof HTMLInputElement) || element.type !== 'checkbox') {
373
+ throw new Error(
374
+ `Validator: element expects a checkbox input, but ${element.tagName.toLowerCase()} given.`
375
+ );
376
+ }
377
+ return element.checked === checked;
378
+ }
379
+
380
+ private validateSelected(
381
+ element: ValidatorElement,
382
+ selected: unknown
383
+ ): boolean {
384
+ if (!(element instanceof HTMLSelectElement)) {
385
+ throw new Error(
386
+ `Validator: element expects a select element, but ${element.tagName.toLowerCase()} given.`
387
+ );
388
+ }
389
+ if (selected !== true) return true;
390
+ return selectedValues(element).some((value) => value.length > 0);
391
+ }
392
+
393
+ private validateMultiple(
394
+ element: ValidatorElement,
395
+ multiple: unknown
396
+ ): boolean {
397
+ if (!(element instanceof HTMLSelectElement)) {
398
+ throw new Error(
399
+ `Validator: element expects a select element, but ${element.tagName.toLowerCase()} given.`
400
+ );
401
+ }
402
+ if (multiple !== true) return true;
403
+ return element.selectedOptions.length > 0;
404
+ }
405
+
406
+ private validateSelectMin(element: ValidatorElement, min: unknown): boolean {
407
+ if (!(element instanceof HTMLSelectElement) || typeof min !== 'number') {
408
+ return true;
409
+ }
410
+ return element.selectedOptions.length >= min;
411
+ }
412
+
413
+ private validateSelectMax(element: ValidatorElement, max: unknown): boolean {
414
+ if (!(element instanceof HTMLSelectElement) || typeof max !== 'number') {
415
+ return true;
416
+ }
417
+ return element.selectedOptions.length <= max;
418
+ }
419
+
420
+ private validateNoSpace(
421
+ element: ValidatorElement,
422
+ noSpace: unknown
423
+ ): boolean {
424
+ return !/\s/.test(element.value) || noSpace !== true;
425
+ }
426
+
427
+ private validatePattern(
428
+ element: ValidatorElement,
429
+ pattern: unknown
430
+ ): boolean {
431
+ if (!(typeof pattern === 'string' || pattern instanceof RegExp))
432
+ return true;
433
+ return new RegExp(pattern).test(element.value);
434
+ }
435
+
436
+ private validateFile(element: ValidatorElement, required: unknown): boolean {
437
+ if (!isFileInput(element)) return true;
438
+ return required === true ? hasFiles(element) : true;
439
+ }
440
+
441
+ private validateMinSize(
442
+ element: ValidatorElement,
443
+ minSize: unknown
444
+ ): boolean {
445
+ if (
446
+ !isFileInput(element) ||
447
+ !hasFiles(element) ||
448
+ typeof minSize !== 'number'
449
+ ) {
450
+ return true;
451
+ }
452
+ return element.files[0].size >= minSize;
453
+ }
454
+
455
+ private validateMaxSize(
456
+ element: ValidatorElement,
457
+ maxSize: unknown
458
+ ): boolean {
459
+ if (
460
+ !isFileInput(element) ||
461
+ !hasFiles(element) ||
462
+ typeof maxSize !== 'number'
463
+ ) {
464
+ return true;
465
+ }
466
+ return element.files[0].size <= maxSize;
467
+ }
468
+
469
+ private validateAccept(element: ValidatorElement, accept: unknown): boolean {
470
+ if (
471
+ !isFileInput(element) ||
472
+ !hasFiles(element) ||
473
+ typeof accept !== 'string'
474
+ ) {
475
+ return true;
476
+ }
477
+
478
+ const file = element.files[0];
479
+ const allowed = accept.split(',').map((item) => item.trim().toLowerCase());
480
+
481
+ return allowed.some((rule) => {
482
+ if (rule.startsWith('.')) {
483
+ return file.name.toLowerCase().endsWith(rule);
484
+ }
485
+ if (rule.endsWith('/*')) {
486
+ return file.type.startsWith(rule.replace('/*', '/'));
487
+ }
488
+ return file.type === rule;
489
+ });
490
+ }
491
+
492
+ private validateCustom(
493
+ element: ValidatorElement,
494
+ rule: unknown
495
+ ): ValidatorCustomResult {
496
+ if (typeof rule !== 'function') return true;
497
+ const result = (rule as ValidatorCustomRule)(element, this);
498
+ return typeof result === 'string' ? result : !!result;
499
+ }
500
+
501
+ private showError(
502
+ element: ValidatorElement,
503
+ name: string,
504
+ rule: string,
505
+ customMessage = ''
506
+ ): void {
507
+ if (!(element instanceof HTMLInputElement) || element.type !== 'checkbox') {
508
+ element.classList.remove('is-valid');
509
+ element.classList.add('is-invalid');
510
+ }
511
+
512
+ const error = customMessage || this.options?.messages[name]?.[rule] || '';
513
+ if (!error) return;
514
+
515
+ this.runtime.message = error;
516
+
517
+ const formControl = getControlContainer(element);
518
+ if (!formControl) return;
519
+
520
+ let help =
521
+ all<HTMLElement>('[data-validator-help]', formControl).find(
522
+ (element) => element.dataset.validatorHelp === name
523
+ ) || null;
524
+ if (!help) {
525
+ help = jsx('div', {
526
+ className: 'help-block is-invalid',
527
+ 'data-validator-help': name,
528
+ }) as HTMLElement;
529
+ formControl.appendChild(help);
530
+ }
531
+ help.textContent = error;
532
+ }
533
+
534
+ private showSuccess(element: ValidatorElement): void {
535
+ const formControl = getControlContainer(element);
536
+ if (formControl) {
537
+ for (const help of all<HTMLElement>(
538
+ '[data-validator-help]',
539
+ formControl
540
+ )) {
541
+ if (help.dataset.validatorHelp === element.name) help.remove();
542
+ }
543
+ }
544
+
545
+ if (!(element instanceof HTMLInputElement) || element.type !== 'checkbox') {
546
+ element.classList.remove('is-invalid');
547
+ element.classList.add('is-valid');
548
+ }
549
+ }
550
+
551
+ /**
552
+ * 重置表单与校验状态。
553
+ */
554
+ reset({ native = true }: ResetOptions = {}): void {
555
+ if (!this.root) return;
556
+ if (native) this.root.reset();
557
+
558
+ for (const element of Array.from(this.root.elements)) {
559
+ if (!isValidatorElement(element)) continue;
560
+ element.classList.remove('is-valid');
561
+ element.classList.remove('is-invalid');
562
+ }
563
+
564
+ for (const help of all<HTMLElement>('[data-validator-help]', this.root)) {
565
+ help.remove();
566
+ }
567
+
568
+ this.runtime.valid = true;
569
+ this.runtime.message = '';
570
+ }
571
+
572
+ /**
573
+ * 销毁当前校验实例。
574
+ */
575
+ destroy(): void {
576
+ if (this.runtime.destroyed) return;
577
+ this.runtime.destroyed = true;
578
+
579
+ this.unbindEvents();
580
+ this.reset({ native: false });
581
+ this.root = null;
582
+ this.options = null;
583
+ this.runtime.valid = false;
584
+ this.runtime.message = '';
585
+ this.cleanup?.events.clear();
586
+ this.cleanup = null;
587
+ }
588
+ }
589
+
590
+ export function createValidator(
591
+ element: DOMReference,
592
+ options: ValidatorOptions = {},
593
+ bindEvents = false
594
+ ): Validator {
595
+ return new Validator(element, options, bindEvents);
596
+ }