vanilla-jui 1.3.10 → 1.4.0

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 (90) hide show
  1. package/dist/index.d.ts +2927 -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 +496 -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 +2 -2
  26. package/src/css/animation.css +31 -1
  27. package/src/css/breadcrumb.css +1 -1
  28. package/src/css/button.css +5 -5
  29. package/src/css/card.css +7 -7
  30. package/src/css/color.css +865 -1028
  31. package/src/css/color_p3.css +871 -1036
  32. package/src/css/components.css +1 -0
  33. package/src/css/drop.css +1 -1
  34. package/src/css/flow.css +1 -1
  35. package/src/css/form.css +21 -21
  36. package/src/css/gradient.css +17 -4
  37. package/src/css/icon.css +17 -1
  38. package/src/css/index.css +3 -3
  39. package/src/css/layout.css +1 -1
  40. package/src/css/menu.css +27 -27
  41. package/src/css/modal.css +10 -27
  42. package/src/css/offcanvas.css +2 -2
  43. package/src/css/pagination.css +1 -1
  44. package/src/css/popup.css +2 -2
  45. package/src/css/swiper.css +12 -12
  46. package/src/css/tabs.css +3 -2
  47. package/src/css/tag.css +3 -4
  48. package/src/css/theme-palette.css +6 -6
  49. package/src/css/tip.css +1 -1
  50. package/src/css/toast.css +8 -8
  51. package/src/css/tokens.css +197 -226
  52. package/src/css/toolbar.css +3 -3
  53. package/src/css/tooltip.css +1 -1
  54. package/src/index.ts +37 -0
  55. package/src/locales/{index.js → index.ts} +1 -2
  56. package/src/types.d.ts +1 -0
  57. package/src/utilities/browser.ts +131 -0
  58. package/src/utilities/cache.ts +57 -0
  59. package/src/utilities/core.ts +614 -0
  60. package/src/utilities/dom.ts +468 -0
  61. package/src/utilities/events.ts +106 -0
  62. package/src/utilities/http.ts +35 -0
  63. package/src/utilities/storage.ts +46 -0
  64. package/src/components/accordion.js +0 -373
  65. package/src/components/drop.js +0 -437
  66. package/src/components/flow.js +0 -1133
  67. package/src/components/form.js +0 -585
  68. package/src/components/menu.js +0 -422
  69. package/src/components/offcanvas.js +0 -332
  70. package/src/components/parabola.js +0 -295
  71. package/src/components/tabs.js +0 -746
  72. package/src/components/theme.js +0 -353
  73. package/src/components/toast.js +0 -329
  74. package/src/components/toc.js +0 -226
  75. package/src/components/tooltip.js +0 -86
  76. package/src/components/validator.js +0 -586
  77. package/src/css/toast.min.css +0 -1
  78. package/src/index.js +0 -45
  79. package/src/locales/index.min.js +0 -1
  80. package/src/utilities/browser.js +0 -111
  81. package/src/utilities/cache.js +0 -159
  82. package/src/utilities/core.js +0 -502
  83. package/src/utilities/debug.js +0 -103
  84. package/src/utilities/dom.js +0 -313
  85. package/src/utilities/events.js +0 -69
  86. package/src/utilities/http.js +0 -30
  87. package/src/utilities/legacy-dom.js +0 -179
  88. package/src/utilities/storage.js +0 -46
  89. /package/src/{components/components.js → css/themes/index.css} +0 -0
  90. /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
+ }