@windwalker-io/unicorn-next 0.1.7 → 0.1.9

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 (133) hide show
  1. package/.editorconfig +18 -18
  2. package/.gulp.json +7 -7
  3. package/bin/release.mjs +47 -47
  4. package/dist/chunks/button-radio.js.map +1 -1
  5. package/dist/chunks/checkboxes-multi-select.js.map +1 -1
  6. package/dist/chunks/field-cascade-select.js +2 -2
  7. package/dist/chunks/field-cascade-select.js.map +1 -1
  8. package/dist/chunks/field-file-drag.js.map +1 -1
  9. package/dist/chunks/field-flatpickr.js.map +1 -1
  10. package/dist/chunks/field-modal-select.js.map +1 -1
  11. package/dist/chunks/field-modal-tree.js +2 -2
  12. package/dist/chunks/field-modal-tree.js.map +1 -1
  13. package/dist/chunks/field-multi-uploader.js.map +1 -1
  14. package/dist/chunks/field-repeatable.js.map +1 -1
  15. package/dist/chunks/field-single-image-drag.js.map +1 -1
  16. package/dist/chunks/form.js.map +1 -1
  17. package/dist/chunks/grid.js.map +1 -1
  18. package/dist/chunks/http-client.js.map +1 -1
  19. package/dist/chunks/iframe-modal.js.map +1 -1
  20. package/dist/chunks/index.js +1 -15
  21. package/dist/chunks/index.js.map +1 -1
  22. package/dist/chunks/keep-tab.js +2 -2
  23. package/dist/chunks/keep-tab.js.map +1 -1
  24. package/dist/chunks/legacy.js +3 -3
  25. package/dist/chunks/legacy.js.map +1 -1
  26. package/dist/chunks/list-dependent.js.map +1 -1
  27. package/dist/chunks/s3-multipart-uploader.js +23 -2
  28. package/dist/chunks/s3-multipart-uploader.js.map +1 -1
  29. package/dist/chunks/s3-uploader.js.map +1 -1
  30. package/dist/chunks/show-on.js +2 -2
  31. package/dist/chunks/show-on.js.map +1 -1
  32. package/dist/chunks/tinymce.js.map +1 -1
  33. package/dist/chunks/ui-bootstrap5.js.map +1 -1
  34. package/dist/chunks/unicorn.js +108 -89
  35. package/dist/chunks/unicorn.js.map +1 -1
  36. package/dist/chunks/validation.js.map +1 -1
  37. package/dist/index.d.ts +28 -6
  38. package/dist/unicorn.js +43 -41
  39. package/fusionfile.mjs +155 -155
  40. package/package.json +104 -104
  41. package/scss/bootstrap/multi-level-menu.scss +121 -121
  42. package/scss/editor.scss +116 -116
  43. package/scss/field/file-drag.scss +102 -102
  44. package/scss/field/single-image-drag.scss +88 -88
  45. package/scss/field/vue-drag-uploader.scss +160 -160
  46. package/scss/switcher.scss +156 -156
  47. package/src/app.ts +128 -128
  48. package/src/bootstrap/button-radio.ts +208 -208
  49. package/src/bootstrap/keep-tab.ts +155 -155
  50. package/src/composable/index.ts +22 -22
  51. package/src/composable/useCheckboxesMultiSelect.ts +22 -22
  52. package/src/composable/useFieldCascadeSelect.ts +9 -9
  53. package/src/composable/useFieldFileDrag.ts +9 -9
  54. package/src/composable/useFieldFlatpickr.ts +3 -3
  55. package/src/composable/useFieldModalSelect.ts +6 -6
  56. package/src/composable/useFieldModalTree.ts +3 -3
  57. package/src/composable/useFieldMultiUploader.ts +9 -9
  58. package/src/composable/useFieldRepeatable.ts +9 -9
  59. package/src/composable/useFieldSingleImageDrag.ts +9 -9
  60. package/src/composable/useForm.ts +43 -43
  61. package/src/composable/useGrid.ts +57 -57
  62. package/src/composable/useHttp.ts +9 -9
  63. package/src/composable/useIframeModal.ts +10 -10
  64. package/src/composable/useListDependent.ts +26 -26
  65. package/src/composable/useQueue.ts +13 -13
  66. package/src/composable/useS3Uploader.ts +32 -32
  67. package/src/composable/useShowOn.ts +9 -9
  68. package/src/composable/useStack.ts +13 -13
  69. package/src/composable/useTinymce.ts +29 -29
  70. package/src/composable/useTomSelect.ts +72 -72
  71. package/src/composable/useUIBootstrap5.ts +48 -48
  72. package/src/composable/useUniDirective.ts +43 -43
  73. package/src/composable/useValidation.ts +50 -50
  74. package/src/data.ts +34 -34
  75. package/src/events.ts +82 -82
  76. package/src/legacy/legacy.ts +190 -190
  77. package/src/legacy/loader.ts +125 -125
  78. package/src/module/checkboxes-multi-select.ts +54 -54
  79. package/src/module/field-cascade-select.ts +292 -292
  80. package/src/module/field-file-drag.ts +295 -295
  81. package/src/module/field-flatpickr.ts +130 -130
  82. package/src/module/field-modal-select.ts +179 -179
  83. package/src/module/field-modal-tree.ts +31 -31
  84. package/src/module/field-multi-uploader.ts +368 -368
  85. package/src/module/field-repeatable.ts +202 -202
  86. package/src/module/field-single-image-drag.ts +477 -477
  87. package/src/module/form.ts +223 -223
  88. package/src/module/grid.ts +479 -479
  89. package/src/module/http-client.ts +248 -248
  90. package/src/module/iframe-modal.ts +170 -170
  91. package/src/module/list-dependent.ts +321 -321
  92. package/src/module/s3-multipart-uploader.ts +330 -300
  93. package/src/module/s3-uploader.ts +234 -234
  94. package/src/module/show-on.ts +175 -175
  95. package/src/module/tinymce.ts +276 -276
  96. package/src/module/ui-bootstrap5.ts +116 -116
  97. package/src/module/validation.ts +1046 -1046
  98. package/src/plugin/index.ts +1 -1
  99. package/src/plugin/php-adapter.ts +72 -72
  100. package/src/polyfill/form-request-submit.ts +31 -31
  101. package/src/polyfill/index.ts +9 -9
  102. package/src/service/animate.ts +58 -58
  103. package/src/service/crypto.ts +27 -27
  104. package/src/service/dom-watcher.ts +62 -62
  105. package/src/service/dom.ts +291 -265
  106. package/src/service/helper.ts +52 -48
  107. package/src/service/index.ts +10 -10
  108. package/src/service/lang.ts +122 -122
  109. package/src/service/loader.ts +152 -152
  110. package/src/service/router.ts +118 -118
  111. package/src/service/ui.ts +525 -525
  112. package/src/service/uri.ts +106 -106
  113. package/src/types/base.ts +9 -9
  114. package/src/types/index.ts +4 -4
  115. package/src/types/modal-tree.ts +12 -12
  116. package/src/types/plugin.ts +6 -6
  117. package/src/types/shims.d.ts +18 -18
  118. package/src/types/ui.ts +6 -6
  119. package/src/unicorn.ts +79 -79
  120. package/src/utilities/arr.ts +25 -25
  121. package/src/utilities/base.ts +9 -9
  122. package/src/utilities/data.ts +48 -48
  123. package/src/utilities/index.ts +5 -5
  124. package/src/utilities/tree.ts +20 -20
  125. package/src/vue/components/ModalTree/ModalTreeApp.vue +175 -175
  126. package/src/vue/components/ModalTree/TreeItem.vue +262 -262
  127. package/src/vue/components/ModalTree/TreeModal.vue +225 -225
  128. package/tests/test.js +4 -4
  129. package/tsconfig.js.json +25 -25
  130. package/tsconfig.json +17 -17
  131. package/vite.assets.config.ts +61 -61
  132. package/vite.config.test.ts +36 -36
  133. package/vite.config.ts +112 -112
@@ -1,1046 +1,1046 @@
1
- import * as punycode from 'punycode';
2
- import { useUniDirective } from '../composable';
3
- import { getBoundedInstance, html, selectAll, selectOne, trans, useUITheme } from '../service';
4
- import { Nullable } from '../types';
5
- import { mergeDeep } from '../utilities';
6
-
7
- export declare type ValidationHandler<V = any, E = HTMLElement, P = Record<string, any>> = (
8
- value: V,
9
- input: E,
10
- options?: ValidatorOptions<E, P>,
11
- fv?: UnicornFieldValidation
12
- ) => any;
13
-
14
- export declare type ValidatorNoticeFunction<E = HTMLElement> = (input: E, field: UnicornFieldValidation) => any;
15
-
16
- export type ValidatorOptions<E = HTMLElement, P = Record<string, any>> = {
17
- notice?: ValidatorNoticeFunction<E> | string;
18
- [name: string]: any;
19
- } & Partial<P>;
20
-
21
- export declare type Validator<V = any, E = HTMLElement, P = Record<string, any>> = {
22
- handler: ValidationHandler<V, E, P>,
23
- options?: ValidatorOptions<E, P>;
24
- };
25
-
26
- const validatorHandlers: Record<string, ValidationHandler<any, any>> = {};
27
-
28
- type InputElements = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
29
-
30
- export interface FormValidationOptions {
31
- scroll: boolean;
32
- validatedClass: null;
33
- fieldSelector: null;
34
- scrollOffset: number;
35
- enabled: boolean;
36
- }
37
-
38
- export interface FieldValidationOptions {
39
- validClass: string;
40
- errorSelector: string;
41
- inputOptions: boolean;
42
- inputOptionsSelector: string;
43
- formSelector: string;
44
- selector: string;
45
- inputOptionsWrapperSelector: string;
46
- events: string[];
47
- invalidClass: string;
48
- errorMessageClass: string;
49
- }
50
-
51
- const defaultOptions: FormValidationOptions = {
52
- scroll: false,
53
- scrollOffset: -100,
54
- enabled: true,
55
- fieldSelector: null,
56
- validatedClass: null,
57
- };
58
-
59
- const defaultFieldOptions: FieldValidationOptions = {
60
- formSelector: '[uni-form-validate]',
61
- errorSelector: '[data-field-error]',
62
- selector: 'input[data-field-input], select[data-field-input], textarea[data-field-input]',
63
- validClass: 'is-valid',
64
- invalidClass: 'is-invalid',
65
- events: ['change'],
66
- errorMessageClass: 'invalid-tooltip',
67
- inputOptions: false,
68
- inputOptionsWrapperSelector: 'div[data-field-input]',
69
- inputOptionsSelector: '[data-input-option]'
70
- };
71
-
72
- export class UnicornFormValidation {
73
- presetFields: HTMLElement[] = [];
74
-
75
- static globalValidators: Record<string, Validator<any, any, any>> = {};
76
-
77
- validators: Record<string, Validator<any, any, any>> = {};
78
- options: FormValidationOptions;
79
- $form: HTMLElement;
80
-
81
- static is = 'uni-form-validate';
82
-
83
- constructor(el: HTMLElement, options: Partial<FormValidationOptions> = {}) {
84
- this.$form = selectOne(el);
85
- this.options = this.mergeOptions(options);
86
-
87
- this.registerDefaultValidators();
88
-
89
- this.init();
90
- }
91
-
92
- mergeOptions(options: Partial<FormValidationOptions>) {
93
- // Fix PHP empty array to JSON issue.
94
- if (Array.isArray(options)) {
95
- options = {};
96
- }
97
-
98
- return this.options = mergeDeep({}, defaultOptions, options);
99
- }
100
-
101
- get scrollEnabled() {
102
- return this.options.scroll;
103
- }
104
-
105
- get scrollOffset() {
106
- return Number(this.options.scrollOffset || -100);
107
- }
108
-
109
- get fieldSelector() {
110
- return this.options.fieldSelector || 'input, select, textarea';
111
- }
112
-
113
- get validatedClass() {
114
- return this.options.validatedClass || 'was-validated';
115
- }
116
-
117
- init() {
118
- if (this.$form.tagName === 'FORM') {
119
- this.$form.setAttribute('novalidate', 'true');
120
- this.$form.addEventListener('submit', (event) => {
121
- if (this.options.enabled && !this.validateAll()) {
122
- event.stopImmediatePropagation(); // Stop following events
123
- event.stopPropagation();
124
- event.preventDefault();
125
-
126
- this.$form.dispatchEvent(new CustomEvent('invalid'));
127
-
128
- return false;
129
- }
130
-
131
- return true;
132
- }, false);
133
- }
134
-
135
- this.prepareFields(this.findDOMFields());
136
- this.prepareFields(this.presetFields);
137
- }
138
-
139
- findDOMFields(): HTMLElement[] {
140
- return selectAll(this.$form.querySelectorAll<HTMLElement>(this.fieldSelector));
141
- }
142
-
143
- prepareFields(inputs: HTMLElement[]): Promise<void> {
144
- inputs.forEach((input) => {
145
- this.prepareFieldWrapper(input);
146
- });
147
-
148
- // Wait next tick
149
- return Promise.resolve();
150
- }
151
-
152
- prepareFieldWrapper(input: HTMLElement): HTMLElement | null {
153
- if (['INPUT', 'SELECT', 'TEXTAREA'].indexOf(input.tagName) !== -1) {
154
- let wrapper: HTMLElement | null = input.closest('[uni-field-validate]');
155
-
156
- if (!wrapper) {
157
- wrapper = input.closest('[data-input-container]') || input.parentElement;
158
-
159
- wrapper?.setAttribute('uni-field-validate', '{}');
160
- }
161
-
162
- return wrapper;
163
- }
164
-
165
- return input;
166
- }
167
-
168
- findFields(containsPresets: boolean = true): HTMLElement[] {
169
- let inputs = this.findDOMFields();
170
-
171
- if (containsPresets) {
172
- inputs.push(...this.presetFields);
173
- }
174
-
175
- return inputs.map((input) => this.prepareFieldWrapper(input))
176
- .filter(input => input != null) as HTMLElement[];
177
- }
178
-
179
- getFieldComponent(input: HTMLElement): UnicornFieldValidation | null {
180
- let v = getBoundedInstance(input, 'field.validation');
181
-
182
- if (!v) {
183
- const wrapper = input.closest('[uni-field-validate]') as HTMLElement | null;
184
-
185
- if (wrapper) {
186
- v = getBoundedInstance(wrapper, 'field.validation');
187
- }
188
- }
189
-
190
- return v;
191
- }
192
-
193
- validateAll(fields?: Nullable<HTMLElement[]>): boolean {
194
- this.markFormAsUnvalidated();
195
-
196
- fields = fields || this.findFields();
197
- let firstFail: HTMLElement | null = null;
198
-
199
- for (const field of fields) {
200
- const fv = this.getFieldComponent(field);
201
-
202
- if (!fv) {
203
- continue;
204
- }
205
-
206
- const result = fv.checkValidity();
207
-
208
- if (!result && !firstFail) {
209
- firstFail = field;
210
- }
211
- }
212
-
213
- this.markFormAsValidated();
214
-
215
- if (firstFail && this.scrollEnabled) {
216
- this.scrollTo(firstFail);
217
- }
218
-
219
- return firstFail === null;
220
- }
221
-
222
- async validateAllAsync(fields?: Nullable<HTMLElement[]>): Promise<boolean> {
223
- this.markFormAsUnvalidated();
224
-
225
- fields = fields || this.findFields();
226
- let firstFail: HTMLElement | null = null;
227
- const promises: Promise<boolean>[] = [];
228
-
229
- for (const field of fields) {
230
- const fv = this.getFieldComponent(field);
231
-
232
- if (!fv) {
233
- continue;
234
- }
235
-
236
- promises.push(
237
- fv.checkValidityAsync().then((result) => {
238
- if (!result && !firstFail) {
239
- firstFail = field;
240
- }
241
-
242
- return result;
243
- })
244
- );
245
- }
246
-
247
- await Promise.all(promises);
248
-
249
- this.markFormAsValidated();
250
-
251
- if (firstFail && this.scrollEnabled) {
252
- this.scrollTo(firstFail);
253
- }
254
-
255
- return firstFail === null;
256
- }
257
-
258
- scrollTo(element: HTMLElement): void {
259
- const offset = this.scrollOffset;
260
- const elementPosition = element.getBoundingClientRect().top;
261
- const offsetPosition = elementPosition + window.scrollY + offset;
262
-
263
- window.scrollTo({
264
- top: offsetPosition,
265
- behavior: 'smooth'
266
- });
267
- }
268
-
269
- markFormAsValidated(): void {
270
- if (!this.$form) {
271
- return;
272
- }
273
-
274
- this.$form.classList.add(this.validatedClass);
275
- }
276
-
277
- markFormAsUnvalidated(): void {
278
- if (!this.$form) {
279
- return;
280
- }
281
-
282
- this.$form.classList.remove(this.validatedClass);
283
- }
284
-
285
- addField(field: HTMLElement): this {
286
- this.presetFields.push(field);
287
-
288
- this.prepareFieldWrapper(field);
289
-
290
- return this;
291
- }
292
-
293
- registerDefaultValidators(): void {
294
- for (let name in validatorHandlers) {
295
- this.addValidator(name, validatorHandlers[name]);
296
- }
297
- }
298
-
299
- /**
300
- * Add validator handler.
301
- */
302
- addValidator<T extends any, E extends HTMLElement, P = Record<string, any>>(
303
- name: string,
304
- handler: ValidationHandler<T, E, P>,
305
- options: ValidatorOptions<E, P> = {}
306
- ) {
307
- options = options || {};
308
-
309
- this.validators[name] = {
310
- handler,
311
- options
312
- };
313
-
314
- return this;
315
- }
316
-
317
- /**
318
- * Add validator handler.
319
- */
320
- static addGlobalValidator<T extends any, E extends HTMLElement, P = Record<string, any>>(
321
- name: string,
322
- handler: ValidationHandler<T, E, P>,
323
- options: ValidatorOptions<E, P> = {}
324
- ) {
325
- options = options || {};
326
-
327
- this.globalValidators[name] = {
328
- handler,
329
- options
330
- };
331
-
332
- return this;
333
- }
334
- }
335
-
336
- export class UnicornFieldValidation {
337
- $input: InputElements | undefined;
338
- options: FieldValidationOptions;
339
-
340
- static is = 'uni-field-validate';
341
-
342
- constructor(protected el: HTMLElement, options: Partial<FieldValidationOptions> = {}) {
343
- this.options = this.mergeOptions(options);
344
-
345
- this.$input = this.selectInput();
346
-
347
- this.init();
348
- }
349
-
350
- mergeOptions(options: Partial<FieldValidationOptions>) {
351
- // Fix PHP empty array to JSON issue.
352
- if (Array.isArray(options)) {
353
- options = {};
354
- }
355
-
356
- return this.options = mergeDeep({}, defaultFieldOptions, options);
357
- }
358
-
359
- get $form(): HTMLFormElement {
360
- return this.getForm();
361
- }
362
-
363
- get errorSelector(): string {
364
- return this.options.errorSelector;
365
- }
366
-
367
- get selector(): string {
368
- return this.options.selector;
369
- }
370
-
371
- get validClass(): string {
372
- return this.options.validClass;
373
- }
374
-
375
- get invalidClass(): string {
376
- return this.options.invalidClass;
377
- }
378
-
379
- get isVisible(): boolean {
380
- return !!(this.el.offsetWidth || this.el.offsetHeight || this.el.getClientRects().length);
381
- }
382
-
383
- get isInputOptions(): boolean {
384
- return Boolean(this.options.inputOptions);
385
- }
386
-
387
- get validationMessage(): string {
388
- return this.$input?.validationMessage || '';
389
- }
390
-
391
- get validity(): ValidityState | undefined {
392
- return this.$input?.validity;
393
- }
394
-
395
- selectInput(): InputElements | undefined {
396
- let selector = this.selector;
397
-
398
- if (this.options.inputOptions) {
399
- selector += ', ' + this.options.inputOptionsWrapperSelector;
400
- }
401
-
402
- let input = this.el.querySelector<InputElements>(selector);
403
-
404
- if (!input) {
405
- input = this.el.querySelector<InputElements>('input, select, textarea');
406
- }
407
-
408
- if (!input) {
409
- return undefined;
410
- }
411
-
412
- return this.$input = input;
413
- }
414
-
415
- init() {
416
- this.selectInput();
417
-
418
- this.bindEvents();
419
-
420
- this.prepareWrapper();
421
-
422
- if (this.isInputOptions) {
423
- const $input = this.$input as any;
424
-
425
- if (
426
- !($input instanceof HTMLInputElement)
427
- && !($input instanceof HTMLSelectElement)
428
- && !($input instanceof HTMLTextAreaElement)
429
- ) {
430
- $input.validationMessage = '';
431
-
432
- $input.setCustomValidity = (msg: string) => {
433
- $input.validationMessage = String(msg);
434
- };
435
-
436
- $input.checkValidity = () => {
437
- return this.checkInputOptionsValidity();
438
- };
439
- }
440
- }
441
- }
442
-
443
- bindEvents() {
444
- if (!this.$input) {
445
- return;
446
- }
447
-
448
- this.$input.addEventListener('invalid', (e) => {
449
- this.showInvalidResponse();
450
- });
451
-
452
- const events = this.options.events;
453
-
454
- events.forEach((eventName) => {
455
- this.$input?.addEventListener(eventName, () => {
456
- this.checkValidity();
457
- });
458
- });
459
- }
460
-
461
- prepareWrapper() {
462
- if (this.el.querySelector(this.errorSelector)?.classList?.contains('invalid-tooltip')) {
463
- if (window.getComputedStyle(this.el).position === 'static') {
464
- this.el.style.position = 'relative';
465
- }
466
- }
467
- }
468
-
469
- checkValidity() {
470
- if (!this.$input) {
471
- return true;
472
- }
473
-
474
- if (this.$input.hasAttribute('readonly')) {
475
- return true;
476
- }
477
-
478
- if (this.$input.hasAttribute('[data-novalidate]')) {
479
- return true;
480
- }
481
-
482
- if (this.$input.closest('[data-novalidate]')) {
483
- return true;
484
- }
485
-
486
- this.$input.setCustomValidity('');
487
- let valid = this.$input.checkValidity();
488
-
489
- if (valid && this.$form) {
490
- valid = this.runCustomValidity();
491
- }
492
-
493
- // Raise invalid event
494
- // this.$input.checkValidity();
495
-
496
- this.updateValidClass(valid);
497
-
498
- return valid;
499
- }
500
-
501
- runCustomValidity() {
502
- if (!this.$input) {
503
- return true;
504
- }
505
-
506
- // Check custom validity
507
- const validates = (this.$input.getAttribute('data-validate') || '').split('|');
508
- let result = true;
509
-
510
- if (this.$input.value !== '' && validates.length) {
511
- if (!this.checkCustomDataAttributeValidity()) {
512
- return false;
513
- }
514
-
515
- for (const validatorName of validates) {
516
- const [validator, options] = this.getValidator(validatorName) || [null, {}];
517
-
518
- if (!validator) {
519
- continue;
520
- }
521
-
522
- Object.assign(options, validator.options);
523
-
524
- let r = validator.handler(this.$input.value, this.$input, options, this);
525
-
526
- // If return is a promise, push to stack and resolve later
527
- if (r instanceof Promise || (typeof r === 'object' && r.then)) {
528
- r.then((result: boolean) => {
529
- this.handleAsyncCustomResult(result, validator);
530
- });
531
- continue;
532
- }
533
-
534
- if (!this.handleCustomResult(r, validator)) {
535
- result = false;
536
-
537
- break;
538
- }
539
- }
540
- }
541
-
542
- return result;
543
- }
544
-
545
- async checkValidityAsync() {
546
- if (!this.$input) {
547
- return true;
548
- }
549
-
550
- if (this.$input.hasAttribute('readonly')) {
551
- return true;
552
- }
553
-
554
- this.$input.setCustomValidity('');
555
- let valid = this.$input.checkValidity();
556
-
557
- if (valid && this.$form) {
558
- valid = await this.runCustomValidityAsync();
559
- }
560
-
561
- this.updateValidClass(valid);
562
-
563
- return valid;
564
- }
565
-
566
- async runCustomValidityAsync(): Promise<boolean> {
567
- if (!this.$input) {
568
- return true;
569
- }
570
-
571
- // Check custom validity
572
- const validates = (this.$input.getAttribute('data-validate') || '').split('|');
573
-
574
- const results: Array<boolean | string | undefined> = [];
575
- const promises: Promise<boolean>[] = [];
576
-
577
- if (this.$input.value !== '' && validates.length) {
578
- if (!this.checkCustomDataAttributeValidity()) {
579
- return false;
580
- }
581
-
582
- for (const validatorName of validates) {
583
- let [validator, options] = this.getValidator(validatorName) || [null, {}];
584
-
585
- if (!validator) {
586
- continue;
587
- }
588
-
589
- options = Object.assign({}, options, validator.options || {});
590
-
591
- promises.push(
592
- Promise.resolve(validator.handler(this.$input.value, this.$input, options, this))
593
- .then((r) => {
594
- results.push(this.handleAsyncCustomResult(r, validator));
595
-
596
- return r;
597
- })
598
- );
599
- }
600
- }
601
-
602
- await Promise.all(promises);
603
-
604
- for (const result of results) {
605
- if (result === false) {
606
- return false;
607
- }
608
- }
609
-
610
- return true;
611
- }
612
-
613
- checkCustomDataAttributeValidity(): boolean {
614
- const error = this.$input?.dataset.validationFail;
615
-
616
- return this.handleCustomResult(error);
617
- }
618
-
619
- checkInputOptionsValidity(): boolean {
620
- if (!this.$input) {
621
- return true;
622
- }
623
-
624
- const isRequired = this.$input.getAttribute('required') != null;
625
- const optionWrappers = this.$input.querySelectorAll(this.options.inputOptionsSelector);
626
- let result = true;
627
-
628
- if (isRequired) {
629
- for (const optionWrapper of optionWrappers) {
630
- const input = optionWrapper.querySelector('input');
631
-
632
- result = false;
633
-
634
- // Only need one checked
635
- if (input?.checked) {
636
- result = true;
637
- break;
638
- }
639
- }
640
- }
641
-
642
- // Get browser input validation message
643
- const n = document.createElement('input');
644
- n.required = isRequired;
645
-
646
- if (result) {
647
- n.value = 'placeholder';
648
- }
649
-
650
- n.checkValidity();
651
-
652
- (this.$input as any).validationMessage = n.validationMessage;
653
- (this.$input as any).validity = n.validity;
654
-
655
- for (const optionWrapper of optionWrappers) {
656
- const input = optionWrapper.querySelector<HTMLInputElement>('input');
657
-
658
- input?.setCustomValidity(n.validationMessage);
659
- }
660
-
661
- if (!result) {
662
- this.$input.dispatchEvent(
663
- new CustomEvent('invalid')
664
- );
665
- }
666
-
667
- return result;
668
- }
669
-
670
- /**
671
- * @param valid {boolean}
672
- */
673
- updateValidClass(valid: Boolean) {
674
- const $errorElement = this.getErrorElement();
675
- const $invalidTarget = $errorElement?.previousElementSibling;
676
-
677
- this.$input?.classList.remove(this.invalidClass);
678
- this.$input?.classList.remove(this.validClass);
679
- this.el.classList.remove(this.invalidClass);
680
- this.el.classList.remove(this.validClass);
681
- $invalidTarget?.classList.remove(this.invalidClass);
682
- $invalidTarget?.classList.remove(this.validClass);
683
-
684
- if (valid) {
685
- this.$input?.classList.add(this.validClass);
686
- this.el.classList.add(this.validClass);
687
-
688
- $invalidTarget?.classList.add(this.validClass);
689
- } else {
690
- this.$input?.classList.add(this.invalidClass);
691
- this.el.classList.add(this.invalidClass);
692
-
693
- $invalidTarget?.classList.add(this.invalidClass);
694
- }
695
- }
696
-
697
- getFormValidation(element?: Nullable<HTMLFormElement>): UnicornFormValidation | null {
698
- return getBoundedInstance(element || this.getForm(), 'form.validation')!;
699
- }
700
-
701
- getValidator(name: string): [Validator, Record<string, any>] | null {
702
- const matches = name.match(/(?<type>[\w\-_]+)(\((?<params>.*)\))*/);
703
-
704
- if (!matches) {
705
- return null;
706
- }
707
-
708
- const validatorName = matches.groups?.type || '';
709
-
710
- const params = matches.groups?.params || '';
711
-
712
- const fv = this.getFormValidation(this.$form!);
713
- const validator = fv?.validators[validatorName] || UnicornFormValidation.globalValidators[validatorName];
714
-
715
- if (!validator) {
716
- return null;
717
- }
718
-
719
- const paramMatches = params.matchAll(/(?<key>\w+)(\s?[=:]\s?(?<value>\w+))?/g);
720
- const options: Record<string, string> = {};
721
-
722
- for (const paramMatch of paramMatches) {
723
- const match = paramMatch?.groups as {
724
- key: string;
725
- value: string;
726
- } | undefined;
727
-
728
- if (!match) {
729
- continue;
730
- }
731
-
732
- options[match.key] = handleParamValue(match.value);
733
- }
734
-
735
- return [validator, options];
736
- }
737
-
738
- handleCustomResult(result: boolean | string | undefined, validator?: Nullable<Validator>): boolean {
739
- if (typeof result === 'string') {
740
- this.$input?.setCustomValidity(result);
741
- result = result === '';
742
- } else if (result === undefined) {
743
- result = true;
744
- }
745
-
746
- if (result) {
747
- this.$input?.setCustomValidity('');
748
- } else if (validator) {
749
- this.raiseCustomErrorState(validator);
750
- }
751
-
752
- return result;
753
- }
754
-
755
- handleAsyncCustomResult(result: boolean, validator?: Nullable<Validator>): boolean {
756
- result = this.handleCustomResult(result, validator);
757
-
758
- // Fire invalid events
759
- this.$input?.checkValidity();
760
-
761
- this.updateValidClass(result);
762
-
763
- return result;
764
- }
765
-
766
- raiseCustomErrorState(validator: Validator): void {
767
- let help;
768
-
769
- if (this.$input?.validationMessage === '') {
770
- help = validator.options?.notice;
771
-
772
- if (typeof help === 'function') {
773
- help = help(this.$input, this);
774
- }
775
-
776
- if (help != null) {
777
- this.$input?.setCustomValidity(help);
778
- }
779
- }
780
-
781
- if (this.$input?.validationMessage === '') {
782
- this.$input?.setCustomValidity(trans('unicorn.message.validation.custom.error'));
783
- }
784
-
785
- this.$input?.dispatchEvent(
786
- new CustomEvent('invalid')
787
- );
788
- }
789
-
790
- setAsInvalidAndReport(error: string) {
791
- this.setCustomValidity(error);
792
- this.showInvalidResponse();
793
- }
794
-
795
- setCustomValidity(error: string) {
796
- this.$input?.setCustomValidity(error);
797
- }
798
-
799
- reportValidity() {
800
- if (this.validationMessage !== '') {
801
- this.showInvalidResponse();
802
- }
803
- }
804
-
805
- showInvalidResponse() {
806
- /** @type ValidityState */
807
- const state = this.$input?.validity;
808
- let message: string = this.$input?.validationMessage || '';
809
-
810
- for (let key in state) {
811
- if (state[(key as keyof ValidityState)] && this.$input?.dataset[key + 'Message']) {
812
- message = this.$input?.dataset[key + 'Message'] || '';
813
- break;
814
- }
815
- }
816
-
817
- if (!this.isVisible) {
818
- let title = this.findLabel()?.textContent;
819
-
820
- if (!title) {
821
- title = this.$input?.name || '';
822
- }
823
-
824
- useUITheme().renderMessage(
825
- `Field: ${title} - ${message}`,
826
- 'warning'
827
- );
828
- }
829
-
830
- let $help = this.getErrorElement();
831
-
832
- if (!$help) {
833
- $help = this.createHelpElement()!;
834
- this.el.appendChild($help);
835
- this.prepareWrapper();
836
- }
837
-
838
- $help.textContent = message;
839
-
840
- this.updateValidClass(false);
841
- }
842
-
843
- getErrorElement() {
844
- return this.el.querySelector(this.errorSelector);
845
- }
846
-
847
- createHelpElement() {
848
- const className = this.options.errorMessageClass;
849
- const parsed = this.parseSelector(this.errorSelector || '');
850
-
851
- const $help = html(`<div class="${className}"></div>`)!;
852
-
853
- $help.classList.add(...parsed.classes);
854
-
855
- parsed.attrs.forEach((attr) => {
856
- $help.setAttribute(attr[0], attr[1] || '');
857
- });
858
-
859
- parsed.ids.forEach((id) => {
860
- $help.id = id;
861
- });
862
-
863
- return $help;
864
- }
865
-
866
- /**
867
- * @see https://stackoverflow.com/a/17888178
868
- */
869
- parseSelector(subselector: string): { tags: string[]; classes: string[]; ids: string[]; attrs: string[][] } {
870
- const obj: {
871
- tags: string[];
872
- classes: string[];
873
- ids: string[];
874
- attrs: string[][];
875
- } = { tags: [], classes: [], ids: [], attrs: [] };
876
- for (const token of subselector.split(/(?=\.)|(?=#)|(?=\[)/)) {
877
- switch (token[0]) {
878
- case '#':
879
- obj.ids.push(token.slice(1));
880
- break;
881
- case '.':
882
- obj.classes.push(token.slice(1));
883
- break;
884
- case '[':
885
- obj.attrs.push(token.slice(1, -1).split('='));
886
- break;
887
- default :
888
- obj.tags.push(token);
889
- break;
890
- }
891
- }
892
- return obj;
893
- }
894
-
895
- setAsValidAndClearResponse() {
896
- this.setCustomValidity('');
897
- this.updateValidClass(true);
898
- this.clearInvalidResponse();
899
- }
900
-
901
- clearInvalidResponse() {
902
- const $help = this.el.querySelector(this.errorSelector)!;
903
-
904
- $help.textContent = '';
905
- }
906
-
907
- getForm() {
908
- return this.el.closest(this.options.formSelector || '[uni-form-validate]') as HTMLFormElement;
909
- }
910
-
911
- findLabel() {
912
- const id = this.$input?.id || '';
913
-
914
- const wrapper = this.$input?.closest('[data-field-wrapper]');
915
- let label = null;
916
-
917
- if (wrapper) {
918
- label = wrapper.querySelector('[data-field-label]');
919
- }
920
-
921
- if (!label) {
922
- label = document.querySelector(`label[for="${id}"]`);
923
- }
924
-
925
- return label;
926
- }
927
- }
928
-
929
- function camelTo(str: string, sep: string) {
930
- return str.replace(/([a-z])([A-Z])/g, `$1${sep}$2`).toLowerCase();
931
- }
932
-
933
- validatorHandlers.username = function (value: any, element: HTMLElement) {
934
- const regex = new RegExp('[\<|\>|"|\'|\%|\;|\(|\)|\&]', 'i');
935
- return !regex.test(value);
936
- };
937
-
938
- validatorHandlers.numeric = function (value: any, element: HTMLElement) {
939
- const regex = /^(\d|-)?(\d|,)*\.?\d*$/;
940
- return regex.test(value);
941
- };
942
-
943
- validatorHandlers.email = function (value: any, element: HTMLElement) {
944
- value = punycode.toASCII(value);
945
- const regex = /^[a-zA-Z0-9.!#$%&’*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
946
- return regex.test(value);
947
- };
948
-
949
- validatorHandlers.url = function (value: any, element: HTMLElement) {
950
- const regex = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/i;
951
- return regex.test(value);
952
- };
953
-
954
- validatorHandlers.alnum = function (value: any, element: HTMLElement) {
955
- const regex = /^[a-zA-Z0-9]*$/;
956
- return regex.test(value);
957
- };
958
-
959
- validatorHandlers.color = function (value: any, element: HTMLElement) {
960
- const regex = /^#(?:[0-9a-f]{3}){1,2}$/;
961
- return regex.test(value);
962
- };
963
-
964
- /**
965
- * @see http://www.virtuosimedia.com/dev/php/37-tested-php-perl-and-javascript-regular-expressions
966
- */
967
- validatorHandlers.creditcard = function (value: any, element: HTMLElement) {
968
- const regex = /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6011[0-9]{12}|622((12[6-9]|1[3-9][0-9])|([2-8][0-9][0-9])|(9(([0-1][0-9])|(2[0-5]))))[0-9]{10}|64[4-9][0-9]{13}|65[0-9]{14}|3(?:0[0-5]|[68][0-9])[0-9]{11}|3[47][0-9]{13})*$/;
969
- return regex.test(value);
970
- };
971
-
972
- validatorHandlers.ip = function (value: any, element: HTMLElement) {
973
- const regex = /^((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))*$/;
974
- return regex.test(value);
975
- };
976
-
977
- validatorHandlers['password-confirm'] = function (value: any, element: HTMLElement) {
978
- const selector = element.dataset.confirmTarget;
979
-
980
- if (!selector) {
981
- throw new Error('Validator: "password-confirm" must add "data-confirm-target" attribute.');
982
- }
983
-
984
- const target = document.querySelector<HTMLInputElement>(selector);
985
-
986
- return target?.value === value;
987
- };
988
-
989
- export { validatorHandlers as validators };
990
-
991
- // customElements.define(UnicornFormValidateElement.is, UnicornFormValidateElement);
992
- // customElements.define(UnicornFieldValidateElement.is, UnicornFieldValidateElement);
993
-
994
- export const ready = Promise.all([
995
- useUniDirective('form-validate', {
996
- mounted(el, binding) {
997
- getBoundedInstance(el, 'form.validation', (ele) => {
998
- return new UnicornFormValidation(ele as HTMLElement, JSON.parse(binding.value || '{}'));
999
- });
1000
- },
1001
- updated(el, binding) {
1002
- const instance = getBoundedInstance<UnicornFormValidation>(el, 'form.validation');
1003
- instance.mergeOptions(JSON.parse(binding.value || '{}'));
1004
- }
1005
- }),
1006
-
1007
- useUniDirective('field-validate', {
1008
- mounted(el, binding) {
1009
- getBoundedInstance<UnicornFieldValidation>(el, 'field.validation', (ele) => {
1010
- return new UnicornFieldValidation(ele as HTMLElement, JSON.parse(binding.value || '{}'));
1011
- });
1012
- },
1013
-
1014
- updated(el, binding) {
1015
- const instance = getBoundedInstance<UnicornFieldValidation>(el, 'field.validation');
1016
- instance.mergeOptions(JSON.parse(binding.value || '{}') || {});
1017
- }
1018
- })
1019
- ]);
1020
-
1021
- function handleParamValue(value: any) {
1022
- if (!isNaN(Number(value))) {
1023
- return Number(value);
1024
- }
1025
-
1026
- if (value === 'null') {
1027
- return null;
1028
- }
1029
-
1030
- if (value === 'true') {
1031
- return true;
1032
- }
1033
-
1034
- if (value === 'false') {
1035
- return true;
1036
- }
1037
-
1038
- return value;
1039
- }
1040
-
1041
- export interface ValidationModule {
1042
- UnicornFormValidation: typeof UnicornFormValidation;
1043
- UnicornFieldValidation: typeof UnicornFieldValidation;
1044
- ready: Promise<any>;
1045
- validators: typeof validatorHandlers;
1046
- }
1
+ import * as punycode from 'punycode';
2
+ import { useUniDirective } from '../composable';
3
+ import { getBoundedInstance, html, selectAll, selectOne, trans, useUITheme } from '../service';
4
+ import { Nullable } from '../types';
5
+ import { mergeDeep } from '../utilities';
6
+
7
+ export declare type ValidationHandler<V = any, E = HTMLElement, P = Record<string, any>> = (
8
+ value: V,
9
+ input: E,
10
+ options?: ValidatorOptions<E, P>,
11
+ fv?: UnicornFieldValidation
12
+ ) => any;
13
+
14
+ export declare type ValidatorNoticeFunction<E = HTMLElement> = (input: E, field: UnicornFieldValidation) => any;
15
+
16
+ export type ValidatorOptions<E = HTMLElement, P = Record<string, any>> = {
17
+ notice?: ValidatorNoticeFunction<E> | string;
18
+ [name: string]: any;
19
+ } & Partial<P>;
20
+
21
+ export declare type Validator<V = any, E = HTMLElement, P = Record<string, any>> = {
22
+ handler: ValidationHandler<V, E, P>,
23
+ options?: ValidatorOptions<E, P>;
24
+ };
25
+
26
+ const validatorHandlers: Record<string, ValidationHandler<any, any>> = {};
27
+
28
+ type InputElements = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
29
+
30
+ export interface FormValidationOptions {
31
+ scroll: boolean;
32
+ validatedClass: null;
33
+ fieldSelector: null;
34
+ scrollOffset: number;
35
+ enabled: boolean;
36
+ }
37
+
38
+ export interface FieldValidationOptions {
39
+ validClass: string;
40
+ errorSelector: string;
41
+ inputOptions: boolean;
42
+ inputOptionsSelector: string;
43
+ formSelector: string;
44
+ selector: string;
45
+ inputOptionsWrapperSelector: string;
46
+ events: string[];
47
+ invalidClass: string;
48
+ errorMessageClass: string;
49
+ }
50
+
51
+ const defaultOptions: FormValidationOptions = {
52
+ scroll: false,
53
+ scrollOffset: -100,
54
+ enabled: true,
55
+ fieldSelector: null,
56
+ validatedClass: null,
57
+ };
58
+
59
+ const defaultFieldOptions: FieldValidationOptions = {
60
+ formSelector: '[uni-form-validate]',
61
+ errorSelector: '[data-field-error]',
62
+ selector: 'input[data-field-input], select[data-field-input], textarea[data-field-input]',
63
+ validClass: 'is-valid',
64
+ invalidClass: 'is-invalid',
65
+ events: ['change'],
66
+ errorMessageClass: 'invalid-tooltip',
67
+ inputOptions: false,
68
+ inputOptionsWrapperSelector: 'div[data-field-input]',
69
+ inputOptionsSelector: '[data-input-option]'
70
+ };
71
+
72
+ export class UnicornFormValidation {
73
+ presetFields: HTMLElement[] = [];
74
+
75
+ static globalValidators: Record<string, Validator<any, any, any>> = {};
76
+
77
+ validators: Record<string, Validator<any, any, any>> = {};
78
+ options: FormValidationOptions;
79
+ $form: HTMLElement;
80
+
81
+ static is = 'uni-form-validate';
82
+
83
+ constructor(el: HTMLElement, options: Partial<FormValidationOptions> = {}) {
84
+ this.$form = selectOne(el);
85
+ this.options = this.mergeOptions(options);
86
+
87
+ this.registerDefaultValidators();
88
+
89
+ this.init();
90
+ }
91
+
92
+ mergeOptions(options: Partial<FormValidationOptions>) {
93
+ // Fix PHP empty array to JSON issue.
94
+ if (Array.isArray(options)) {
95
+ options = {};
96
+ }
97
+
98
+ return this.options = mergeDeep({}, defaultOptions, options);
99
+ }
100
+
101
+ get scrollEnabled() {
102
+ return this.options.scroll;
103
+ }
104
+
105
+ get scrollOffset() {
106
+ return Number(this.options.scrollOffset || -100);
107
+ }
108
+
109
+ get fieldSelector() {
110
+ return this.options.fieldSelector || 'input, select, textarea';
111
+ }
112
+
113
+ get validatedClass() {
114
+ return this.options.validatedClass || 'was-validated';
115
+ }
116
+
117
+ init() {
118
+ if (this.$form.tagName === 'FORM') {
119
+ this.$form.setAttribute('novalidate', 'true');
120
+ this.$form.addEventListener('submit', (event) => {
121
+ if (this.options.enabled && !this.validateAll()) {
122
+ event.stopImmediatePropagation(); // Stop following events
123
+ event.stopPropagation();
124
+ event.preventDefault();
125
+
126
+ this.$form.dispatchEvent(new CustomEvent('invalid'));
127
+
128
+ return false;
129
+ }
130
+
131
+ return true;
132
+ }, false);
133
+ }
134
+
135
+ this.prepareFields(this.findDOMFields());
136
+ this.prepareFields(this.presetFields);
137
+ }
138
+
139
+ findDOMFields(): HTMLElement[] {
140
+ return selectAll(this.$form.querySelectorAll<HTMLElement>(this.fieldSelector));
141
+ }
142
+
143
+ prepareFields(inputs: HTMLElement[]): Promise<void> {
144
+ inputs.forEach((input) => {
145
+ this.prepareFieldWrapper(input);
146
+ });
147
+
148
+ // Wait next tick
149
+ return Promise.resolve();
150
+ }
151
+
152
+ prepareFieldWrapper(input: HTMLElement): HTMLElement | null {
153
+ if (['INPUT', 'SELECT', 'TEXTAREA'].indexOf(input.tagName) !== -1) {
154
+ let wrapper: HTMLElement | null = input.closest('[uni-field-validate]');
155
+
156
+ if (!wrapper) {
157
+ wrapper = input.closest('[data-input-container]') || input.parentElement;
158
+
159
+ wrapper?.setAttribute('uni-field-validate', '{}');
160
+ }
161
+
162
+ return wrapper;
163
+ }
164
+
165
+ return input;
166
+ }
167
+
168
+ findFields(containsPresets: boolean = true): HTMLElement[] {
169
+ let inputs = this.findDOMFields();
170
+
171
+ if (containsPresets) {
172
+ inputs.push(...this.presetFields);
173
+ }
174
+
175
+ return inputs.map((input) => this.prepareFieldWrapper(input))
176
+ .filter(input => input != null) as HTMLElement[];
177
+ }
178
+
179
+ getFieldComponent(input: HTMLElement): UnicornFieldValidation | null {
180
+ let v = getBoundedInstance(input, 'field.validation');
181
+
182
+ if (!v) {
183
+ const wrapper = input.closest('[uni-field-validate]') as HTMLElement | null;
184
+
185
+ if (wrapper) {
186
+ v = getBoundedInstance(wrapper, 'field.validation');
187
+ }
188
+ }
189
+
190
+ return v;
191
+ }
192
+
193
+ validateAll(fields?: Nullable<HTMLElement[]>): boolean {
194
+ this.markFormAsUnvalidated();
195
+
196
+ fields = fields || this.findFields();
197
+ let firstFail: HTMLElement | null = null;
198
+
199
+ for (const field of fields) {
200
+ const fv = this.getFieldComponent(field);
201
+
202
+ if (!fv) {
203
+ continue;
204
+ }
205
+
206
+ const result = fv.checkValidity();
207
+
208
+ if (!result && !firstFail) {
209
+ firstFail = field;
210
+ }
211
+ }
212
+
213
+ this.markFormAsValidated();
214
+
215
+ if (firstFail && this.scrollEnabled) {
216
+ this.scrollTo(firstFail);
217
+ }
218
+
219
+ return firstFail === null;
220
+ }
221
+
222
+ async validateAllAsync(fields?: Nullable<HTMLElement[]>): Promise<boolean> {
223
+ this.markFormAsUnvalidated();
224
+
225
+ fields = fields || this.findFields();
226
+ let firstFail: HTMLElement | null = null;
227
+ const promises: Promise<boolean>[] = [];
228
+
229
+ for (const field of fields) {
230
+ const fv = this.getFieldComponent(field);
231
+
232
+ if (!fv) {
233
+ continue;
234
+ }
235
+
236
+ promises.push(
237
+ fv.checkValidityAsync().then((result) => {
238
+ if (!result && !firstFail) {
239
+ firstFail = field;
240
+ }
241
+
242
+ return result;
243
+ })
244
+ );
245
+ }
246
+
247
+ await Promise.all(promises);
248
+
249
+ this.markFormAsValidated();
250
+
251
+ if (firstFail && this.scrollEnabled) {
252
+ this.scrollTo(firstFail);
253
+ }
254
+
255
+ return firstFail === null;
256
+ }
257
+
258
+ scrollTo(element: HTMLElement): void {
259
+ const offset = this.scrollOffset;
260
+ const elementPosition = element.getBoundingClientRect().top;
261
+ const offsetPosition = elementPosition + window.scrollY + offset;
262
+
263
+ window.scrollTo({
264
+ top: offsetPosition,
265
+ behavior: 'smooth'
266
+ });
267
+ }
268
+
269
+ markFormAsValidated(): void {
270
+ if (!this.$form) {
271
+ return;
272
+ }
273
+
274
+ this.$form.classList.add(this.validatedClass);
275
+ }
276
+
277
+ markFormAsUnvalidated(): void {
278
+ if (!this.$form) {
279
+ return;
280
+ }
281
+
282
+ this.$form.classList.remove(this.validatedClass);
283
+ }
284
+
285
+ addField(field: HTMLElement): this {
286
+ this.presetFields.push(field);
287
+
288
+ this.prepareFieldWrapper(field);
289
+
290
+ return this;
291
+ }
292
+
293
+ registerDefaultValidators(): void {
294
+ for (let name in validatorHandlers) {
295
+ this.addValidator(name, validatorHandlers[name]);
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Add validator handler.
301
+ */
302
+ addValidator<T extends any, E extends HTMLElement, P = Record<string, any>>(
303
+ name: string,
304
+ handler: ValidationHandler<T, E, P>,
305
+ options: ValidatorOptions<E, P> = {}
306
+ ) {
307
+ options = options || {};
308
+
309
+ this.validators[name] = {
310
+ handler,
311
+ options
312
+ };
313
+
314
+ return this;
315
+ }
316
+
317
+ /**
318
+ * Add validator handler.
319
+ */
320
+ static addGlobalValidator<T extends any, E extends HTMLElement, P = Record<string, any>>(
321
+ name: string,
322
+ handler: ValidationHandler<T, E, P>,
323
+ options: ValidatorOptions<E, P> = {}
324
+ ) {
325
+ options = options || {};
326
+
327
+ this.globalValidators[name] = {
328
+ handler,
329
+ options
330
+ };
331
+
332
+ return this;
333
+ }
334
+ }
335
+
336
+ export class UnicornFieldValidation {
337
+ $input: InputElements | undefined;
338
+ options: FieldValidationOptions;
339
+
340
+ static is = 'uni-field-validate';
341
+
342
+ constructor(protected el: HTMLElement, options: Partial<FieldValidationOptions> = {}) {
343
+ this.options = this.mergeOptions(options);
344
+
345
+ this.$input = this.selectInput();
346
+
347
+ this.init();
348
+ }
349
+
350
+ mergeOptions(options: Partial<FieldValidationOptions>) {
351
+ // Fix PHP empty array to JSON issue.
352
+ if (Array.isArray(options)) {
353
+ options = {};
354
+ }
355
+
356
+ return this.options = mergeDeep({}, defaultFieldOptions, options);
357
+ }
358
+
359
+ get $form(): HTMLFormElement {
360
+ return this.getForm();
361
+ }
362
+
363
+ get errorSelector(): string {
364
+ return this.options.errorSelector;
365
+ }
366
+
367
+ get selector(): string {
368
+ return this.options.selector;
369
+ }
370
+
371
+ get validClass(): string {
372
+ return this.options.validClass;
373
+ }
374
+
375
+ get invalidClass(): string {
376
+ return this.options.invalidClass;
377
+ }
378
+
379
+ get isVisible(): boolean {
380
+ return !!(this.el.offsetWidth || this.el.offsetHeight || this.el.getClientRects().length);
381
+ }
382
+
383
+ get isInputOptions(): boolean {
384
+ return Boolean(this.options.inputOptions);
385
+ }
386
+
387
+ get validationMessage(): string {
388
+ return this.$input?.validationMessage || '';
389
+ }
390
+
391
+ get validity(): ValidityState | undefined {
392
+ return this.$input?.validity;
393
+ }
394
+
395
+ selectInput(): InputElements | undefined {
396
+ let selector = this.selector;
397
+
398
+ if (this.options.inputOptions) {
399
+ selector += ', ' + this.options.inputOptionsWrapperSelector;
400
+ }
401
+
402
+ let input = this.el.querySelector<InputElements>(selector);
403
+
404
+ if (!input) {
405
+ input = this.el.querySelector<InputElements>('input, select, textarea');
406
+ }
407
+
408
+ if (!input) {
409
+ return undefined;
410
+ }
411
+
412
+ return this.$input = input;
413
+ }
414
+
415
+ init() {
416
+ this.selectInput();
417
+
418
+ this.bindEvents();
419
+
420
+ this.prepareWrapper();
421
+
422
+ if (this.isInputOptions) {
423
+ const $input = this.$input as any;
424
+
425
+ if (
426
+ !($input instanceof HTMLInputElement)
427
+ && !($input instanceof HTMLSelectElement)
428
+ && !($input instanceof HTMLTextAreaElement)
429
+ ) {
430
+ $input.validationMessage = '';
431
+
432
+ $input.setCustomValidity = (msg: string) => {
433
+ $input.validationMessage = String(msg);
434
+ };
435
+
436
+ $input.checkValidity = () => {
437
+ return this.checkInputOptionsValidity();
438
+ };
439
+ }
440
+ }
441
+ }
442
+
443
+ bindEvents() {
444
+ if (!this.$input) {
445
+ return;
446
+ }
447
+
448
+ this.$input.addEventListener('invalid', (e) => {
449
+ this.showInvalidResponse();
450
+ });
451
+
452
+ const events = this.options.events;
453
+
454
+ events.forEach((eventName) => {
455
+ this.$input?.addEventListener(eventName, () => {
456
+ this.checkValidity();
457
+ });
458
+ });
459
+ }
460
+
461
+ prepareWrapper() {
462
+ if (this.el.querySelector(this.errorSelector)?.classList?.contains('invalid-tooltip')) {
463
+ if (window.getComputedStyle(this.el).position === 'static') {
464
+ this.el.style.position = 'relative';
465
+ }
466
+ }
467
+ }
468
+
469
+ checkValidity() {
470
+ if (!this.$input) {
471
+ return true;
472
+ }
473
+
474
+ if (this.$input.hasAttribute('readonly')) {
475
+ return true;
476
+ }
477
+
478
+ if (this.$input.hasAttribute('[data-novalidate]')) {
479
+ return true;
480
+ }
481
+
482
+ if (this.$input.closest('[data-novalidate]')) {
483
+ return true;
484
+ }
485
+
486
+ this.$input.setCustomValidity('');
487
+ let valid = this.$input.checkValidity();
488
+
489
+ if (valid && this.$form) {
490
+ valid = this.runCustomValidity();
491
+ }
492
+
493
+ // Raise invalid event
494
+ // this.$input.checkValidity();
495
+
496
+ this.updateValidClass(valid);
497
+
498
+ return valid;
499
+ }
500
+
501
+ runCustomValidity() {
502
+ if (!this.$input) {
503
+ return true;
504
+ }
505
+
506
+ // Check custom validity
507
+ const validates = (this.$input.getAttribute('data-validate') || '').split('|');
508
+ let result = true;
509
+
510
+ if (this.$input.value !== '' && validates.length) {
511
+ if (!this.checkCustomDataAttributeValidity()) {
512
+ return false;
513
+ }
514
+
515
+ for (const validatorName of validates) {
516
+ const [validator, options] = this.getValidator(validatorName) || [null, {}];
517
+
518
+ if (!validator) {
519
+ continue;
520
+ }
521
+
522
+ Object.assign(options, validator.options);
523
+
524
+ let r = validator.handler(this.$input.value, this.$input, options, this);
525
+
526
+ // If return is a promise, push to stack and resolve later
527
+ if (r instanceof Promise || (typeof r === 'object' && r.then)) {
528
+ r.then((result: boolean) => {
529
+ this.handleAsyncCustomResult(result, validator);
530
+ });
531
+ continue;
532
+ }
533
+
534
+ if (!this.handleCustomResult(r, validator)) {
535
+ result = false;
536
+
537
+ break;
538
+ }
539
+ }
540
+ }
541
+
542
+ return result;
543
+ }
544
+
545
+ async checkValidityAsync() {
546
+ if (!this.$input) {
547
+ return true;
548
+ }
549
+
550
+ if (this.$input.hasAttribute('readonly')) {
551
+ return true;
552
+ }
553
+
554
+ this.$input.setCustomValidity('');
555
+ let valid = this.$input.checkValidity();
556
+
557
+ if (valid && this.$form) {
558
+ valid = await this.runCustomValidityAsync();
559
+ }
560
+
561
+ this.updateValidClass(valid);
562
+
563
+ return valid;
564
+ }
565
+
566
+ async runCustomValidityAsync(): Promise<boolean> {
567
+ if (!this.$input) {
568
+ return true;
569
+ }
570
+
571
+ // Check custom validity
572
+ const validates = (this.$input.getAttribute('data-validate') || '').split('|');
573
+
574
+ const results: Array<boolean | string | undefined> = [];
575
+ const promises: Promise<boolean>[] = [];
576
+
577
+ if (this.$input.value !== '' && validates.length) {
578
+ if (!this.checkCustomDataAttributeValidity()) {
579
+ return false;
580
+ }
581
+
582
+ for (const validatorName of validates) {
583
+ let [validator, options] = this.getValidator(validatorName) || [null, {}];
584
+
585
+ if (!validator) {
586
+ continue;
587
+ }
588
+
589
+ options = Object.assign({}, options, validator.options || {});
590
+
591
+ promises.push(
592
+ Promise.resolve(validator.handler(this.$input.value, this.$input, options, this))
593
+ .then((r) => {
594
+ results.push(this.handleAsyncCustomResult(r, validator));
595
+
596
+ return r;
597
+ })
598
+ );
599
+ }
600
+ }
601
+
602
+ await Promise.all(promises);
603
+
604
+ for (const result of results) {
605
+ if (result === false) {
606
+ return false;
607
+ }
608
+ }
609
+
610
+ return true;
611
+ }
612
+
613
+ checkCustomDataAttributeValidity(): boolean {
614
+ const error = this.$input?.dataset.validationFail;
615
+
616
+ return this.handleCustomResult(error);
617
+ }
618
+
619
+ checkInputOptionsValidity(): boolean {
620
+ if (!this.$input) {
621
+ return true;
622
+ }
623
+
624
+ const isRequired = this.$input.getAttribute('required') != null;
625
+ const optionWrappers = this.$input.querySelectorAll(this.options.inputOptionsSelector);
626
+ let result = true;
627
+
628
+ if (isRequired) {
629
+ for (const optionWrapper of optionWrappers) {
630
+ const input = optionWrapper.querySelector('input');
631
+
632
+ result = false;
633
+
634
+ // Only need one checked
635
+ if (input?.checked) {
636
+ result = true;
637
+ break;
638
+ }
639
+ }
640
+ }
641
+
642
+ // Get browser input validation message
643
+ const n = document.createElement('input');
644
+ n.required = isRequired;
645
+
646
+ if (result) {
647
+ n.value = 'placeholder';
648
+ }
649
+
650
+ n.checkValidity();
651
+
652
+ (this.$input as any).validationMessage = n.validationMessage;
653
+ (this.$input as any).validity = n.validity;
654
+
655
+ for (const optionWrapper of optionWrappers) {
656
+ const input = optionWrapper.querySelector<HTMLInputElement>('input');
657
+
658
+ input?.setCustomValidity(n.validationMessage);
659
+ }
660
+
661
+ if (!result) {
662
+ this.$input.dispatchEvent(
663
+ new CustomEvent('invalid')
664
+ );
665
+ }
666
+
667
+ return result;
668
+ }
669
+
670
+ /**
671
+ * @param valid {boolean}
672
+ */
673
+ updateValidClass(valid: Boolean) {
674
+ const $errorElement = this.getErrorElement();
675
+ const $invalidTarget = $errorElement?.previousElementSibling;
676
+
677
+ this.$input?.classList.remove(this.invalidClass);
678
+ this.$input?.classList.remove(this.validClass);
679
+ this.el.classList.remove(this.invalidClass);
680
+ this.el.classList.remove(this.validClass);
681
+ $invalidTarget?.classList.remove(this.invalidClass);
682
+ $invalidTarget?.classList.remove(this.validClass);
683
+
684
+ if (valid) {
685
+ this.$input?.classList.add(this.validClass);
686
+ this.el.classList.add(this.validClass);
687
+
688
+ $invalidTarget?.classList.add(this.validClass);
689
+ } else {
690
+ this.$input?.classList.add(this.invalidClass);
691
+ this.el.classList.add(this.invalidClass);
692
+
693
+ $invalidTarget?.classList.add(this.invalidClass);
694
+ }
695
+ }
696
+
697
+ getFormValidation(element?: Nullable<HTMLFormElement>): UnicornFormValidation | null {
698
+ return getBoundedInstance(element || this.getForm(), 'form.validation')!;
699
+ }
700
+
701
+ getValidator(name: string): [Validator, Record<string, any>] | null {
702
+ const matches = name.match(/(?<type>[\w\-_]+)(\((?<params>.*)\))*/);
703
+
704
+ if (!matches) {
705
+ return null;
706
+ }
707
+
708
+ const validatorName = matches.groups?.type || '';
709
+
710
+ const params = matches.groups?.params || '';
711
+
712
+ const fv = this.getFormValidation(this.$form!);
713
+ const validator = fv?.validators[validatorName] || UnicornFormValidation.globalValidators[validatorName];
714
+
715
+ if (!validator) {
716
+ return null;
717
+ }
718
+
719
+ const paramMatches = params.matchAll(/(?<key>\w+)(\s?[=:]\s?(?<value>\w+))?/g);
720
+ const options: Record<string, string> = {};
721
+
722
+ for (const paramMatch of paramMatches) {
723
+ const match = paramMatch?.groups as {
724
+ key: string;
725
+ value: string;
726
+ } | undefined;
727
+
728
+ if (!match) {
729
+ continue;
730
+ }
731
+
732
+ options[match.key] = handleParamValue(match.value);
733
+ }
734
+
735
+ return [validator, options];
736
+ }
737
+
738
+ handleCustomResult(result: boolean | string | undefined, validator?: Nullable<Validator>): boolean {
739
+ if (typeof result === 'string') {
740
+ this.$input?.setCustomValidity(result);
741
+ result = result === '';
742
+ } else if (result === undefined) {
743
+ result = true;
744
+ }
745
+
746
+ if (result) {
747
+ this.$input?.setCustomValidity('');
748
+ } else if (validator) {
749
+ this.raiseCustomErrorState(validator);
750
+ }
751
+
752
+ return result;
753
+ }
754
+
755
+ handleAsyncCustomResult(result: boolean, validator?: Nullable<Validator>): boolean {
756
+ result = this.handleCustomResult(result, validator);
757
+
758
+ // Fire invalid events
759
+ this.$input?.checkValidity();
760
+
761
+ this.updateValidClass(result);
762
+
763
+ return result;
764
+ }
765
+
766
+ raiseCustomErrorState(validator: Validator): void {
767
+ let help;
768
+
769
+ if (this.$input?.validationMessage === '') {
770
+ help = validator.options?.notice;
771
+
772
+ if (typeof help === 'function') {
773
+ help = help(this.$input, this);
774
+ }
775
+
776
+ if (help != null) {
777
+ this.$input?.setCustomValidity(help);
778
+ }
779
+ }
780
+
781
+ if (this.$input?.validationMessage === '') {
782
+ this.$input?.setCustomValidity(trans('unicorn.message.validation.custom.error'));
783
+ }
784
+
785
+ this.$input?.dispatchEvent(
786
+ new CustomEvent('invalid')
787
+ );
788
+ }
789
+
790
+ setAsInvalidAndReport(error: string) {
791
+ this.setCustomValidity(error);
792
+ this.showInvalidResponse();
793
+ }
794
+
795
+ setCustomValidity(error: string) {
796
+ this.$input?.setCustomValidity(error);
797
+ }
798
+
799
+ reportValidity() {
800
+ if (this.validationMessage !== '') {
801
+ this.showInvalidResponse();
802
+ }
803
+ }
804
+
805
+ showInvalidResponse() {
806
+ /** @type ValidityState */
807
+ const state = this.$input?.validity;
808
+ let message: string = this.$input?.validationMessage || '';
809
+
810
+ for (let key in state) {
811
+ if (state[(key as keyof ValidityState)] && this.$input?.dataset[key + 'Message']) {
812
+ message = this.$input?.dataset[key + 'Message'] || '';
813
+ break;
814
+ }
815
+ }
816
+
817
+ if (!this.isVisible) {
818
+ let title = this.findLabel()?.textContent;
819
+
820
+ if (!title) {
821
+ title = this.$input?.name || '';
822
+ }
823
+
824
+ useUITheme().renderMessage(
825
+ `Field: ${title} - ${message}`,
826
+ 'warning'
827
+ );
828
+ }
829
+
830
+ let $help = this.getErrorElement();
831
+
832
+ if (!$help) {
833
+ $help = this.createHelpElement()!;
834
+ this.el.appendChild($help);
835
+ this.prepareWrapper();
836
+ }
837
+
838
+ $help.textContent = message;
839
+
840
+ this.updateValidClass(false);
841
+ }
842
+
843
+ getErrorElement() {
844
+ return this.el.querySelector(this.errorSelector);
845
+ }
846
+
847
+ createHelpElement() {
848
+ const className = this.options.errorMessageClass;
849
+ const parsed = this.parseSelector(this.errorSelector || '');
850
+
851
+ const $help = html(`<div class="${className}"></div>`)!;
852
+
853
+ $help.classList.add(...parsed.classes);
854
+
855
+ parsed.attrs.forEach((attr) => {
856
+ $help.setAttribute(attr[0], attr[1] || '');
857
+ });
858
+
859
+ parsed.ids.forEach((id) => {
860
+ $help.id = id;
861
+ });
862
+
863
+ return $help;
864
+ }
865
+
866
+ /**
867
+ * @see https://stackoverflow.com/a/17888178
868
+ */
869
+ parseSelector(subselector: string): { tags: string[]; classes: string[]; ids: string[]; attrs: string[][] } {
870
+ const obj: {
871
+ tags: string[];
872
+ classes: string[];
873
+ ids: string[];
874
+ attrs: string[][];
875
+ } = { tags: [], classes: [], ids: [], attrs: [] };
876
+ for (const token of subselector.split(/(?=\.)|(?=#)|(?=\[)/)) {
877
+ switch (token[0]) {
878
+ case '#':
879
+ obj.ids.push(token.slice(1));
880
+ break;
881
+ case '.':
882
+ obj.classes.push(token.slice(1));
883
+ break;
884
+ case '[':
885
+ obj.attrs.push(token.slice(1, -1).split('='));
886
+ break;
887
+ default :
888
+ obj.tags.push(token);
889
+ break;
890
+ }
891
+ }
892
+ return obj;
893
+ }
894
+
895
+ setAsValidAndClearResponse() {
896
+ this.setCustomValidity('');
897
+ this.updateValidClass(true);
898
+ this.clearInvalidResponse();
899
+ }
900
+
901
+ clearInvalidResponse() {
902
+ const $help = this.el.querySelector(this.errorSelector)!;
903
+
904
+ $help.textContent = '';
905
+ }
906
+
907
+ getForm() {
908
+ return this.el.closest(this.options.formSelector || '[uni-form-validate]') as HTMLFormElement;
909
+ }
910
+
911
+ findLabel() {
912
+ const id = this.$input?.id || '';
913
+
914
+ const wrapper = this.$input?.closest('[data-field-wrapper]');
915
+ let label = null;
916
+
917
+ if (wrapper) {
918
+ label = wrapper.querySelector('[data-field-label]');
919
+ }
920
+
921
+ if (!label) {
922
+ label = document.querySelector(`label[for="${id}"]`);
923
+ }
924
+
925
+ return label;
926
+ }
927
+ }
928
+
929
+ function camelTo(str: string, sep: string) {
930
+ return str.replace(/([a-z])([A-Z])/g, `$1${sep}$2`).toLowerCase();
931
+ }
932
+
933
+ validatorHandlers.username = function (value: any, element: HTMLElement) {
934
+ const regex = new RegExp('[\<|\>|"|\'|\%|\;|\(|\)|\&]', 'i');
935
+ return !regex.test(value);
936
+ };
937
+
938
+ validatorHandlers.numeric = function (value: any, element: HTMLElement) {
939
+ const regex = /^(\d|-)?(\d|,)*\.?\d*$/;
940
+ return regex.test(value);
941
+ };
942
+
943
+ validatorHandlers.email = function (value: any, element: HTMLElement) {
944
+ value = punycode.toASCII(value);
945
+ const regex = /^[a-zA-Z0-9.!#$%&’*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
946
+ return regex.test(value);
947
+ };
948
+
949
+ validatorHandlers.url = function (value: any, element: HTMLElement) {
950
+ const regex = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]+-?)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/i;
951
+ return regex.test(value);
952
+ };
953
+
954
+ validatorHandlers.alnum = function (value: any, element: HTMLElement) {
955
+ const regex = /^[a-zA-Z0-9]*$/;
956
+ return regex.test(value);
957
+ };
958
+
959
+ validatorHandlers.color = function (value: any, element: HTMLElement) {
960
+ const regex = /^#(?:[0-9a-f]{3}){1,2}$/;
961
+ return regex.test(value);
962
+ };
963
+
964
+ /**
965
+ * @see http://www.virtuosimedia.com/dev/php/37-tested-php-perl-and-javascript-regular-expressions
966
+ */
967
+ validatorHandlers.creditcard = function (value: any, element: HTMLElement) {
968
+ const regex = /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6011[0-9]{12}|622((12[6-9]|1[3-9][0-9])|([2-8][0-9][0-9])|(9(([0-1][0-9])|(2[0-5]))))[0-9]{10}|64[4-9][0-9]{13}|65[0-9]{14}|3(?:0[0-5]|[68][0-9])[0-9]{11}|3[47][0-9]{13})*$/;
969
+ return regex.test(value);
970
+ };
971
+
972
+ validatorHandlers.ip = function (value: any, element: HTMLElement) {
973
+ const regex = /^((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))*$/;
974
+ return regex.test(value);
975
+ };
976
+
977
+ validatorHandlers['password-confirm'] = function (value: any, element: HTMLElement) {
978
+ const selector = element.dataset.confirmTarget;
979
+
980
+ if (!selector) {
981
+ throw new Error('Validator: "password-confirm" must add "data-confirm-target" attribute.');
982
+ }
983
+
984
+ const target = document.querySelector<HTMLInputElement>(selector);
985
+
986
+ return target?.value === value;
987
+ };
988
+
989
+ export { validatorHandlers as validators };
990
+
991
+ // customElements.define(UnicornFormValidateElement.is, UnicornFormValidateElement);
992
+ // customElements.define(UnicornFieldValidateElement.is, UnicornFieldValidateElement);
993
+
994
+ export const ready = Promise.all([
995
+ useUniDirective('form-validate', {
996
+ mounted(el, binding) {
997
+ getBoundedInstance(el, 'form.validation', (ele) => {
998
+ return new UnicornFormValidation(ele as HTMLElement, JSON.parse(binding.value || '{}'));
999
+ });
1000
+ },
1001
+ updated(el, binding) {
1002
+ const instance = getBoundedInstance<UnicornFormValidation>(el, 'form.validation');
1003
+ instance.mergeOptions(JSON.parse(binding.value || '{}'));
1004
+ }
1005
+ }),
1006
+
1007
+ useUniDirective('field-validate', {
1008
+ mounted(el, binding) {
1009
+ getBoundedInstance<UnicornFieldValidation>(el, 'field.validation', (ele) => {
1010
+ return new UnicornFieldValidation(ele as HTMLElement, JSON.parse(binding.value || '{}'));
1011
+ });
1012
+ },
1013
+
1014
+ updated(el, binding) {
1015
+ const instance = getBoundedInstance<UnicornFieldValidation>(el, 'field.validation');
1016
+ instance.mergeOptions(JSON.parse(binding.value || '{}') || {});
1017
+ }
1018
+ })
1019
+ ]);
1020
+
1021
+ function handleParamValue(value: any) {
1022
+ if (!isNaN(Number(value))) {
1023
+ return Number(value);
1024
+ }
1025
+
1026
+ if (value === 'null') {
1027
+ return null;
1028
+ }
1029
+
1030
+ if (value === 'true') {
1031
+ return true;
1032
+ }
1033
+
1034
+ if (value === 'false') {
1035
+ return true;
1036
+ }
1037
+
1038
+ return value;
1039
+ }
1040
+
1041
+ export interface ValidationModule {
1042
+ UnicornFormValidation: typeof UnicornFormValidation;
1043
+ UnicornFieldValidation: typeof UnicornFieldValidation;
1044
+ ready: Promise<any>;
1045
+ validators: typeof validatorHandlers;
1046
+ }