@windwalker-io/unicorn-next 0.1.18 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/_arrayPush.js +325 -108
- package/dist/chunks/_arrayPush.js.map +1 -1
- package/dist/chunks/_baseRest.js +155 -60
- package/dist/chunks/_baseRest.js.map +1 -1
- package/dist/chunks/_baseUnary.js +463 -0
- package/dist/chunks/_baseUnary.js.map +1 -0
- package/dist/chunks/_getPrototype.js +292 -100
- package/dist/chunks/_getPrototype.js.map +1 -1
- package/dist/chunks/alert-adapter.js +29 -0
- package/dist/chunks/alert-adapter.js.map +1 -0
- package/dist/chunks/alert.js +21 -0
- package/dist/chunks/alert.js.map +1 -0
- package/dist/chunks/arr.js +24 -0
- package/dist/chunks/arr.js.map +1 -0
- package/dist/chunks/button-radio.js +127 -145
- package/dist/chunks/button-radio.js.map +1 -1
- package/dist/chunks/checkboxes-multi-select.js +44 -43
- package/dist/chunks/checkboxes-multi-select.js.map +1 -1
- package/dist/chunks/chunk.js +24 -0
- package/dist/chunks/cloneDeep.js +679 -212
- package/dist/chunks/cloneDeep.js.map +1 -1
- package/dist/chunks/cropper.min.js +6 -5
- package/dist/chunks/cropper.min.js.map +1 -1
- package/dist/chunks/crypto.js +26 -0
- package/dist/chunks/crypto.js.map +1 -0
- package/dist/chunks/data.js +49 -0
- package/dist/chunks/data.js.map +1 -0
- package/dist/chunks/dom.js +128 -0
- package/dist/chunks/dom.js.map +1 -0
- package/dist/chunks/events.js +270 -0
- package/dist/chunks/events.js.map +1 -0
- package/dist/chunks/field-cascade-select.js +207 -250
- package/dist/chunks/field-cascade-select.js.map +1 -1
- package/dist/chunks/field-file-drag.js +175 -209
- package/dist/chunks/field-file-drag.js.map +1 -1
- package/dist/chunks/field-flatpickr.js +94 -898
- package/dist/chunks/field-flatpickr.js.map +1 -1
- package/dist/chunks/field-modal-select.js +728 -467
- package/dist/chunks/field-modal-select.js.map +1 -1
- package/dist/chunks/field-modal-tree.js +771 -766
- package/dist/chunks/field-modal-tree.js.map +1 -1
- package/dist/chunks/field-multi-uploader.js +249 -256
- package/dist/chunks/field-multi-uploader.js.map +1 -1
- package/dist/chunks/field-repeatable.js +111 -127
- package/dist/chunks/field-repeatable.js.map +1 -1
- package/dist/chunks/field-single-image-drag.js +286 -338
- package/dist/chunks/field-single-image-drag.js.map +1 -1
- package/dist/chunks/form.js +146 -159
- package/dist/chunks/form.js.map +1 -1
- package/dist/chunks/grid.js +349 -418
- package/dist/chunks/grid.js.map +1 -1
- package/dist/chunks/helper.js +39 -0
- package/dist/chunks/helper.js.map +1 -0
- package/dist/chunks/http-client.js +221 -211
- package/dist/chunks/http-client.js.map +1 -1
- package/dist/chunks/iframe-modal.js +95 -115
- package/dist/chunks/iframe-modal.js.map +1 -1
- package/dist/chunks/keep-tab.js +92 -101
- package/dist/chunks/keep-tab.js.map +1 -1
- package/dist/chunks/lang.js +250 -0
- package/dist/chunks/lang.js.map +1 -0
- package/dist/chunks/legacy.js +197 -201
- package/dist/chunks/legacy.js.map +1 -1
- package/dist/chunks/list-dependent.js +195 -228
- package/dist/chunks/list-dependent.js.map +1 -1
- package/dist/chunks/loader.js +106 -0
- package/dist/chunks/loader.js.map +1 -0
- package/dist/chunks/monthSelect.js +251 -0
- package/dist/chunks/monthSelect.js.map +1 -0
- package/dist/chunks/router.js +111 -0
- package/dist/chunks/router.js.map +1 -0
- package/dist/chunks/s3-multipart-uploader.js +183 -210
- package/dist/chunks/s3-multipart-uploader.js.map +1 -1
- package/dist/chunks/s3-uploader.js +106 -128
- package/dist/chunks/s3-uploader.js.map +1 -1
- package/dist/chunks/show-on.js +358 -205
- package/dist/chunks/show-on.js.map +1 -1
- package/dist/chunks/timing.js +10 -0
- package/dist/chunks/timing.js.map +1 -0
- package/dist/chunks/tinymce.js +153 -203
- package/dist/chunks/tinymce.js.map +1 -1
- package/dist/chunks/ui-bootstrap5.js +58 -72
- package/dist/chunks/ui-bootstrap5.js.map +1 -1
- package/dist/chunks/ui.js +320 -0
- package/dist/chunks/ui.js.map +1 -0
- package/dist/chunks/unicorn.js.map +1 -1
- package/dist/chunks/useQueue.js +111 -0
- package/dist/chunks/useQueue.js.map +1 -0
- package/dist/chunks/useStack.js +76 -0
- package/dist/chunks/useStack.js.map +1 -0
- package/dist/chunks/validation.js +761 -853
- package/dist/chunks/validation.js.map +1 -1
- package/dist/editor.css +1 -1
- package/dist/index.d.ts +27 -15
- package/dist/multi-level-menu.css +1 -1
- package/dist/switcher.css +1 -1
- package/dist/unicorn.js +805 -130
- package/dist/unicorn.js.map +1 -1
- package/package.json +3 -3
- package/src/composable/useBsModalAlert.ts +92 -12
- package/src/composable/useHttp.ts +13 -1
- package/src/module/s3-uploader.ts +1 -1
- package/src/service/ui.ts +31 -15
- package/vite.config.ts +5 -1
- package/dist/chunks/_commonjsHelpers.js +0 -7
- package/dist/chunks/index.js +0 -314
- package/dist/chunks/isArguments.js +0 -146
- package/dist/chunks/unicorn.js +0 -2580
|
@@ -1,888 +1,796 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import { t as mergeDeep } from "./arr.js";
|
|
2
|
+
import { d as selectOne, o as html, r as getBoundedInstance, u as selectAll } from "./dom.js";
|
|
3
|
+
import { x as useUITheme } from "./ui.js";
|
|
4
|
+
import { n as trans } from "./lang.js";
|
|
5
|
+
import { useUniDirective } from "../unicorn.js";
|
|
6
|
+
//#region ../../../../node_modules/punycode/punycode.es6.js
|
|
7
|
+
/** Highest positive signed 32-bit float value */
|
|
8
|
+
var maxInt = 2147483647;
|
|
9
|
+
/** Bootstring parameters */
|
|
10
|
+
var base = 36;
|
|
11
|
+
var tMin = 1;
|
|
12
|
+
var tMax = 26;
|
|
13
|
+
var skew = 38;
|
|
14
|
+
var damp = 700;
|
|
15
|
+
var initialBias = 72;
|
|
16
|
+
var initialN = 128;
|
|
17
|
+
var delimiter = "-";
|
|
18
|
+
var regexNonASCII = /[^\0-\x7F]/;
|
|
19
|
+
var regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g;
|
|
20
|
+
/** Error messages */
|
|
21
|
+
var errors = {
|
|
22
|
+
"overflow": "Overflow: input needs wider integers to process",
|
|
23
|
+
"not-basic": "Illegal input >= 0x80 (not a basic code point)",
|
|
24
|
+
"invalid-input": "Invalid input"
|
|
17
25
|
};
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
/** Convenience shortcuts */
|
|
27
|
+
var baseMinusTMin = base - tMin;
|
|
28
|
+
var floor = Math.floor;
|
|
29
|
+
var stringFromCharCode = String.fromCharCode;
|
|
30
|
+
/**
|
|
31
|
+
* A generic error utility function.
|
|
32
|
+
* @private
|
|
33
|
+
* @param {String} type The error type.
|
|
34
|
+
* @returns {Error} Throws a `RangeError` with the applicable error message.
|
|
35
|
+
*/
|
|
21
36
|
function error(type) {
|
|
22
|
-
|
|
37
|
+
throw new RangeError(errors[type]);
|
|
23
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A generic `Array#map` utility function.
|
|
41
|
+
* @private
|
|
42
|
+
* @param {Array} array The array to iterate over.
|
|
43
|
+
* @param {Function} callback The function that gets called for every array
|
|
44
|
+
* item.
|
|
45
|
+
* @returns {Array} A new array of values returned by the callback function.
|
|
46
|
+
*/
|
|
24
47
|
function map(array, callback) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
return result;
|
|
48
|
+
const result = [];
|
|
49
|
+
let length = array.length;
|
|
50
|
+
while (length--) result[length] = callback(array[length]);
|
|
51
|
+
return result;
|
|
31
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* A simple `Array#map`-like wrapper to work with domain name strings or email
|
|
55
|
+
* addresses.
|
|
56
|
+
* @private
|
|
57
|
+
* @param {String} domain The domain name or email address.
|
|
58
|
+
* @param {Function} callback The function that gets called for every
|
|
59
|
+
* character.
|
|
60
|
+
* @returns {String} A new string of characters returned by the callback
|
|
61
|
+
* function.
|
|
62
|
+
*/
|
|
32
63
|
function mapDomain(domain, callback) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return result + encoded;
|
|
64
|
+
const parts = domain.split("@");
|
|
65
|
+
let result = "";
|
|
66
|
+
if (parts.length > 1) {
|
|
67
|
+
result = parts[0] + "@";
|
|
68
|
+
domain = parts[1];
|
|
69
|
+
}
|
|
70
|
+
domain = domain.replace(regexSeparators, ".");
|
|
71
|
+
const encoded = map(domain.split("."), callback).join(".");
|
|
72
|
+
return result + encoded;
|
|
43
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Creates an array containing the numeric code points of each Unicode
|
|
76
|
+
* character in the string. While JavaScript uses UCS-2 internally,
|
|
77
|
+
* this function will convert a pair of surrogate halves (each of which
|
|
78
|
+
* UCS-2 exposes as separate characters) into a single code point,
|
|
79
|
+
* matching UTF-16.
|
|
80
|
+
* @see `punycode.ucs2.encode`
|
|
81
|
+
* @see <https://mathiasbynens.be/notes/javascript-encoding>
|
|
82
|
+
* @memberOf punycode.ucs2
|
|
83
|
+
* @name decode
|
|
84
|
+
* @param {String} string The Unicode input string (UCS-2).
|
|
85
|
+
* @returns {Array} The new array of code points.
|
|
86
|
+
*/
|
|
44
87
|
function ucs2decode(string) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
return output;
|
|
88
|
+
const output = [];
|
|
89
|
+
let counter = 0;
|
|
90
|
+
const length = string.length;
|
|
91
|
+
while (counter < length) {
|
|
92
|
+
const value = string.charCodeAt(counter++);
|
|
93
|
+
if (value >= 55296 && value <= 56319 && counter < length) {
|
|
94
|
+
const extra = string.charCodeAt(counter++);
|
|
95
|
+
if ((extra & 64512) == 56320) output.push(((value & 1023) << 10) + (extra & 1023) + 65536);
|
|
96
|
+
else {
|
|
97
|
+
output.push(value);
|
|
98
|
+
counter--;
|
|
99
|
+
}
|
|
100
|
+
} else output.push(value);
|
|
101
|
+
}
|
|
102
|
+
return output;
|
|
63
103
|
}
|
|
64
|
-
|
|
65
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Converts a digit/integer into a basic code point.
|
|
106
|
+
* @see `basicToDigit()`
|
|
107
|
+
* @private
|
|
108
|
+
* @param {Number} digit The numeric value of a basic code point.
|
|
109
|
+
* @returns {Number} The basic code point whose value (when used for
|
|
110
|
+
* representing integers) is `digit`, which needs to be in the range
|
|
111
|
+
* `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
|
|
112
|
+
* used; else, the lowercase form is used. The behavior is undefined
|
|
113
|
+
* if `flag` is non-zero and `digit` has no uppercase form.
|
|
114
|
+
*/
|
|
115
|
+
var digitToBasic = function(digit, flag) {
|
|
116
|
+
return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
|
|
66
117
|
};
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Bias adaptation function as per section 3.4 of RFC 3492.
|
|
120
|
+
* https://tools.ietf.org/html/rfc3492#section-3.4
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
123
|
+
var adapt = function(delta, numPoints, firstTime) {
|
|
124
|
+
let k = 0;
|
|
125
|
+
delta = firstTime ? floor(delta / damp) : delta >> 1;
|
|
126
|
+
delta += floor(delta / numPoints);
|
|
127
|
+
for (; delta > baseMinusTMin * tMax >> 1; k += base) delta = floor(delta / baseMinusTMin);
|
|
128
|
+
return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
|
|
75
129
|
};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
output.push(stringFromCharCode(digitToBasic(q, 0)));
|
|
125
|
-
bias = adapt(delta, handledCPCountPlusOne, handledCPCount === basicLength);
|
|
126
|
-
delta = 0;
|
|
127
|
-
++handledCPCount;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
++delta;
|
|
131
|
-
++n;
|
|
132
|
-
}
|
|
133
|
-
return output.join("");
|
|
130
|
+
/**
|
|
131
|
+
* Converts a string of Unicode symbols (e.g. a domain name label) to a
|
|
132
|
+
* Punycode string of ASCII-only symbols.
|
|
133
|
+
* @memberOf punycode
|
|
134
|
+
* @param {String} input The string of Unicode symbols.
|
|
135
|
+
* @returns {String} The resulting Punycode string of ASCII-only symbols.
|
|
136
|
+
*/
|
|
137
|
+
var encode = function(input) {
|
|
138
|
+
const output = [];
|
|
139
|
+
input = ucs2decode(input);
|
|
140
|
+
const inputLength = input.length;
|
|
141
|
+
let n = initialN;
|
|
142
|
+
let delta = 0;
|
|
143
|
+
let bias = initialBias;
|
|
144
|
+
for (const currentValue of input) if (currentValue < 128) output.push(stringFromCharCode(currentValue));
|
|
145
|
+
const basicLength = output.length;
|
|
146
|
+
let handledCPCount = basicLength;
|
|
147
|
+
if (basicLength) output.push(delimiter);
|
|
148
|
+
while (handledCPCount < inputLength) {
|
|
149
|
+
let m = maxInt;
|
|
150
|
+
for (const currentValue of input) if (currentValue >= n && currentValue < m) m = currentValue;
|
|
151
|
+
const handledCPCountPlusOne = handledCPCount + 1;
|
|
152
|
+
if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) error("overflow");
|
|
153
|
+
delta += (m - n) * handledCPCountPlusOne;
|
|
154
|
+
n = m;
|
|
155
|
+
for (const currentValue of input) {
|
|
156
|
+
if (currentValue < n && ++delta > maxInt) error("overflow");
|
|
157
|
+
if (currentValue === n) {
|
|
158
|
+
let q = delta;
|
|
159
|
+
for (let k = base;; k += base) {
|
|
160
|
+
const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;
|
|
161
|
+
if (q < t) break;
|
|
162
|
+
const qMinusT = q - t;
|
|
163
|
+
const baseMinusT = base - t;
|
|
164
|
+
output.push(stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)));
|
|
165
|
+
q = floor(qMinusT / baseMinusT);
|
|
166
|
+
}
|
|
167
|
+
output.push(stringFromCharCode(digitToBasic(q, 0)));
|
|
168
|
+
bias = adapt(delta, handledCPCountPlusOne, handledCPCount === basicLength);
|
|
169
|
+
delta = 0;
|
|
170
|
+
++handledCPCount;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
++delta;
|
|
174
|
+
++n;
|
|
175
|
+
}
|
|
176
|
+
return output.join("");
|
|
134
177
|
};
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Converts a Unicode string representing a domain name or an email address to
|
|
180
|
+
* Punycode. Only the non-ASCII parts of the domain name will be converted,
|
|
181
|
+
* i.e. it doesn't matter if you call it with a domain that's already in
|
|
182
|
+
* ASCII.
|
|
183
|
+
* @memberOf punycode
|
|
184
|
+
* @param {String} input The domain name or email address to convert, as a
|
|
185
|
+
* Unicode string.
|
|
186
|
+
* @returns {String} The Punycode representation of the given domain name or
|
|
187
|
+
* email address.
|
|
188
|
+
*/
|
|
189
|
+
var toASCII = function(input) {
|
|
190
|
+
return mapDomain(input, function(string) {
|
|
191
|
+
return regexNonASCII.test(string) ? "xn--" + encode(string) : string;
|
|
192
|
+
});
|
|
139
193
|
};
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/module/validation.ts
|
|
196
|
+
var validatorHandlers = {};
|
|
197
|
+
var defaultOptions = {
|
|
198
|
+
scroll: false,
|
|
199
|
+
scrollOffset: -100,
|
|
200
|
+
enabled: true,
|
|
201
|
+
fieldSelector: null,
|
|
202
|
+
validatedClass: null
|
|
147
203
|
};
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
204
|
+
var defaultFieldOptions = {
|
|
205
|
+
formSelector: "[uni-form-validate]",
|
|
206
|
+
errorSelector: "[data-field-error]",
|
|
207
|
+
selector: "input[data-field-input], select[data-field-input], textarea[data-field-input]",
|
|
208
|
+
validClass: "is-valid",
|
|
209
|
+
invalidClass: "is-invalid",
|
|
210
|
+
events: ["change"],
|
|
211
|
+
errorMessageClass: "invalid-tooltip",
|
|
212
|
+
inputOptions: false,
|
|
213
|
+
inputOptionsWrapperSelector: "div[data-field-input]",
|
|
214
|
+
inputOptionsSelector: "[data-input-option]"
|
|
215
|
+
};
|
|
216
|
+
var UnicornFormValidation = class {
|
|
217
|
+
presetFields = [];
|
|
218
|
+
static globalValidators = {};
|
|
219
|
+
validators = {};
|
|
220
|
+
options;
|
|
221
|
+
$form;
|
|
222
|
+
static is = "uni-form-validate";
|
|
223
|
+
constructor(el, options = {}) {
|
|
224
|
+
this.$form = selectOne(el);
|
|
225
|
+
this.options = this.mergeOptions(options);
|
|
226
|
+
this.registerDefaultValidators();
|
|
227
|
+
this.init();
|
|
228
|
+
}
|
|
229
|
+
mergeOptions(options) {
|
|
230
|
+
if (Array.isArray(options)) options = {};
|
|
231
|
+
return this.options = mergeDeep({}, defaultOptions, this.options || {}, options);
|
|
232
|
+
}
|
|
233
|
+
get scrollEnabled() {
|
|
234
|
+
return this.options.scroll;
|
|
235
|
+
}
|
|
236
|
+
get scrollOffset() {
|
|
237
|
+
return Number(this.options.scrollOffset || -100);
|
|
238
|
+
}
|
|
239
|
+
get fieldSelector() {
|
|
240
|
+
return this.options.fieldSelector || "input, select, textarea";
|
|
241
|
+
}
|
|
242
|
+
get validatedClass() {
|
|
243
|
+
return this.options.validatedClass || "was-validated";
|
|
244
|
+
}
|
|
245
|
+
init() {
|
|
246
|
+
if (this.$form.tagName === "FORM") {
|
|
247
|
+
this.$form.setAttribute("novalidate", "true");
|
|
248
|
+
this.$form.addEventListener("submit", (event) => {
|
|
249
|
+
if (this.options.enabled && !this.validateAll()) {
|
|
250
|
+
event.stopImmediatePropagation();
|
|
251
|
+
event.stopPropagation();
|
|
252
|
+
event.preventDefault();
|
|
253
|
+
this.$form.dispatchEvent(new CustomEvent("invalid"));
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
return true;
|
|
257
|
+
}, false);
|
|
258
|
+
}
|
|
259
|
+
this.prepareFields(this.findDOMFields());
|
|
260
|
+
this.prepareFields(this.presetFields);
|
|
261
|
+
}
|
|
262
|
+
findDOMFields() {
|
|
263
|
+
return selectAll(this.$form.querySelectorAll(this.fieldSelector));
|
|
264
|
+
}
|
|
265
|
+
prepareFields(inputs) {
|
|
266
|
+
inputs.forEach((input) => {
|
|
267
|
+
this.prepareFieldWrapper(input);
|
|
268
|
+
});
|
|
269
|
+
return Promise.resolve();
|
|
270
|
+
}
|
|
271
|
+
prepareFieldWrapper(input) {
|
|
272
|
+
if ([
|
|
273
|
+
"INPUT",
|
|
274
|
+
"SELECT",
|
|
275
|
+
"TEXTAREA"
|
|
276
|
+
].indexOf(input.tagName) !== -1) {
|
|
277
|
+
let wrapper = input.closest("[uni-field-validate]");
|
|
278
|
+
if (!wrapper) {
|
|
279
|
+
wrapper = input.closest("[data-input-container]") || input.parentElement;
|
|
280
|
+
wrapper?.setAttribute("uni-field-validate", "{}");
|
|
281
|
+
}
|
|
282
|
+
return wrapper;
|
|
283
|
+
}
|
|
284
|
+
return input;
|
|
285
|
+
}
|
|
286
|
+
findFields(containsPresets = true) {
|
|
287
|
+
let inputs = this.findDOMFields();
|
|
288
|
+
if (containsPresets) inputs.push(...this.presetFields);
|
|
289
|
+
return inputs.map((input) => this.prepareFieldWrapper(input)).filter((input) => input != null);
|
|
290
|
+
}
|
|
291
|
+
getFieldComponents(containsPresets = true) {
|
|
292
|
+
const components = [];
|
|
293
|
+
for (const field of this.findFields(containsPresets)) {
|
|
294
|
+
const v = this.getFieldComponent(field);
|
|
295
|
+
if (v) components.push(v);
|
|
296
|
+
}
|
|
297
|
+
return components;
|
|
298
|
+
}
|
|
299
|
+
getFieldComponent(input) {
|
|
300
|
+
let v = getBoundedInstance(input, "field.validation");
|
|
301
|
+
if (!v) {
|
|
302
|
+
const wrapper = input.closest("[uni-field-validate]");
|
|
303
|
+
if (wrapper) v = getBoundedInstance(wrapper, "field.validation");
|
|
304
|
+
}
|
|
305
|
+
return v;
|
|
306
|
+
}
|
|
307
|
+
validateAll(fields) {
|
|
308
|
+
this.markFormAsUnvalidated();
|
|
309
|
+
fields = fields || this.findFields();
|
|
310
|
+
let firstFail = null;
|
|
311
|
+
for (const field of fields) {
|
|
312
|
+
const fv = this.getFieldComponent(field);
|
|
313
|
+
if (!fv) continue;
|
|
314
|
+
if (!fv.checkValidity() && !firstFail) firstFail = field;
|
|
315
|
+
}
|
|
316
|
+
this.markFormAsValidated();
|
|
317
|
+
if (firstFail && this.scrollEnabled) this.scrollTo(firstFail);
|
|
318
|
+
return firstFail === null;
|
|
319
|
+
}
|
|
320
|
+
async validateAllAsync(fields) {
|
|
321
|
+
this.markFormAsUnvalidated();
|
|
322
|
+
fields = fields || this.findFields();
|
|
323
|
+
let firstFail = null;
|
|
324
|
+
const promises = [];
|
|
325
|
+
for (const field of fields) {
|
|
326
|
+
const fv = this.getFieldComponent(field);
|
|
327
|
+
if (!fv) continue;
|
|
328
|
+
promises.push(fv.checkValidityAsync().then((result) => {
|
|
329
|
+
if (!result && !firstFail) firstFail = field;
|
|
330
|
+
return result;
|
|
331
|
+
}));
|
|
332
|
+
}
|
|
333
|
+
await Promise.all(promises);
|
|
334
|
+
this.markFormAsValidated();
|
|
335
|
+
if (firstFail && this.scrollEnabled) this.scrollTo(firstFail);
|
|
336
|
+
return firstFail === null;
|
|
337
|
+
}
|
|
338
|
+
scrollTo(element) {
|
|
339
|
+
const offset = this.scrollOffset;
|
|
340
|
+
const offsetPosition = element.getBoundingClientRect().top + window.scrollY + offset;
|
|
341
|
+
window.scrollTo({
|
|
342
|
+
top: offsetPosition,
|
|
343
|
+
behavior: "smooth"
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
markFormAsValidated() {
|
|
347
|
+
if (!this.$form) return;
|
|
348
|
+
this.$form.classList.add(this.validatedClass);
|
|
349
|
+
}
|
|
350
|
+
markFormAsUnvalidated() {
|
|
351
|
+
if (!this.$form) return;
|
|
352
|
+
this.$form.classList.remove(this.validatedClass);
|
|
353
|
+
}
|
|
354
|
+
addField(field) {
|
|
355
|
+
this.presetFields.push(field);
|
|
356
|
+
this.prepareFieldWrapper(field);
|
|
357
|
+
return this;
|
|
358
|
+
}
|
|
359
|
+
registerDefaultValidators() {
|
|
360
|
+
for (let name in validatorHandlers) this.addValidator(name, validatorHandlers[name]);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Add validator handler.
|
|
364
|
+
*/
|
|
365
|
+
addValidator(name, handler, options = {}) {
|
|
366
|
+
options = options || {};
|
|
367
|
+
this.validators[name] = {
|
|
368
|
+
handler,
|
|
369
|
+
options
|
|
370
|
+
};
|
|
371
|
+
return this;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Add validator handler.
|
|
375
|
+
*/
|
|
376
|
+
static addGlobalValidator(name, handler, options = {}) {
|
|
377
|
+
options = options || {};
|
|
378
|
+
this.globalValidators[name] = {
|
|
379
|
+
handler,
|
|
380
|
+
options
|
|
381
|
+
};
|
|
382
|
+
return this;
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
var UnicornFieldValidation = class {
|
|
386
|
+
$input;
|
|
387
|
+
options = {};
|
|
388
|
+
static is = "uni-field-validate";
|
|
389
|
+
constructor(el, options = {}) {
|
|
390
|
+
this.el = el;
|
|
391
|
+
this.setOptions(options);
|
|
392
|
+
this.$input = this.selectInput();
|
|
393
|
+
this.init();
|
|
394
|
+
}
|
|
395
|
+
setOptions(options) {
|
|
396
|
+
if (Array.isArray(options)) options = {};
|
|
397
|
+
this.options = options;
|
|
398
|
+
return this;
|
|
399
|
+
}
|
|
400
|
+
get mergedOptions() {
|
|
401
|
+
return mergeDeep({}, defaultFieldOptions, this.globalOptions, this.options);
|
|
402
|
+
}
|
|
403
|
+
get $form() {
|
|
404
|
+
return this.getForm();
|
|
405
|
+
}
|
|
406
|
+
get errorSelector() {
|
|
407
|
+
return this.mergedOptions.errorSelector;
|
|
408
|
+
}
|
|
409
|
+
get selector() {
|
|
410
|
+
return this.mergedOptions.selector;
|
|
411
|
+
}
|
|
412
|
+
get validClass() {
|
|
413
|
+
return this.mergedOptions.validClass;
|
|
414
|
+
}
|
|
415
|
+
get invalidClass() {
|
|
416
|
+
return this.mergedOptions.invalidClass;
|
|
417
|
+
}
|
|
418
|
+
get isVisible() {
|
|
419
|
+
return !!(this.el.offsetWidth || this.el.offsetHeight || this.el.getClientRects().length);
|
|
420
|
+
}
|
|
421
|
+
get isInputOptions() {
|
|
422
|
+
return Boolean(this.mergedOptions.inputOptions);
|
|
423
|
+
}
|
|
424
|
+
get validationMessage() {
|
|
425
|
+
return this.$input?.validationMessage || "";
|
|
426
|
+
}
|
|
427
|
+
get validity() {
|
|
428
|
+
return this.$input?.validity;
|
|
429
|
+
}
|
|
430
|
+
selectInput() {
|
|
431
|
+
let selector = this.selector;
|
|
432
|
+
if (this.mergedOptions.inputOptions) selector += ", " + this.mergedOptions.inputOptionsWrapperSelector;
|
|
433
|
+
let input = this.el.querySelector(selector);
|
|
434
|
+
if (!input) input = this.el.querySelector("input, select, textarea");
|
|
435
|
+
if (!input) return;
|
|
436
|
+
return this.$input = input;
|
|
437
|
+
}
|
|
438
|
+
init() {
|
|
439
|
+
this.selectInput();
|
|
440
|
+
this.bindEvents();
|
|
441
|
+
this.prepareWrapper();
|
|
442
|
+
if (this.isInputOptions) {
|
|
443
|
+
const $input = this.$input;
|
|
444
|
+
if (!($input instanceof HTMLInputElement) && !($input instanceof HTMLSelectElement) && !($input instanceof HTMLTextAreaElement)) {
|
|
445
|
+
$input.validationMessage = "";
|
|
446
|
+
$input.setCustomValidity = (msg) => {
|
|
447
|
+
$input.validationMessage = String(msg);
|
|
448
|
+
};
|
|
449
|
+
$input.checkValidity = () => {
|
|
450
|
+
return this.checkInputOptionsValidity();
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
bindEvents() {
|
|
456
|
+
if (!this.$input) return;
|
|
457
|
+
this.$input.addEventListener("invalid", (e) => {
|
|
458
|
+
this.showInvalidResponse();
|
|
459
|
+
});
|
|
460
|
+
this.mergedOptions.events.forEach((eventName) => {
|
|
461
|
+
this.$input?.addEventListener(eventName, () => {
|
|
462
|
+
this.checkValidity();
|
|
463
|
+
});
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
prepareWrapper() {
|
|
467
|
+
if (this.el.querySelector(this.errorSelector)?.classList?.contains("invalid-tooltip")) {
|
|
468
|
+
if (window.getComputedStyle(this.el).position === "static") this.el.style.position = "relative";
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
checkValidity() {
|
|
472
|
+
if (!this.$input) return true;
|
|
473
|
+
if (this.$input.hasAttribute("readonly")) return true;
|
|
474
|
+
if (this.$input.hasAttribute("[data-novalidate]")) return true;
|
|
475
|
+
if (this.$input.closest("[data-novalidate]")) return true;
|
|
476
|
+
if (this.hasChildDirectives()) return true;
|
|
477
|
+
this.$input.setCustomValidity("");
|
|
478
|
+
let valid = this.$input.checkValidity();
|
|
479
|
+
if (valid && this.$form) valid = this.runCustomValidity();
|
|
480
|
+
this.updateValidClass(valid);
|
|
481
|
+
return valid;
|
|
482
|
+
}
|
|
483
|
+
runCustomValidity() {
|
|
484
|
+
if (!this.$input) return true;
|
|
485
|
+
const validates = (this.$input.getAttribute("data-validate") || "").split("|");
|
|
486
|
+
let result = true;
|
|
487
|
+
if (this.$input.value !== "" && validates.length) {
|
|
488
|
+
if (!this.checkCustomDataAttributeValidity()) return false;
|
|
489
|
+
for (const validatorName of validates) {
|
|
490
|
+
const [validator, options] = this.getValidator(validatorName) || [null, {}];
|
|
491
|
+
if (!validator) continue;
|
|
492
|
+
Object.assign(options, validator.options);
|
|
493
|
+
let r = validator.handler(this.$input.value, this.$input, options, this);
|
|
494
|
+
if (r instanceof Promise || typeof r === "object" && r.then) {
|
|
495
|
+
r.then((result) => {
|
|
496
|
+
this.handleAsyncCustomResult(result, validator);
|
|
497
|
+
});
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
if (!this.handleCustomResult(r, validator)) {
|
|
501
|
+
result = false;
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return result;
|
|
507
|
+
}
|
|
508
|
+
async checkValidityAsync() {
|
|
509
|
+
if (!this.$input) return true;
|
|
510
|
+
if (this.$input.hasAttribute("readonly")) return true;
|
|
511
|
+
if (this.$input.hasAttribute("[data-novalidate]")) return true;
|
|
512
|
+
if (this.hasChildDirectives()) return true;
|
|
513
|
+
this.$input.setCustomValidity("");
|
|
514
|
+
let valid = this.$input.checkValidity();
|
|
515
|
+
if (valid && this.$form) valid = await this.runCustomValidityAsync();
|
|
516
|
+
this.updateValidClass(valid);
|
|
517
|
+
return valid;
|
|
518
|
+
}
|
|
519
|
+
async runCustomValidityAsync() {
|
|
520
|
+
if (!this.$input) return true;
|
|
521
|
+
const validates = (this.$input.getAttribute("data-validate") || "").split("|");
|
|
522
|
+
const results = [];
|
|
523
|
+
const promises = [];
|
|
524
|
+
if (this.$input.value !== "" && validates.length) {
|
|
525
|
+
if (!this.checkCustomDataAttributeValidity()) return false;
|
|
526
|
+
for (const validatorName of validates) {
|
|
527
|
+
let [validator, options] = this.getValidator(validatorName) || [null, {}];
|
|
528
|
+
if (!validator) continue;
|
|
529
|
+
options = Object.assign({}, options, validator.options || {});
|
|
530
|
+
promises.push(Promise.resolve(validator.handler(this.$input.value, this.$input, options, this)).then((r) => {
|
|
531
|
+
results.push(this.handleAsyncCustomResult(r, validator));
|
|
532
|
+
return r;
|
|
533
|
+
}));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
await Promise.all(promises);
|
|
537
|
+
for (const result of results) if (result === false) return false;
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
checkCustomDataAttributeValidity() {
|
|
541
|
+
const error = this.$input?.dataset.validationFail;
|
|
542
|
+
return this.handleCustomResult(error);
|
|
543
|
+
}
|
|
544
|
+
checkInputOptionsValidity() {
|
|
545
|
+
if (!this.$input) return true;
|
|
546
|
+
const isRequired = this.$input.getAttribute("required") != null;
|
|
547
|
+
const optionWrappers = this.$input.querySelectorAll(this.mergedOptions.inputOptionsSelector);
|
|
548
|
+
let result = true;
|
|
549
|
+
if (isRequired) for (const optionWrapper of optionWrappers) {
|
|
550
|
+
const input = optionWrapper.querySelector("input");
|
|
551
|
+
result = false;
|
|
552
|
+
if (input?.checked) {
|
|
553
|
+
result = true;
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
const n = document.createElement("input");
|
|
558
|
+
n.required = isRequired;
|
|
559
|
+
if (result) n.value = "placeholder";
|
|
560
|
+
n.checkValidity();
|
|
561
|
+
this.$input.validationMessage = n.validationMessage;
|
|
562
|
+
this.$input.validity = n.validity;
|
|
563
|
+
for (const optionWrapper of optionWrappers) optionWrapper.querySelector("input")?.setCustomValidity(n.validationMessage);
|
|
564
|
+
if (!result) this.$input.dispatchEvent(new CustomEvent("invalid"));
|
|
565
|
+
return result;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* @param valid {boolean}
|
|
569
|
+
*/
|
|
570
|
+
updateValidClass(valid) {
|
|
571
|
+
const $invalidTarget = this.getErrorElement()?.previousElementSibling;
|
|
572
|
+
this.$input?.classList.remove(this.invalidClass);
|
|
573
|
+
this.$input?.classList.remove(this.validClass);
|
|
574
|
+
this.el.classList.remove(this.invalidClass);
|
|
575
|
+
this.el.classList.remove(this.validClass);
|
|
576
|
+
$invalidTarget?.classList.remove(this.invalidClass);
|
|
577
|
+
$invalidTarget?.classList.remove(this.validClass);
|
|
578
|
+
if (valid) {
|
|
579
|
+
this.$input?.classList.add(this.validClass);
|
|
580
|
+
this.el.classList.add(this.validClass);
|
|
581
|
+
$invalidTarget?.classList.add(this.validClass);
|
|
582
|
+
} else {
|
|
583
|
+
this.$input?.classList.add(this.invalidClass);
|
|
584
|
+
this.el.classList.add(this.invalidClass);
|
|
585
|
+
$invalidTarget?.classList.add(this.invalidClass);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
getFormValidation(element) {
|
|
589
|
+
return getBoundedInstance(element || this.getForm(), "form.validation");
|
|
590
|
+
}
|
|
591
|
+
get globalOptions() {
|
|
592
|
+
return this.getFormValidation()?.options?.fieldDefaults ?? {};
|
|
593
|
+
}
|
|
594
|
+
getValidator(name) {
|
|
595
|
+
const matches = name.match(/(?<type>[\w\-_]+)(\((?<params>.*)\))*/);
|
|
596
|
+
if (!matches) return null;
|
|
597
|
+
const validatorName = matches.groups?.type || "";
|
|
598
|
+
const params = matches.groups?.params || "";
|
|
599
|
+
const validator = this.getFormValidation(this.$form)?.validators[validatorName] || UnicornFormValidation.globalValidators[validatorName];
|
|
600
|
+
if (!validator) return null;
|
|
601
|
+
const paramMatches = params.matchAll(/(?<key>\w+)(\s?[=:]\s?(?<value>\w+))?/g);
|
|
602
|
+
const options = {};
|
|
603
|
+
for (const paramMatch of paramMatches) {
|
|
604
|
+
const match = paramMatch?.groups;
|
|
605
|
+
if (!match) continue;
|
|
606
|
+
options[match.key] = handleParamValue(match.value);
|
|
607
|
+
}
|
|
608
|
+
return [validator, options];
|
|
609
|
+
}
|
|
610
|
+
handleCustomResult(result, validator) {
|
|
611
|
+
if (typeof result === "string") {
|
|
612
|
+
this.$input?.setCustomValidity(result);
|
|
613
|
+
result = result === "";
|
|
614
|
+
} else if (result === void 0) result = true;
|
|
615
|
+
if (result) this.$input?.setCustomValidity("");
|
|
616
|
+
else if (validator) this.raiseCustomErrorState(validator);
|
|
617
|
+
return result;
|
|
618
|
+
}
|
|
619
|
+
handleAsyncCustomResult(result, validator) {
|
|
620
|
+
result = this.handleCustomResult(result, validator);
|
|
621
|
+
this.$input?.checkValidity();
|
|
622
|
+
this.updateValidClass(result);
|
|
623
|
+
return result;
|
|
624
|
+
}
|
|
625
|
+
raiseCustomErrorState(validator) {
|
|
626
|
+
let help;
|
|
627
|
+
if (this.$input?.validationMessage === "") {
|
|
628
|
+
help = validator.options?.notice;
|
|
629
|
+
if (typeof help === "function") help = help(this.$input, this);
|
|
630
|
+
if (help != null) this.$input?.setCustomValidity(help);
|
|
631
|
+
}
|
|
632
|
+
if (this.$input?.validationMessage === "") this.$input?.setCustomValidity(trans("unicorn.message.validation.custom.error"));
|
|
633
|
+
this.$input?.dispatchEvent(new CustomEvent("invalid"));
|
|
634
|
+
}
|
|
635
|
+
setAsInvalidAndReport(error) {
|
|
636
|
+
this.setCustomValidity(error);
|
|
637
|
+
this.showInvalidResponse();
|
|
638
|
+
}
|
|
639
|
+
setCustomValidity(error) {
|
|
640
|
+
this.$input?.setCustomValidity(error);
|
|
641
|
+
}
|
|
642
|
+
reportValidity() {
|
|
643
|
+
if (this.validationMessage !== "") this.showInvalidResponse();
|
|
644
|
+
}
|
|
645
|
+
showInvalidResponse() {
|
|
646
|
+
if (this.hasChildDirectives()) return;
|
|
647
|
+
/** @type ValidityState */
|
|
648
|
+
const state = this.$input?.validity;
|
|
649
|
+
let message = this.$input?.validationMessage || "";
|
|
650
|
+
for (let key in state) if (state[key] && this.$input?.dataset[key + "Message"]) {
|
|
651
|
+
message = this.$input?.dataset[key + "Message"] || "";
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
if (!this.isVisible) {
|
|
655
|
+
let title = this.findLabel()?.textContent;
|
|
656
|
+
if (!title) title = this.$input?.name || "";
|
|
657
|
+
useUITheme().renderMessage(`Field: ${title} - ${message}`, "warning");
|
|
658
|
+
}
|
|
659
|
+
let $help = this.getErrorElement();
|
|
660
|
+
if (!$help) {
|
|
661
|
+
$help = this.createHelpElement();
|
|
662
|
+
this.el.appendChild($help);
|
|
663
|
+
this.prepareWrapper();
|
|
664
|
+
}
|
|
665
|
+
$help.textContent = message;
|
|
666
|
+
this.updateValidClass(false);
|
|
667
|
+
}
|
|
668
|
+
getErrorElement() {
|
|
669
|
+
return this.el.querySelector(this.errorSelector);
|
|
670
|
+
}
|
|
671
|
+
createHelpElement() {
|
|
672
|
+
const className = this.mergedOptions.errorMessageClass;
|
|
673
|
+
const parsed = this.parseSelector(this.errorSelector || "");
|
|
674
|
+
const $help = html(`<div class="${className}"></div>`);
|
|
675
|
+
$help.classList.add(...parsed.classes);
|
|
676
|
+
parsed.attrs.forEach((attr) => {
|
|
677
|
+
$help.setAttribute(attr[0], attr[1] || "");
|
|
678
|
+
});
|
|
679
|
+
parsed.ids.forEach((id) => {
|
|
680
|
+
$help.id = id;
|
|
681
|
+
});
|
|
682
|
+
return $help;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* @see https://stackoverflow.com/a/17888178
|
|
686
|
+
*/
|
|
687
|
+
parseSelector(subselector) {
|
|
688
|
+
const obj = {
|
|
689
|
+
tags: [],
|
|
690
|
+
classes: [],
|
|
691
|
+
ids: [],
|
|
692
|
+
attrs: []
|
|
693
|
+
};
|
|
694
|
+
for (const token of subselector.split(/(?=\.)|(?=#)|(?=\[)/)) switch (token[0]) {
|
|
695
|
+
case "#":
|
|
696
|
+
obj.ids.push(token.slice(1));
|
|
697
|
+
break;
|
|
698
|
+
case ".":
|
|
699
|
+
obj.classes.push(token.slice(1));
|
|
700
|
+
break;
|
|
701
|
+
case "[":
|
|
702
|
+
obj.attrs.push(token.slice(1, -1).split("="));
|
|
703
|
+
break;
|
|
704
|
+
default:
|
|
705
|
+
obj.tags.push(token);
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
return obj;
|
|
709
|
+
}
|
|
710
|
+
setAsValidAndClearResponse() {
|
|
711
|
+
this.setCustomValidity("");
|
|
712
|
+
this.updateValidClass(true);
|
|
713
|
+
this.clearInvalidResponse();
|
|
714
|
+
}
|
|
715
|
+
clearInvalidResponse() {
|
|
716
|
+
const $help = this.el.querySelector(this.errorSelector);
|
|
717
|
+
$help.textContent = "";
|
|
718
|
+
}
|
|
719
|
+
getForm() {
|
|
720
|
+
return this.el.closest(this.options.formSelector || "[uni-form-validate]");
|
|
721
|
+
}
|
|
722
|
+
findLabel() {
|
|
723
|
+
const id = this.$input?.id || "";
|
|
724
|
+
const wrapper = this.$input?.closest("[data-field-wrapper]");
|
|
725
|
+
let label = null;
|
|
726
|
+
if (wrapper) label = wrapper.querySelector("[data-field-label]");
|
|
727
|
+
if (!label) label = document.querySelector(`label[for="${id}"]`);
|
|
728
|
+
return label;
|
|
729
|
+
}
|
|
730
|
+
hasChildDirectives() {
|
|
731
|
+
return this.el.querySelector("[uni-field-validate]") != null;
|
|
732
|
+
}
|
|
159
733
|
};
|
|
160
|
-
class UnicornFormValidation {
|
|
161
|
-
presetFields = [];
|
|
162
|
-
static globalValidators = {};
|
|
163
|
-
validators = {};
|
|
164
|
-
options;
|
|
165
|
-
$form;
|
|
166
|
-
static is = "uni-form-validate";
|
|
167
|
-
constructor(el, options = {}) {
|
|
168
|
-
this.$form = selectOne(el);
|
|
169
|
-
this.options = this.mergeOptions(options);
|
|
170
|
-
this.registerDefaultValidators();
|
|
171
|
-
this.init();
|
|
172
|
-
}
|
|
173
|
-
mergeOptions(options) {
|
|
174
|
-
if (Array.isArray(options)) {
|
|
175
|
-
options = {};
|
|
176
|
-
}
|
|
177
|
-
return this.options = mergeDeep({}, defaultOptions, this.options || {}, options);
|
|
178
|
-
}
|
|
179
|
-
get scrollEnabled() {
|
|
180
|
-
return this.options.scroll;
|
|
181
|
-
}
|
|
182
|
-
get scrollOffset() {
|
|
183
|
-
return Number(this.options.scrollOffset || -100);
|
|
184
|
-
}
|
|
185
|
-
get fieldSelector() {
|
|
186
|
-
return this.options.fieldSelector || "input, select, textarea";
|
|
187
|
-
}
|
|
188
|
-
get validatedClass() {
|
|
189
|
-
return this.options.validatedClass || "was-validated";
|
|
190
|
-
}
|
|
191
|
-
init() {
|
|
192
|
-
if (this.$form.tagName === "FORM") {
|
|
193
|
-
this.$form.setAttribute("novalidate", "true");
|
|
194
|
-
this.$form.addEventListener("submit", (event) => {
|
|
195
|
-
if (this.options.enabled && !this.validateAll()) {
|
|
196
|
-
event.stopImmediatePropagation();
|
|
197
|
-
event.stopPropagation();
|
|
198
|
-
event.preventDefault();
|
|
199
|
-
this.$form.dispatchEvent(new CustomEvent("invalid"));
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
return true;
|
|
203
|
-
}, false);
|
|
204
|
-
}
|
|
205
|
-
this.prepareFields(this.findDOMFields());
|
|
206
|
-
this.prepareFields(this.presetFields);
|
|
207
|
-
}
|
|
208
|
-
findDOMFields() {
|
|
209
|
-
return selectAll(this.$form.querySelectorAll(this.fieldSelector));
|
|
210
|
-
}
|
|
211
|
-
prepareFields(inputs) {
|
|
212
|
-
inputs.forEach((input) => {
|
|
213
|
-
this.prepareFieldWrapper(input);
|
|
214
|
-
});
|
|
215
|
-
return Promise.resolve();
|
|
216
|
-
}
|
|
217
|
-
prepareFieldWrapper(input) {
|
|
218
|
-
if (["INPUT", "SELECT", "TEXTAREA"].indexOf(input.tagName) !== -1) {
|
|
219
|
-
let wrapper = input.closest("[uni-field-validate]");
|
|
220
|
-
if (!wrapper) {
|
|
221
|
-
wrapper = input.closest("[data-input-container]") || input.parentElement;
|
|
222
|
-
wrapper?.setAttribute("uni-field-validate", "{}");
|
|
223
|
-
}
|
|
224
|
-
return wrapper;
|
|
225
|
-
}
|
|
226
|
-
return input;
|
|
227
|
-
}
|
|
228
|
-
findFields(containsPresets = true) {
|
|
229
|
-
let inputs = this.findDOMFields();
|
|
230
|
-
if (containsPresets) {
|
|
231
|
-
inputs.push(...this.presetFields);
|
|
232
|
-
}
|
|
233
|
-
return inputs.map((input) => this.prepareFieldWrapper(input)).filter((input) => input != null);
|
|
234
|
-
}
|
|
235
|
-
getFieldComponents(containsPresets = true) {
|
|
236
|
-
const components = [];
|
|
237
|
-
for (const field of this.findFields(containsPresets)) {
|
|
238
|
-
const v = this.getFieldComponent(field);
|
|
239
|
-
if (v) {
|
|
240
|
-
components.push(v);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return components;
|
|
244
|
-
}
|
|
245
|
-
getFieldComponent(input) {
|
|
246
|
-
let v = getBoundedInstance(input, "field.validation");
|
|
247
|
-
if (!v) {
|
|
248
|
-
const wrapper = input.closest("[uni-field-validate]");
|
|
249
|
-
if (wrapper) {
|
|
250
|
-
v = getBoundedInstance(wrapper, "field.validation");
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return v;
|
|
254
|
-
}
|
|
255
|
-
validateAll(fields) {
|
|
256
|
-
this.markFormAsUnvalidated();
|
|
257
|
-
fields = fields || this.findFields();
|
|
258
|
-
let firstFail = null;
|
|
259
|
-
for (const field of fields) {
|
|
260
|
-
const fv = this.getFieldComponent(field);
|
|
261
|
-
if (!fv) {
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
const result = fv.checkValidity();
|
|
265
|
-
if (!result && !firstFail) {
|
|
266
|
-
firstFail = field;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
this.markFormAsValidated();
|
|
270
|
-
if (firstFail && this.scrollEnabled) {
|
|
271
|
-
this.scrollTo(firstFail);
|
|
272
|
-
}
|
|
273
|
-
return firstFail === null;
|
|
274
|
-
}
|
|
275
|
-
async validateAllAsync(fields) {
|
|
276
|
-
this.markFormAsUnvalidated();
|
|
277
|
-
fields = fields || this.findFields();
|
|
278
|
-
let firstFail = null;
|
|
279
|
-
const promises = [];
|
|
280
|
-
for (const field of fields) {
|
|
281
|
-
const fv = this.getFieldComponent(field);
|
|
282
|
-
if (!fv) {
|
|
283
|
-
continue;
|
|
284
|
-
}
|
|
285
|
-
promises.push(
|
|
286
|
-
fv.checkValidityAsync().then((result) => {
|
|
287
|
-
if (!result && !firstFail) {
|
|
288
|
-
firstFail = field;
|
|
289
|
-
}
|
|
290
|
-
return result;
|
|
291
|
-
})
|
|
292
|
-
);
|
|
293
|
-
}
|
|
294
|
-
await Promise.all(promises);
|
|
295
|
-
this.markFormAsValidated();
|
|
296
|
-
if (firstFail && this.scrollEnabled) {
|
|
297
|
-
this.scrollTo(firstFail);
|
|
298
|
-
}
|
|
299
|
-
return firstFail === null;
|
|
300
|
-
}
|
|
301
|
-
scrollTo(element) {
|
|
302
|
-
const offset = this.scrollOffset;
|
|
303
|
-
const elementPosition = element.getBoundingClientRect().top;
|
|
304
|
-
const offsetPosition = elementPosition + window.scrollY + offset;
|
|
305
|
-
window.scrollTo({
|
|
306
|
-
top: offsetPosition,
|
|
307
|
-
behavior: "smooth"
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
markFormAsValidated() {
|
|
311
|
-
if (!this.$form) {
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
this.$form.classList.add(this.validatedClass);
|
|
315
|
-
}
|
|
316
|
-
markFormAsUnvalidated() {
|
|
317
|
-
if (!this.$form) {
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
this.$form.classList.remove(this.validatedClass);
|
|
321
|
-
}
|
|
322
|
-
addField(field) {
|
|
323
|
-
this.presetFields.push(field);
|
|
324
|
-
this.prepareFieldWrapper(field);
|
|
325
|
-
return this;
|
|
326
|
-
}
|
|
327
|
-
registerDefaultValidators() {
|
|
328
|
-
for (let name in validatorHandlers) {
|
|
329
|
-
this.addValidator(name, validatorHandlers[name]);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Add validator handler.
|
|
334
|
-
*/
|
|
335
|
-
addValidator(name, handler, options = {}) {
|
|
336
|
-
options = options || {};
|
|
337
|
-
this.validators[name] = {
|
|
338
|
-
handler,
|
|
339
|
-
options
|
|
340
|
-
};
|
|
341
|
-
return this;
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* Add validator handler.
|
|
345
|
-
*/
|
|
346
|
-
static addGlobalValidator(name, handler, options = {}) {
|
|
347
|
-
options = options || {};
|
|
348
|
-
this.globalValidators[name] = {
|
|
349
|
-
handler,
|
|
350
|
-
options
|
|
351
|
-
};
|
|
352
|
-
return this;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
class UnicornFieldValidation {
|
|
356
|
-
constructor(el, options = {}) {
|
|
357
|
-
this.el = el;
|
|
358
|
-
this.setOptions(options);
|
|
359
|
-
this.$input = this.selectInput();
|
|
360
|
-
this.init();
|
|
361
|
-
}
|
|
362
|
-
$input;
|
|
363
|
-
options = {};
|
|
364
|
-
static is = "uni-field-validate";
|
|
365
|
-
setOptions(options) {
|
|
366
|
-
if (Array.isArray(options)) {
|
|
367
|
-
options = {};
|
|
368
|
-
}
|
|
369
|
-
this.options = options;
|
|
370
|
-
return this;
|
|
371
|
-
}
|
|
372
|
-
get mergedOptions() {
|
|
373
|
-
return mergeDeep({}, defaultFieldOptions, this.globalOptions, this.options);
|
|
374
|
-
}
|
|
375
|
-
get $form() {
|
|
376
|
-
return this.getForm();
|
|
377
|
-
}
|
|
378
|
-
get errorSelector() {
|
|
379
|
-
return this.mergedOptions.errorSelector;
|
|
380
|
-
}
|
|
381
|
-
get selector() {
|
|
382
|
-
return this.mergedOptions.selector;
|
|
383
|
-
}
|
|
384
|
-
get validClass() {
|
|
385
|
-
return this.mergedOptions.validClass;
|
|
386
|
-
}
|
|
387
|
-
get invalidClass() {
|
|
388
|
-
return this.mergedOptions.invalidClass;
|
|
389
|
-
}
|
|
390
|
-
get isVisible() {
|
|
391
|
-
return !!(this.el.offsetWidth || this.el.offsetHeight || this.el.getClientRects().length);
|
|
392
|
-
}
|
|
393
|
-
get isInputOptions() {
|
|
394
|
-
return Boolean(this.mergedOptions.inputOptions);
|
|
395
|
-
}
|
|
396
|
-
get validationMessage() {
|
|
397
|
-
return this.$input?.validationMessage || "";
|
|
398
|
-
}
|
|
399
|
-
get validity() {
|
|
400
|
-
return this.$input?.validity;
|
|
401
|
-
}
|
|
402
|
-
selectInput() {
|
|
403
|
-
let selector = this.selector;
|
|
404
|
-
if (this.mergedOptions.inputOptions) {
|
|
405
|
-
selector += ", " + this.mergedOptions.inputOptionsWrapperSelector;
|
|
406
|
-
}
|
|
407
|
-
let input = this.el.querySelector(selector);
|
|
408
|
-
if (!input) {
|
|
409
|
-
input = this.el.querySelector("input, select, textarea");
|
|
410
|
-
}
|
|
411
|
-
if (!input) {
|
|
412
|
-
return void 0;
|
|
413
|
-
}
|
|
414
|
-
return this.$input = input;
|
|
415
|
-
}
|
|
416
|
-
init() {
|
|
417
|
-
this.selectInput();
|
|
418
|
-
this.bindEvents();
|
|
419
|
-
this.prepareWrapper();
|
|
420
|
-
if (this.isInputOptions) {
|
|
421
|
-
const $input = this.$input;
|
|
422
|
-
if (!($input instanceof HTMLInputElement) && !($input instanceof HTMLSelectElement) && !($input instanceof HTMLTextAreaElement)) {
|
|
423
|
-
$input.validationMessage = "";
|
|
424
|
-
$input.setCustomValidity = (msg) => {
|
|
425
|
-
$input.validationMessage = String(msg);
|
|
426
|
-
};
|
|
427
|
-
$input.checkValidity = () => {
|
|
428
|
-
return this.checkInputOptionsValidity();
|
|
429
|
-
};
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
bindEvents() {
|
|
434
|
-
if (!this.$input) {
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
this.$input.addEventListener("invalid", (e) => {
|
|
438
|
-
this.showInvalidResponse();
|
|
439
|
-
});
|
|
440
|
-
const events = this.mergedOptions.events;
|
|
441
|
-
events.forEach((eventName) => {
|
|
442
|
-
this.$input?.addEventListener(eventName, () => {
|
|
443
|
-
this.checkValidity();
|
|
444
|
-
});
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
prepareWrapper() {
|
|
448
|
-
if (this.el.querySelector(this.errorSelector)?.classList?.contains("invalid-tooltip")) {
|
|
449
|
-
if (window.getComputedStyle(this.el).position === "static") {
|
|
450
|
-
this.el.style.position = "relative";
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
checkValidity() {
|
|
455
|
-
if (!this.$input) {
|
|
456
|
-
return true;
|
|
457
|
-
}
|
|
458
|
-
if (this.$input.hasAttribute("readonly")) {
|
|
459
|
-
return true;
|
|
460
|
-
}
|
|
461
|
-
if (this.$input.hasAttribute("[data-novalidate]")) {
|
|
462
|
-
return true;
|
|
463
|
-
}
|
|
464
|
-
if (this.$input.closest("[data-novalidate]")) {
|
|
465
|
-
return true;
|
|
466
|
-
}
|
|
467
|
-
if (this.hasChildDirectives()) {
|
|
468
|
-
return true;
|
|
469
|
-
}
|
|
470
|
-
this.$input.setCustomValidity("");
|
|
471
|
-
let valid = this.$input.checkValidity();
|
|
472
|
-
if (valid && this.$form) {
|
|
473
|
-
valid = this.runCustomValidity();
|
|
474
|
-
}
|
|
475
|
-
this.updateValidClass(valid);
|
|
476
|
-
return valid;
|
|
477
|
-
}
|
|
478
|
-
runCustomValidity() {
|
|
479
|
-
if (!this.$input) {
|
|
480
|
-
return true;
|
|
481
|
-
}
|
|
482
|
-
const validates = (this.$input.getAttribute("data-validate") || "").split("|");
|
|
483
|
-
let result = true;
|
|
484
|
-
if (this.$input.value !== "" && validates.length) {
|
|
485
|
-
if (!this.checkCustomDataAttributeValidity()) {
|
|
486
|
-
return false;
|
|
487
|
-
}
|
|
488
|
-
for (const validatorName of validates) {
|
|
489
|
-
const [validator, options] = this.getValidator(validatorName) || [null, {}];
|
|
490
|
-
if (!validator) {
|
|
491
|
-
continue;
|
|
492
|
-
}
|
|
493
|
-
Object.assign(options, validator.options);
|
|
494
|
-
let r = validator.handler(this.$input.value, this.$input, options, this);
|
|
495
|
-
if (r instanceof Promise || typeof r === "object" && r.then) {
|
|
496
|
-
r.then((result2) => {
|
|
497
|
-
this.handleAsyncCustomResult(result2, validator);
|
|
498
|
-
});
|
|
499
|
-
continue;
|
|
500
|
-
}
|
|
501
|
-
if (!this.handleCustomResult(r, validator)) {
|
|
502
|
-
result = false;
|
|
503
|
-
break;
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
return result;
|
|
508
|
-
}
|
|
509
|
-
async checkValidityAsync() {
|
|
510
|
-
if (!this.$input) {
|
|
511
|
-
return true;
|
|
512
|
-
}
|
|
513
|
-
if (this.$input.hasAttribute("readonly")) {
|
|
514
|
-
return true;
|
|
515
|
-
}
|
|
516
|
-
if (this.$input.hasAttribute("[data-novalidate]")) {
|
|
517
|
-
return true;
|
|
518
|
-
}
|
|
519
|
-
if (this.hasChildDirectives()) {
|
|
520
|
-
return true;
|
|
521
|
-
}
|
|
522
|
-
this.$input.setCustomValidity("");
|
|
523
|
-
let valid = this.$input.checkValidity();
|
|
524
|
-
if (valid && this.$form) {
|
|
525
|
-
valid = await this.runCustomValidityAsync();
|
|
526
|
-
}
|
|
527
|
-
this.updateValidClass(valid);
|
|
528
|
-
return valid;
|
|
529
|
-
}
|
|
530
|
-
async runCustomValidityAsync() {
|
|
531
|
-
if (!this.$input) {
|
|
532
|
-
return true;
|
|
533
|
-
}
|
|
534
|
-
const validates = (this.$input.getAttribute("data-validate") || "").split("|");
|
|
535
|
-
const results = [];
|
|
536
|
-
const promises = [];
|
|
537
|
-
if (this.$input.value !== "" && validates.length) {
|
|
538
|
-
if (!this.checkCustomDataAttributeValidity()) {
|
|
539
|
-
return false;
|
|
540
|
-
}
|
|
541
|
-
for (const validatorName of validates) {
|
|
542
|
-
let [validator, options] = this.getValidator(validatorName) || [null, {}];
|
|
543
|
-
if (!validator) {
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
options = Object.assign({}, options, validator.options || {});
|
|
547
|
-
promises.push(
|
|
548
|
-
Promise.resolve(validator.handler(this.$input.value, this.$input, options, this)).then((r) => {
|
|
549
|
-
results.push(this.handleAsyncCustomResult(r, validator));
|
|
550
|
-
return r;
|
|
551
|
-
})
|
|
552
|
-
);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
await Promise.all(promises);
|
|
556
|
-
for (const result of results) {
|
|
557
|
-
if (result === false) {
|
|
558
|
-
return false;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
return true;
|
|
562
|
-
}
|
|
563
|
-
checkCustomDataAttributeValidity() {
|
|
564
|
-
const error2 = this.$input?.dataset.validationFail;
|
|
565
|
-
return this.handleCustomResult(error2);
|
|
566
|
-
}
|
|
567
|
-
checkInputOptionsValidity() {
|
|
568
|
-
if (!this.$input) {
|
|
569
|
-
return true;
|
|
570
|
-
}
|
|
571
|
-
const isRequired = this.$input.getAttribute("required") != null;
|
|
572
|
-
const optionWrappers = this.$input.querySelectorAll(this.mergedOptions.inputOptionsSelector);
|
|
573
|
-
let result = true;
|
|
574
|
-
if (isRequired) {
|
|
575
|
-
for (const optionWrapper of optionWrappers) {
|
|
576
|
-
const input = optionWrapper.querySelector("input");
|
|
577
|
-
result = false;
|
|
578
|
-
if (input?.checked) {
|
|
579
|
-
result = true;
|
|
580
|
-
break;
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
const n = document.createElement("input");
|
|
585
|
-
n.required = isRequired;
|
|
586
|
-
if (result) {
|
|
587
|
-
n.value = "placeholder";
|
|
588
|
-
}
|
|
589
|
-
n.checkValidity();
|
|
590
|
-
this.$input.validationMessage = n.validationMessage;
|
|
591
|
-
this.$input.validity = n.validity;
|
|
592
|
-
for (const optionWrapper of optionWrappers) {
|
|
593
|
-
const input = optionWrapper.querySelector("input");
|
|
594
|
-
input?.setCustomValidity(n.validationMessage);
|
|
595
|
-
}
|
|
596
|
-
if (!result) {
|
|
597
|
-
this.$input.dispatchEvent(
|
|
598
|
-
new CustomEvent("invalid")
|
|
599
|
-
);
|
|
600
|
-
}
|
|
601
|
-
return result;
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* @param valid {boolean}
|
|
605
|
-
*/
|
|
606
|
-
updateValidClass(valid) {
|
|
607
|
-
const $errorElement = this.getErrorElement();
|
|
608
|
-
const $invalidTarget = $errorElement?.previousElementSibling;
|
|
609
|
-
this.$input?.classList.remove(this.invalidClass);
|
|
610
|
-
this.$input?.classList.remove(this.validClass);
|
|
611
|
-
this.el.classList.remove(this.invalidClass);
|
|
612
|
-
this.el.classList.remove(this.validClass);
|
|
613
|
-
$invalidTarget?.classList.remove(this.invalidClass);
|
|
614
|
-
$invalidTarget?.classList.remove(this.validClass);
|
|
615
|
-
if (valid) {
|
|
616
|
-
this.$input?.classList.add(this.validClass);
|
|
617
|
-
this.el.classList.add(this.validClass);
|
|
618
|
-
$invalidTarget?.classList.add(this.validClass);
|
|
619
|
-
} else {
|
|
620
|
-
this.$input?.classList.add(this.invalidClass);
|
|
621
|
-
this.el.classList.add(this.invalidClass);
|
|
622
|
-
$invalidTarget?.classList.add(this.invalidClass);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
getFormValidation(element) {
|
|
626
|
-
return getBoundedInstance(element || this.getForm(), "form.validation");
|
|
627
|
-
}
|
|
628
|
-
get globalOptions() {
|
|
629
|
-
return this.getFormValidation()?.options?.fieldDefaults ?? {};
|
|
630
|
-
}
|
|
631
|
-
getValidator(name) {
|
|
632
|
-
const matches = name.match(/(?<type>[\w\-_]+)(\((?<params>.*)\))*/);
|
|
633
|
-
if (!matches) {
|
|
634
|
-
return null;
|
|
635
|
-
}
|
|
636
|
-
const validatorName = matches.groups?.type || "";
|
|
637
|
-
const params = matches.groups?.params || "";
|
|
638
|
-
const fv = this.getFormValidation(this.$form);
|
|
639
|
-
const validator = fv?.validators[validatorName] || UnicornFormValidation.globalValidators[validatorName];
|
|
640
|
-
if (!validator) {
|
|
641
|
-
return null;
|
|
642
|
-
}
|
|
643
|
-
const paramMatches = params.matchAll(/(?<key>\w+)(\s?[=:]\s?(?<value>\w+))?/g);
|
|
644
|
-
const options = {};
|
|
645
|
-
for (const paramMatch of paramMatches) {
|
|
646
|
-
const match = paramMatch?.groups;
|
|
647
|
-
if (!match) {
|
|
648
|
-
continue;
|
|
649
|
-
}
|
|
650
|
-
options[match.key] = handleParamValue(match.value);
|
|
651
|
-
}
|
|
652
|
-
return [validator, options];
|
|
653
|
-
}
|
|
654
|
-
handleCustomResult(result, validator) {
|
|
655
|
-
if (typeof result === "string") {
|
|
656
|
-
this.$input?.setCustomValidity(result);
|
|
657
|
-
result = result === "";
|
|
658
|
-
} else if (result === void 0) {
|
|
659
|
-
result = true;
|
|
660
|
-
}
|
|
661
|
-
if (result) {
|
|
662
|
-
this.$input?.setCustomValidity("");
|
|
663
|
-
} else if (validator) {
|
|
664
|
-
this.raiseCustomErrorState(validator);
|
|
665
|
-
}
|
|
666
|
-
return result;
|
|
667
|
-
}
|
|
668
|
-
handleAsyncCustomResult(result, validator) {
|
|
669
|
-
result = this.handleCustomResult(result, validator);
|
|
670
|
-
this.$input?.checkValidity();
|
|
671
|
-
this.updateValidClass(result);
|
|
672
|
-
return result;
|
|
673
|
-
}
|
|
674
|
-
raiseCustomErrorState(validator) {
|
|
675
|
-
let help;
|
|
676
|
-
if (this.$input?.validationMessage === "") {
|
|
677
|
-
help = validator.options?.notice;
|
|
678
|
-
if (typeof help === "function") {
|
|
679
|
-
help = help(this.$input, this);
|
|
680
|
-
}
|
|
681
|
-
if (help != null) {
|
|
682
|
-
this.$input?.setCustomValidity(help);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
if (this.$input?.validationMessage === "") {
|
|
686
|
-
this.$input?.setCustomValidity(trans("unicorn.message.validation.custom.error"));
|
|
687
|
-
}
|
|
688
|
-
this.$input?.dispatchEvent(
|
|
689
|
-
new CustomEvent("invalid")
|
|
690
|
-
);
|
|
691
|
-
}
|
|
692
|
-
setAsInvalidAndReport(error2) {
|
|
693
|
-
this.setCustomValidity(error2);
|
|
694
|
-
this.showInvalidResponse();
|
|
695
|
-
}
|
|
696
|
-
setCustomValidity(error2) {
|
|
697
|
-
this.$input?.setCustomValidity(error2);
|
|
698
|
-
}
|
|
699
|
-
reportValidity() {
|
|
700
|
-
if (this.validationMessage !== "") {
|
|
701
|
-
this.showInvalidResponse();
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
showInvalidResponse() {
|
|
705
|
-
if (this.hasChildDirectives()) {
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
const state = this.$input?.validity;
|
|
709
|
-
let message = this.$input?.validationMessage || "";
|
|
710
|
-
for (let key in state) {
|
|
711
|
-
if (state[key] && this.$input?.dataset[key + "Message"]) {
|
|
712
|
-
message = this.$input?.dataset[key + "Message"] || "";
|
|
713
|
-
break;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
if (!this.isVisible) {
|
|
717
|
-
let title = this.findLabel()?.textContent;
|
|
718
|
-
if (!title) {
|
|
719
|
-
title = this.$input?.name || "";
|
|
720
|
-
}
|
|
721
|
-
useUITheme().renderMessage(
|
|
722
|
-
`Field: ${title} - ${message}`,
|
|
723
|
-
"warning"
|
|
724
|
-
);
|
|
725
|
-
}
|
|
726
|
-
let $help = this.getErrorElement();
|
|
727
|
-
if (!$help) {
|
|
728
|
-
$help = this.createHelpElement();
|
|
729
|
-
this.el.appendChild($help);
|
|
730
|
-
this.prepareWrapper();
|
|
731
|
-
}
|
|
732
|
-
$help.textContent = message;
|
|
733
|
-
this.updateValidClass(false);
|
|
734
|
-
}
|
|
735
|
-
getErrorElement() {
|
|
736
|
-
return this.el.querySelector(this.errorSelector);
|
|
737
|
-
}
|
|
738
|
-
createHelpElement() {
|
|
739
|
-
const className = this.mergedOptions.errorMessageClass;
|
|
740
|
-
const parsed = this.parseSelector(this.errorSelector || "");
|
|
741
|
-
const $help = html(`<div class="${className}"></div>`);
|
|
742
|
-
$help.classList.add(...parsed.classes);
|
|
743
|
-
parsed.attrs.forEach((attr) => {
|
|
744
|
-
$help.setAttribute(attr[0], attr[1] || "");
|
|
745
|
-
});
|
|
746
|
-
parsed.ids.forEach((id) => {
|
|
747
|
-
$help.id = id;
|
|
748
|
-
});
|
|
749
|
-
return $help;
|
|
750
|
-
}
|
|
751
|
-
/**
|
|
752
|
-
* @see https://stackoverflow.com/a/17888178
|
|
753
|
-
*/
|
|
754
|
-
parseSelector(subselector) {
|
|
755
|
-
const obj = { tags: [], classes: [], ids: [], attrs: [] };
|
|
756
|
-
for (const token of subselector.split(/(?=\.)|(?=#)|(?=\[)/)) {
|
|
757
|
-
switch (token[0]) {
|
|
758
|
-
case "#":
|
|
759
|
-
obj.ids.push(token.slice(1));
|
|
760
|
-
break;
|
|
761
|
-
case ".":
|
|
762
|
-
obj.classes.push(token.slice(1));
|
|
763
|
-
break;
|
|
764
|
-
case "[":
|
|
765
|
-
obj.attrs.push(token.slice(1, -1).split("="));
|
|
766
|
-
break;
|
|
767
|
-
default:
|
|
768
|
-
obj.tags.push(token);
|
|
769
|
-
break;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
return obj;
|
|
773
|
-
}
|
|
774
|
-
setAsValidAndClearResponse() {
|
|
775
|
-
this.setCustomValidity("");
|
|
776
|
-
this.updateValidClass(true);
|
|
777
|
-
this.clearInvalidResponse();
|
|
778
|
-
}
|
|
779
|
-
clearInvalidResponse() {
|
|
780
|
-
const $help = this.el.querySelector(this.errorSelector);
|
|
781
|
-
$help.textContent = "";
|
|
782
|
-
}
|
|
783
|
-
getForm() {
|
|
784
|
-
return this.el.closest(this.options.formSelector || "[uni-form-validate]");
|
|
785
|
-
}
|
|
786
|
-
findLabel() {
|
|
787
|
-
const id = this.$input?.id || "";
|
|
788
|
-
const wrapper = this.$input?.closest("[data-field-wrapper]");
|
|
789
|
-
let label = null;
|
|
790
|
-
if (wrapper) {
|
|
791
|
-
label = wrapper.querySelector("[data-field-label]");
|
|
792
|
-
}
|
|
793
|
-
if (!label) {
|
|
794
|
-
label = document.querySelector(`label[for="${id}"]`);
|
|
795
|
-
}
|
|
796
|
-
return label;
|
|
797
|
-
}
|
|
798
|
-
hasChildDirectives() {
|
|
799
|
-
return this.el.querySelector("[uni-field-validate]") != null;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
734
|
validatorHandlers.username = function(value, element) {
|
|
803
|
-
|
|
804
|
-
return !regex.test(value);
|
|
735
|
+
return !(/* @__PURE__ */ new RegExp("[<|>|\"|'|%|;|(|)|&]", "i")).test(value);
|
|
805
736
|
};
|
|
806
737
|
validatorHandlers.numeric = function(value, element) {
|
|
807
|
-
|
|
808
|
-
return regex.test(value);
|
|
738
|
+
return /^(\d|-)?(\d|,)*\.?\d*$/.test(value);
|
|
809
739
|
};
|
|
810
740
|
validatorHandlers.email = function(value, element) {
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
return regex.test(value);
|
|
741
|
+
value = toASCII(value);
|
|
742
|
+
return /^[a-zA-Z0-9.!#$%&’*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(value);
|
|
814
743
|
};
|
|
815
744
|
validatorHandlers.url = function(value, element) {
|
|
816
|
-
|
|
817
|
-
return regex.test(value);
|
|
745
|
+
return /^(?:(?: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.test(value);
|
|
818
746
|
};
|
|
819
747
|
validatorHandlers.alnum = function(value, element) {
|
|
820
|
-
|
|
821
|
-
return regex.test(value);
|
|
748
|
+
return /^[a-zA-Z0-9]*$/.test(value);
|
|
822
749
|
};
|
|
823
750
|
validatorHandlers.color = function(value, element) {
|
|
824
|
-
|
|
825
|
-
return regex.test(value);
|
|
751
|
+
return /^#(?:[0-9a-f]{3}){1,2}$/.test(value);
|
|
826
752
|
};
|
|
753
|
+
/**
|
|
754
|
+
* @see http://www.virtuosimedia.com/dev/php/37-tested-php-perl-and-javascript-regular-expressions
|
|
755
|
+
*/
|
|
827
756
|
validatorHandlers.creditcard = function(value, element) {
|
|
828
|
-
|
|
829
|
-
return regex.test(value);
|
|
757
|
+
return /^(?: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})*$/.test(value);
|
|
830
758
|
};
|
|
831
759
|
validatorHandlers.ip = function(value, element) {
|
|
832
|
-
|
|
833
|
-
return regex.test(value);
|
|
760
|
+
return /^((?:(?: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]?))*$/.test(value);
|
|
834
761
|
};
|
|
835
762
|
validatorHandlers["password-confirm"] = function(value, element) {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
}
|
|
840
|
-
const target = document.querySelector(selector);
|
|
841
|
-
return target?.value === value;
|
|
763
|
+
const selector = element.dataset.confirmTarget;
|
|
764
|
+
if (!selector) throw new Error("Validator: \"password-confirm\" must add \"data-confirm-target\" attribute.");
|
|
765
|
+
return document.querySelector(selector)?.value === value;
|
|
842
766
|
};
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
const instance = getBoundedInstance(el, "field.validation");
|
|
863
|
-
instance.setOptions(JSON.parse(binding.value || "{}") || {});
|
|
864
|
-
}
|
|
865
|
-
})
|
|
866
|
-
]);
|
|
767
|
+
var ready = /* @__PURE__ */ Promise.all([/* @__PURE__ */ useUniDirective("form-validate", {
|
|
768
|
+
mounted(el, binding) {
|
|
769
|
+
getBoundedInstance(el, "form.validation", (ele) => {
|
|
770
|
+
return new UnicornFormValidation(ele, JSON.parse(binding.value || "{}"));
|
|
771
|
+
});
|
|
772
|
+
},
|
|
773
|
+
updated(el, binding) {
|
|
774
|
+
getBoundedInstance(el, "form.validation").mergeOptions(JSON.parse(binding.value || "{}"));
|
|
775
|
+
}
|
|
776
|
+
}), /* @__PURE__ */ useUniDirective("field-validate", {
|
|
777
|
+
mounted(el, binding) {
|
|
778
|
+
getBoundedInstance(el, "field.validation", (ele) => {
|
|
779
|
+
return new UnicornFieldValidation(ele, JSON.parse(binding.value || "{}"));
|
|
780
|
+
});
|
|
781
|
+
},
|
|
782
|
+
updated(el, binding) {
|
|
783
|
+
getBoundedInstance(el, "field.validation").setOptions(JSON.parse(binding.value || "{}") || {});
|
|
784
|
+
}
|
|
785
|
+
})]);
|
|
867
786
|
function handleParamValue(value) {
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
}
|
|
874
|
-
if (value === "true") {
|
|
875
|
-
return true;
|
|
876
|
-
}
|
|
877
|
-
if (value === "false") {
|
|
878
|
-
return true;
|
|
879
|
-
}
|
|
880
|
-
return value;
|
|
787
|
+
if (!isNaN(Number(value))) return Number(value);
|
|
788
|
+
if (value === "null") return null;
|
|
789
|
+
if (value === "true") return true;
|
|
790
|
+
if (value === "false") return true;
|
|
791
|
+
return value;
|
|
881
792
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
validatorHandlers as validators
|
|
887
|
-
};
|
|
888
|
-
//# sourceMappingURL=validation.js.map
|
|
793
|
+
//#endregion
|
|
794
|
+
export { UnicornFieldValidation, UnicornFormValidation, ready, validatorHandlers as validators };
|
|
795
|
+
|
|
796
|
+
//# sourceMappingURL=validation.js.map
|