@jdlien/validator-utils 1.1.3 → 1.1.4

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.
@@ -1,570 +1 @@
1
- /**
2
- * Utilities used by the Validator class.
3
- *
4
- * @format
5
- */
6
- export function isFormControl(el) {
7
- return (el instanceof HTMLInputElement ||
8
- el instanceof HTMLSelectElement ||
9
- el instanceof HTMLTextAreaElement);
10
- }
11
- // Checks if an element has a type or data-type attribute matching
12
- // one of the types in the passed array
13
- export function isType(el, types) {
14
- if (typeof types === 'string')
15
- types = [types];
16
- const dataType = el.dataset.type || '';
17
- const type = el.type;
18
- if (types.includes(dataType))
19
- return true;
20
- if (types.includes(type))
21
- return true;
22
- return false;
23
- }
24
- // Converts moment.js-style formats strings to the flatpickr format
25
- // https://flatpickr.js.org/formatting/
26
- // Useful to use FlatPickr in conjunction with Validator
27
- // Not comprehensive but covers common format strings
28
- export function momentToFPFormat(format) {
29
- return format
30
- .replace(/YYYY/g, 'Y')
31
- .replace(/YY/g, 'y')
32
- .replace(/MMMM/g, 'F')
33
- .replace(/MMM/g, '{3}')
34
- .replace(/MM/g, '{2}')
35
- .replace(/M/g, 'n')
36
- .replace(/DD/g, '{5}')
37
- .replace(/D/g, 'j')
38
- .replace(/dddd/g, 'l')
39
- .replace(/ddd/g, 'D')
40
- .replace(/dd/g, 'D')
41
- .replace(/d/g, 'w')
42
- .replace(/HH/g, '{6}')
43
- .replace(/H/g, 'G')
44
- .replace(/hh/g, 'h')
45
- .replace(/mm/g, 'i')
46
- .replace(/m/g, 'i')
47
- .replace(/ss/g, 'S')
48
- .replace(/s/g, 's')
49
- .replace(/A/gi, 'K')
50
- .replace(/\{3\}/g, 'M')
51
- .replace(/\{2\}/g, 'm')
52
- .replace(/\{5\}/g, 'd')
53
- .replace(/\{6\}/g, 'H');
54
- }
55
- // Converts month name to zero-based month number
56
- export function monthToNumber(str) {
57
- const num = parseInt(str);
58
- if (typeof str === 'number' || !isNaN(num))
59
- return num - 1;
60
- const m = new Date(`1 ${str} 2000`).getMonth();
61
- if (!isNaN(m))
62
- return m;
63
- const dict = {
64
- ja: 0,
65
- en: 0,
66
- fe: 1,
67
- fé: 1,
68
- ap: 3,
69
- ab: 3,
70
- av: 3,
71
- mai: 4,
72
- juin: 5,
73
- juil: 6,
74
- au: 7,
75
- ag: 7,
76
- ao: 7,
77
- se: 8,
78
- o: 9,
79
- n: 10,
80
- d: 11,
81
- };
82
- for (const key in dict) {
83
- if (str.toLowerCase().startsWith(key))
84
- return dict[key];
85
- }
86
- throw new Error('Invalid month name: ' + str);
87
- }
88
- // Convert two-digit year to a four digit year
89
- export function yearToFull(year) {
90
- if (typeof year === 'string')
91
- year = parseInt(year.replace(/\D/g, ''));
92
- if (year > 99)
93
- return year;
94
- // If year less than 20 years in the future, assume the 21st century
95
- if (year < (new Date().getFullYear() + 20) % 100)
96
- return year + 2000;
97
- return year + 1900;
98
- }
99
- // Parses a string and returns the most plausible date
100
- export function parseDate(value) {
101
- if (value instanceof Date)
102
- return value;
103
- value = value.trim().toLowerCase();
104
- let year = 0;
105
- let month = 0;
106
- let day = 0;
107
- let hour = 0;
108
- let minute = 0;
109
- let second = 0;
110
- const timeRE = new RegExp(/\d{1,2}\:\d\d(?:\:\d\ds?)?\s?(?:[a|p]m?)?/gi);
111
- // If the value contains a time, set the time variables
112
- if (timeRE.test(value)) {
113
- const timeStr = value.match(timeRE)[0];
114
- // Remove the time from the string
115
- value = value.replace(timeStr, '').trim();
116
- const timeParts = parseTime(timeStr);
117
- // Assign the time to the variables
118
- if (timeParts !== null)
119
- ({ hour, minute, second } = timeParts);
120
- // If the value seems to be a time only, return a date with this time.
121
- if (value.length <= 2) {
122
- const now = new Date();
123
- return new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute, second);
124
- }
125
- }
126
- // Strip day of the week from the string in English, French, or Spanish
127
- const dayOfWeekRegex = /(^|\b)(mo|tu|we|th|fr|sa|su|lu|mard|mer|jeu|ve|dom)[\w]*\.?/gi;
128
- value = value.replace(dayOfWeekRegex, '').trim();
129
- // Convert now and today to the current date at midnight
130
- const today = new Date(new Date().setHours(0, 0, 0, 0));
131
- if (/(now|today)/.test(value))
132
- return today;
133
- if (value.includes('tomorrow'))
134
- return new Date(today.setDate(today.getDate() + 1));
135
- // Handle a undelimited 6 or 8-digit number and treat it as YYYYMMDD
136
- if (value.length === 8)
137
- value = value.replace(/(\d\d\d\d)(\d\d)(\d\d)/, '$1-$2-$3');
138
- if (value.length === 6)
139
- value = value.replace(/(\d\d)(\d\d)(\d\d)/, yearToFull(value.slice(0, 2)) + '-$2-$3');
140
- try {
141
- ;
142
- ({ year, month, day } = guessDateParts(value));
143
- }
144
- catch (e) {
145
- return new Date('');
146
- }
147
- // Return date (month is 0-based)
148
- return new Date(year, month - 1, day, hour, minute, second);
149
- } // end parseDate
150
- // Returns array of possible meanings for a token in a date string
151
- // Pass an array of known parameters to exclude them from the guess
152
- // Assumes months are 1-based.
153
- export function guessDatePart(num, knownMeanings = [null, null, null]) {
154
- const unknown = (arr) => arr.filter((i) => !knownMeanings.includes(i));
155
- if (num === 0 || num > 31)
156
- return unknown(['y']);
157
- if (num > 12)
158
- return unknown(['d', 'y']);
159
- if (num >= 1 && num <= 12)
160
- return unknown(['m', 'd', 'y']);
161
- return [];
162
- }
163
- // Returns most likely meanings of each part of a date. Assumes 1-based months
164
- export function guessDateParts(str) {
165
- const tokens = str.split(/[\s-/:.,]+/).filter((i) => i !== '');
166
- // If two tokens, add a year to the beginning
167
- if (tokens.length < 3) {
168
- // If one of the tokens is a 4-digit number, don't have enough info to guess the date
169
- if (str.match(/\d{4}/) !== null)
170
- throw new Error('Invalid Date');
171
- else
172
- tokens.unshift(String(new Date().getFullYear()));
173
- }
174
- const date = { year: 0, month: 0, day: 0 };
175
- function assignPart(part, num) {
176
- if (part === 'year')
177
- date.year = yearToFull(num);
178
- else
179
- date[part] = num;
180
- }
181
- let count = 0;
182
- while (!(date.year && date.month && date.day)) {
183
- tokenLoop: for (const token of tokens) {
184
- count++;
185
- // If word
186
- if (/^[a-zA-Zé]+$/.test(token)) {
187
- if (!date.month)
188
- assignPart('month', monthToNumber(token) + 1);
189
- continue;
190
- }
191
- // If the token is a year
192
- if (/^'\d\d$/.test(token) || /^\d{3,5}$/.test(token)) {
193
- if (!date.year)
194
- assignPart('year', parseInt(token.replace(/'/, '')));
195
- continue;
196
- }
197
- // If the token is a number
198
- const num = parseInt(token);
199
- if (isNaN(num)) {
200
- console.error(`not date because ${token} isNaN`);
201
- throw new Error('Invalid Date');
202
- }
203
- const meanings = guessDatePart(num, [
204
- date.year ? 'y' : null,
205
- date.month ? 'm' : null,
206
- date.day ? 'd' : null,
207
- ]);
208
- if (meanings.length == 1) {
209
- if (meanings[0] === 'm' && !date.month) {
210
- assignPart('month', num);
211
- continue tokenLoop;
212
- }
213
- if (meanings[0] === 'd' && !date.day) {
214
- assignPart('day', num);
215
- continue tokenLoop;
216
- }
217
- if (meanings[0] === 'y' && !date.year) {
218
- assignPart('year', num);
219
- continue tokenLoop;
220
- }
221
- }
222
- // If we have no idea what the token is after going through all of them
223
- // set token to the first thing it could be starting with month
224
- if (count > 3) {
225
- if (!date.month && meanings.includes('m'))
226
- assignPart('month', num);
227
- else if (!date.day && meanings.includes('d'))
228
- assignPart('day', num);
229
- // If the previous two lines ran, the year will be assigned on the next iteration
230
- }
231
- }
232
- // Should never take more than six iterations to figure out a valid date
233
- if (count > 6)
234
- throw new Error('Invalid Date');
235
- }
236
- if (date.year && date.month && date.day)
237
- return date;
238
- /* c8 ignore next */
239
- throw new Error('Invalid Date');
240
- }
241
- // A simplified version of apps-date.ts's parseTime function.
242
- export function parseTime(value) {
243
- // if "now" or "today" is in the string, return the current time
244
- value = value.trim().toLowerCase();
245
- if (value === 'now') {
246
- const now = new Date();
247
- return { hour: now.getHours(), minute: now.getMinutes(), second: now.getSeconds() };
248
- }
249
- // If there is a 3-4 digit number, assume it's a time and add a colon
250
- const timeParts = value.match(/(\d{3,4})/);
251
- if (timeParts) {
252
- const length = timeParts[1].length;
253
- const hour = timeParts[1].slice(0, length == 3 ? 1 : 2);
254
- const minutes = timeParts[1].slice(-2);
255
- value = value.replace(timeParts[1], hour + ':' + minutes);
256
- }
257
- // Match a simple time without minutes or seconds and optional am/pm
258
- const shortTimeRegex = new RegExp(/^(\d{1,2})(?::(\d{1,2}))?\s*(?:(a|p)m?)?$/i);
259
- if (shortTimeRegex.test(value)) {
260
- const shortParts = value.match(shortTimeRegex);
261
- /* c8 ignore next */
262
- if (shortParts === null)
263
- return null;
264
- value = shortParts[1] + ':' + (shortParts[2] || '00') + (shortParts[3] || '');
265
- }
266
- // Regex to match time in 0:0 format with optional seconds and am/pm
267
- const timeRegex = new RegExp(/^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(?:(a|p)m?)?$/i);
268
- if (!timeRegex.test(value))
269
- return null;
270
- const parts = value.match(timeRegex);
271
- /* c8 ignore next */
272
- if (parts === null)
273
- return null;
274
- const hour = parseInt(parts[1]);
275
- const minute = parseInt(parts[2]);
276
- const second = parts[3] ? parseInt(parts[3]) : 0;
277
- const ampm = parts[4];
278
- /* c8 ignore next */
279
- if (isNaN(hour) || isNaN(minute) || isNaN(second))
280
- return null;
281
- if (ampm === 'p' && hour < 12)
282
- return { hour: hour + 12, minute, second };
283
- if (ampm === 'a' && hour === 12)
284
- return { hour: 0, minute, second };
285
- /* c8 ignore next 3 */
286
- if (hour < 0 || hour > 23)
287
- return null;
288
- if (minute < 0 || minute > 59)
289
- return null;
290
- if (second < 0 || second > 59)
291
- return null;
292
- return { hour, minute, second };
293
- } // parseTime
294
- export function parseTimeToString(value, format = 'h:mm A') {
295
- const time = parseTime(value);
296
- if (time) {
297
- const date = new Date();
298
- date.setHours(time.hour);
299
- date.setMinutes(time.minute);
300
- date.setSeconds(time.second);
301
- date.setMilliseconds(0);
302
- return formatDateTime(date, format);
303
- }
304
- return '';
305
- }
306
- // Accepts a date or date-like string and returns a formatted date string
307
- // Uses moment-compatible format strings
308
- export function formatDateTime(date, format = 'YYYY-MM-DD') {
309
- // Ensure the date is a valid date object
310
- date = parseDate(date);
311
- // if date is an invalid date object, return empty string
312
- if (isNaN(date.getTime()))
313
- return '';
314
- const d = {
315
- y: date.getFullYear(),
316
- M: date.getMonth(),
317
- D: date.getDate(),
318
- W: date.getDay(),
319
- H: date.getHours(),
320
- m: date.getMinutes(),
321
- s: date.getSeconds(),
322
- ms: date.getMilliseconds(),
323
- };
324
- const pad = (n, w = 2) => (n + '').padStart(w, '0');
325
- const getH = () => d.H % 12 || 12;
326
- const getMeridiem = (hour) => (hour < 12 ? 'AM' : 'PM');
327
- const monthToString = (month) => 'January|February|March|April|May|June|July|August|September|October|November|December'.split('|')[month];
328
- function dayToString(day, len = 0) {
329
- const days = 'Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday'.split('|');
330
- return len ? days[day].slice(0, len) : days[day];
331
- }
332
- const matches = {
333
- YY: String(d.y).slice(-2),
334
- YYYY: d.y,
335
- M: d.M + 1,
336
- MM: pad(d.M + 1),
337
- MMMM: monthToString(d.M),
338
- MMM: monthToString(d.M).slice(0, 3),
339
- D: String(d.D),
340
- DD: pad(d.D),
341
- d: String(d.W),
342
- dd: dayToString(d.W, 2),
343
- ddd: dayToString(d.W, 3),
344
- dddd: dayToString(d.W),
345
- H: String(d.H),
346
- HH: pad(d.H),
347
- h: getH(),
348
- hh: pad(getH()),
349
- A: getMeridiem(d.H),
350
- a: getMeridiem(d.H).toLowerCase(),
351
- m: String(d.m),
352
- mm: pad(d.m),
353
- s: String(d.s),
354
- ss: pad(d.s),
355
- SSS: pad(d.ms, 3),
356
- };
357
- return format.replace(/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, (match, $1) => $1 || matches[match]);
358
- } // end formatDateTime
359
- export function parseDateToString(value, format) {
360
- const date = parseDate(value);
361
- if (isNaN(date.getTime()))
362
- return '';
363
- // if the format is undefined or has length of 0, set it to the default format
364
- if (!format || format.length === 0)
365
- format = 'YYYY-MMM-DD';
366
- return formatDateTime(date, format);
367
- }
368
- // Check if a the value of a specified input is a valid date and is in a specified date range
369
- export function isDate(value) {
370
- if (typeof value !== 'string' && !(value instanceof Date))
371
- return false;
372
- let date = parseDate(value);
373
- if (date === null || date === undefined)
374
- return false;
375
- return !isNaN(date.getTime());
376
- }
377
- // Check if a date is within the specified range
378
- export function isDateInRange(date, range) {
379
- if (range === 'past' && date > new Date())
380
- return false;
381
- if (range === 'future' && date.getTime() < new Date().setHours(0, 0, 0, 0))
382
- return false;
383
- // In the future, may add support for ranges like 'last 30 days' or 'next 3 months'
384
- // or specific dates like '2019-01-01 to 2019-12-31'
385
- return true;
386
- }
387
- export function isTime(value) {
388
- let timeObj = parseTime(value);
389
- if (timeObj === null)
390
- return false;
391
- return !isNaN(timeObj.hour) && !isNaN(timeObj.minute) && !isNaN(timeObj.second);
392
- }
393
- // Input validation for email fields
394
- export function isEmail(value) {
395
- // Emails cannot be longer than 255 characters
396
- if (value.length > 255)
397
- return false;
398
- // This is a relatively simple regex just to check that an email has a valid TLD
399
- // It will not catch all invalid emails, the next regex does that
400
- let emailTLDRegex = new RegExp(/^.+@.+\.[a-zA-Z0-9]{2,}$/);
401
- if (!emailTLDRegex.test(value))
402
- return false;
403
- // A comprehensive regular expression to check for valid emails. Does not allow for unicode characters.
404
- let re = '';
405
- // Begin local part. Allow alphanumeric characters and some special characters
406
- re += "^([a-zA-Z0-9!#$%'*+/=?^_`{|}~-]+";
407
- // Allow dot separated sequences of the above characters (representing multiple labels in the local part)
408
- re += "(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*";
409
- // Allow a quoted string (using either single or double quotes)
410
- re += '|';
411
- re +=
412
- '"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*"';
413
- // End of local part and begin domain part of email address
414
- re += ')@(';
415
- // Domain part can be either a sequence of labels, separated by dots, ending with a TLD
416
- re += '(';
417
- re += '(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+';
418
- // TLD must be at least 2 characters long
419
- re += '[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?';
420
- re += ')';
421
- // Or, it can be an address within square brackets but
422
- // we will not allow this - real people shouldn't be using IP addresses in email addresses
423
- re += ')$'; // End of string
424
- let emailRegex = new RegExp(re);
425
- return emailRegex.test(value);
426
- }
427
- // Parse a North American Numbering Plan phone number (xxx-xxx-xxxx)
428
- export function parseNANPTel(value) {
429
- // first character regex, strip anything that isn't part of the area code
430
- value = value.replace(/^[^2-90]+/g, '');
431
- // now the first number should be for the area code
432
- value = value.replace(/(\d\d\d).*?(\d\d\d).*?(\d\d\d\d)(.*)/, '$1-$2-$3$4');
433
- return value;
434
- }
435
- // Checks that the phone number is valid in North American Numbering Plan
436
- export function isNANPTel(value) {
437
- return /^\d\d\d-\d\d\d-\d\d\d\d$/.test(value);
438
- }
439
- export function parseInteger(value) {
440
- return value.replace(/[^0-9]/g, '');
441
- }
442
- export function isNumber(value) {
443
- return /^\-?\d*\.?\d*$/.test(value);
444
- }
445
- export function parseNumber(value) {
446
- return value
447
- .replace(/[^\-0-9.]/g, '') // all but digits, hyphens, and periods
448
- .replace(/(^-)|(-)/g, (_match, p1) => (p1 ? '-' : '')) // all but the first hyphen
449
- .replace(/(\..*)\./g, '$1'); // periods after a first one
450
- }
451
- export function isInteger(value) {
452
- return /^\-?\d*$/.test(value);
453
- }
454
- // If the string isn't already a valid url, prepends 'https://'
455
- export function parseUrl(value) {
456
- value = value.trim();
457
- const urlRegex = new RegExp('^(?:[a-z+]+:)?//', 'i');
458
- if (urlRegex.test(value))
459
- return value;
460
- else
461
- return 'https://' + value;
462
- }
463
- // Checks if this is a valid URL in a protocol agnostic way,
464
- // allows for protocol-relative absolute URLs (eg //example.com)
465
- export function isUrl(value) {
466
- const urlRegex = new RegExp('^(?:[-a-z+]+:)?//', 'i');
467
- return urlRegex.test(value);
468
- }
469
- export function parseZip(value) {
470
- value = value
471
- .replace(/[^0-9]/g, '')
472
- .replace(/(.{5})(.*)/, '$1-$2')
473
- .trim();
474
- // If the zip code has 6 characters, remove a hyphen
475
- if (value.length === 6)
476
- value = value.replace(/-/, '');
477
- return value;
478
- }
479
- export function isZip(value) {
480
- const zipRegex = new RegExp(/^\d{5}(-\d{4})?$/);
481
- return zipRegex.test(value);
482
- }
483
- export function parsePostalCA(value) {
484
- value = value
485
- .toUpperCase()
486
- .replace(/[^A-Z0-9]/g, '')
487
- .replace(/(.{3})\s*(.*)/, '$1 $2')
488
- .trim();
489
- return value;
490
- }
491
- export function isPostalCA(value) {
492
- const postalRegex = new RegExp(/^[ABCEGHJKLMNPRSTVXY][0-9][ABCEGHJKLMNPRSTVWXYZ] ?[0-9][ABCEGHJKLMNPRSTVWXYZ][0-9]$/);
493
- return postalRegex.test(value);
494
- }
495
- // Checks if the value is a valid CSS color
496
- // Falls back to a regex if CSS.supports isn't available
497
- export function isColor(value) {
498
- if (['transparent', 'currentColor'].includes(value))
499
- return true;
500
- if (typeof value !== 'string' || !value.trim())
501
- return false;
502
- if (typeof CSS === 'object' && typeof CSS.supports === 'function') {
503
- return CSS.supports('color', value);
504
- }
505
- // If CSS.supports isn't available, use regexes to check for valid rgb or hsl color values
506
- // Not as comprehensive as the CSS.supports method, but should work in older browsers
507
- return isColorRegex(value);
508
- }
509
- function isColorRegex(value) {
510
- const rgbRegex = new RegExp(/^rgba?\(\s*(\d{1,3}%?,\s*){2}\d{1,3}%?\s*(?:,\s*(\.\d+|0+(\.\d+)?|1(\.0+)?|0|1\.0|\d{1,2}(\.\d*)?%|100%))?\s*\)$/);
511
- const hslRegex = new RegExp(/^hsla?\(\s*\d+(deg|grad|rad|turn)?,\s*\d{1,3}%,\s*\s*\d{1,3}%(?:,\s*(\.\d+|0+(\.\d+)?|1(\.0+)?|0|1\.0|\d{1,2}(\.\d*)?%|100%))?\s*\)$/);
512
- // Support for the newer space-separated syntax
513
- const rgbSpaceRegex = new RegExp(/^rgba?\(\s*(\d{1,3}%?\s+){2}\d{1,3}%?\s*(?:\s*\/\s*(\.\d+|0+(\.\d+)?|1(\.0+)?|0|1\.0|\d{1,2}(\.\d*)?%|100%))?\s*\)$/);
514
- const hslSpaceRegex = new RegExp(/^hsla?\(\s*\d+(deg|grad|rad|turn)?\s+\d{1,3}%\s+\s*\d{1,3}%(?:\s*\/\s*(\.\d+|0+(\.\d+)?|1(\.0+)?|0|1\.0|\d{1,2}(\.\d*)?%|100%))?\s*\)$/);
515
- // Hex color regex (short and long formats with and without alpha)
516
- const hexRegex = new RegExp(/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i);
517
- let colors = `aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|rebeccapurple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen`;
518
- const colorNameRegex = new RegExp(`^(${colors})$`, 'i');
519
- return (rgbRegex.test(value) ||
520
- hslRegex.test(value) ||
521
- rgbSpaceRegex.test(value) ||
522
- hslSpaceRegex.test(value) ||
523
- hexRegex.test(value) ||
524
- colorNameRegex.test(value));
525
- }
526
- // Used to convert color names to hex values
527
- let colorCanvas = null;
528
- // Cache of color names to hex values to reduce reads to canvas
529
- const colorCache = new Map();
530
- // Uses a Canvas element to convert a color name to a hex value
531
- export function parseColor(value) {
532
- value = value.trim().toLowerCase();
533
- if (['transparent', 'currentcolor'].includes(value))
534
- return value;
535
- if (colorCache.has(value))
536
- return colorCache.get(value);
537
- if (colorCanvas === null) {
538
- colorCanvas = document.createElement('canvas');
539
- colorCanvas.willReadFrequently = true;
540
- }
541
- let ctx = colorCanvas.getContext('2d');
542
- if (!ctx)
543
- throw new Error("Can't get context from colorCanvas");
544
- ctx.fillStyle = value;
545
- ctx.fillRect(0, 0, 1, 1);
546
- let d = ctx.getImageData(0, 0, 1, 1).data;
547
- let hex = '#' + ('000000' + ((d[0] << 16) | (d[1] << 8) | d[2]).toString(16)).slice(-6);
548
- colorCache.set(value, hex);
549
- return hex;
550
- }
551
- // Homogenizes the return of a custom validation function to a ValidationResult
552
- // that has a boolean valid property and messages array of strings
553
- export function normalizeValidationResult(res) {
554
- let result = { valid: false, error: false, messages: [] };
555
- if (typeof res === 'boolean')
556
- return { valid: res, error: false, messages: [] };
557
- if (typeof res === 'string')
558
- return { valid: false, error: false, messages: [res] };
559
- if (typeof res.valid === 'boolean')
560
- result.valid = res.valid;
561
- if (typeof res.message === 'string')
562
- result.messages = [res.message];
563
- if (typeof res.messages === 'string')
564
- result.messages = [res.messages];
565
- if (Array.isArray(res.messages))
566
- result.messages = res.messages;
567
- if (res.error === true)
568
- result.error = true;
569
- return result;
570
- }
1
+ (function(i,f){typeof exports=="object"&&typeof module<"u"?f(exports):typeof define=="function"&&define.amd?define(["exports"],f):(i=typeof globalThis<"u"?globalThis:i||self,f(i.validatorUtils={}))})(this,function(i){"use strict";function f(e){return e instanceof HTMLInputElement||e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement}function R(e,n){typeof n=="string"&&(n=[n]);const t=e.dataset.type||"",r=e.type;return!!(n.includes(t)||n.includes(r))}function T(e){return e.replace(/YYYY/g,"Y").replace(/YY/g,"y").replace(/MMMM/g,"F").replace(/MMM/g,"{3}").replace(/MM/g,"{2}").replace(/M/g,"n").replace(/DD/g,"{5}").replace(/D/g,"j").replace(/dddd/g,"l").replace(/ddd/g,"D").replace(/dd/g,"D").replace(/d/g,"w").replace(/HH/g,"{6}").replace(/H/g,"G").replace(/hh/g,"h").replace(/mm/g,"i").replace(/m/g,"i").replace(/ss/g,"S").replace(/s/g,"s").replace(/A/gi,"K").replace(/\{3\}/g,"M").replace(/\{2\}/g,"m").replace(/\{5\}/g,"d").replace(/\{6\}/g,"H")}function M(e){const n=parseInt(e);if(typeof e=="number"||!isNaN(n))return n-1;const t=new Date(`1 ${e} 2000`).getMonth();if(!isNaN(t))return t;const r={ja:0,en:0,fe:1,fé:1,ap:3,ab:3,av:3,mai:4,juin:5,juil:6,au:7,ag:7,ao:7,se:8,o:9,n:10,d:11};for(const l in r)if(e.toLowerCase().startsWith(l))return r[l];throw new Error("Invalid month name: "+e)}function y(e){return typeof e=="string"&&(e=parseInt(e.replace(/\D/g,""))),e>99?e:e<(new Date().getFullYear()+20)%100?e+2e3:e+1900}function m(e){if(e instanceof Date)return e;e=e.trim().toLowerCase();let n=0,t=0,r=0,l=0,s=0,a=0;const d=new RegExp(/\d{1,2}\:\d\d(?:\:\d\ds?)?\s?(?:[a|p]m?)?/gi);if(d.test(e)){const c=e.match(d)[0];e=e.replace(c,"").trim();const g=p(c);if(g!==null&&({hour:l,minute:s,second:a}=g),e.length<=2){const D=new Date;return new Date(D.getFullYear(),D.getMonth(),D.getDate(),l,s,a)}}const u=/(^|\b)(mo|tu|we|th|fr|sa|su|lu|mard|mer|jeu|ve|dom)[\w]*\.?/gi;e=e.replace(u,"").trim();const o=new Date(new Date().setHours(0,0,0,0));if(/(now|today)/.test(e))return o;if(e.includes("tomorrow"))return new Date(o.setDate(o.getDate()+1));e.length===8&&(e=e.replace(/(\d\d\d\d)(\d\d)(\d\d)/,"$1-$2-$3")),e.length===6&&(e=e.replace(/(\d\d)(\d\d)(\d\d)/,y(e.slice(0,2))+"-$2-$3"));try{({year:n,month:t,day:r}=S(e))}catch{return new Date("")}return new Date(n,t-1,r,l,s,a)}function N(e,n=[null,null,null]){const t=r=>r.filter(l=>!n.includes(l));return e===0||e>31?t(["y"]):e>12?t(["d","y"]):e>=1&&e<=12?t(["m","d","y"]):[]}function S(e){const n=e.split(/[\s-/:.,]+/).filter(s=>s!=="");if(n.length<3){if(e.match(/\d{4}/)!==null)throw new Error("Invalid Date");n.unshift(String(new Date().getFullYear()))}const t={year:0,month:0,day:0};function r(s,a){s==="year"?t.year=y(a):t[s]=a}let l=0;for(;!(t.year&&t.month&&t.day);){e:for(const s of n){if(l++,/^[a-zA-Zé]+$/.test(s)){t.month||r("month",M(s)+1);continue}if(/^'\d\d$/.test(s)||/^\d{3,5}$/.test(s)){t.year||r("year",parseInt(s.replace(/'/,"")));continue}const a=parseInt(s);if(isNaN(a))throw console.error(`not date because ${s} isNaN`),new Error("Invalid Date");const d=N(a,[t.year?"y":null,t.month?"m":null,t.day?"d":null]);if(d.length==1){if(d[0]==="m"&&!t.month){r("month",a);continue e}if(d[0]==="d"&&!t.day){r("day",a);continue e}if(d[0]==="y"&&!t.year){r("year",a);continue e}}l>3&&(!t.month&&d.includes("m")?r("month",a):!t.day&&d.includes("d")&&r("day",a))}if(l>6)throw new Error("Invalid Date")}if(t.year&&t.month&&t.day)return t;throw new Error("Invalid Date")}function p(e){if(e=e.trim().toLowerCase(),e==="now"){const o=new Date;return{hour:o.getHours(),minute:o.getMinutes(),second:o.getSeconds()}}const n=e.match(/(\d{3,4})/);if(n){const o=n[1].length,c=n[1].slice(0,o==3?1:2),g=n[1].slice(-2);e=e.replace(n[1],c+":"+g)}const t=new RegExp(/^(\d{1,2})(?::(\d{1,2}))?\s*(?:(a|p)m?)?$/i);if(t.test(e)){const o=e.match(t);if(o===null)return null;e=o[1]+":"+(o[2]||"00")+(o[3]||"")}const r=new RegExp(/^(\d{1,2}):(\d{1,2})(?::(\d{1,2}))?\s*(?:(a|p)m?)?$/i);if(!r.test(e))return null;const l=e.match(r);if(l===null)return null;const s=parseInt(l[1]),a=parseInt(l[2]),d=l[3]?parseInt(l[3]):0,u=l[4];return isNaN(s)||isNaN(a)||isNaN(d)?null:u==="p"&&s<12?{hour:s+12,minute:a,second:d}:u==="a"&&s===12?{hour:0,minute:a,second:d}:s<0||s>23||a<0||a>59||d<0||d>59?null:{hour:s,minute:a,second:d}}function k(e,n="h:mm A"){const t=p(e);if(t){const r=new Date;return r.setHours(t.hour),r.setMinutes(t.minute),r.setSeconds(t.second),r.setMilliseconds(0),w(r,n)}return""}function w(e,n="YYYY-MM-DD"){if(e=m(e),isNaN(e.getTime()))return"";const t={y:e.getFullYear(),M:e.getMonth(),D:e.getDate(),W:e.getDay(),H:e.getHours(),m:e.getMinutes(),s:e.getSeconds(),ms:e.getMilliseconds()},r=(o,c=2)=>(o+"").padStart(c,"0"),l=()=>t.H%12||12,s=o=>o<12?"AM":"PM",a=o=>"January|February|March|April|May|June|July|August|September|October|November|December".split("|")[o];function d(o,c=0){const g="Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday".split("|");return c?g[o].slice(0,c):g[o]}const u={YY:String(t.y).slice(-2),YYYY:t.y,M:t.M+1,MM:r(t.M+1),MMMM:a(t.M),MMM:a(t.M).slice(0,3),D:String(t.D),DD:r(t.D),d:String(t.W),dd:d(t.W,2),ddd:d(t.W,3),dddd:d(t.W),H:String(t.H),HH:r(t.H),h:l(),hh:r(l()),A:s(t.H),a:s(t.H).toLowerCase(),m:String(t.m),mm:r(t.m),s:String(t.s),ss:r(t.s),SSS:r(t.ms,3)};return n.replace(/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,(o,c)=>c||u[o])}function $(e,n){const t=m(e);return isNaN(t.getTime())?"":((!n||n.length===0)&&(n="YYYY-MMM-DD"),w(t,n))}function x(e){if(typeof e!="string"&&!(e instanceof Date))return!1;let n=m(e);return n==null?!1:!isNaN(n.getTime())}function A(e,n){return!(n==="past"&&e>new Date||n==="future"&&e.getTime()<new Date().setHours(0,0,0,0))}function E(e){let n=p(e);return n===null?!1:!isNaN(n.hour)&&!isNaN(n.minute)&&!isNaN(n.second)}function C(e){if(e.length>255||!new RegExp(/^.+@.+\.[a-zA-Z0-9]{2,}$/).test(e))return!1;let t="";return t+="^([a-zA-Z0-9!#$%'*+/=?^_`{|}~-]+",t+="(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*",t+="|",t+='"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*"',t+=")@(",t+="(",t+="(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\\.)+",t+="[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?",t+=")",t+=")$",new RegExp(t).test(e)}function Y(e){return e=e.replace(/^[^2-90]+/g,""),e=e.replace(/(\d\d\d).*?(\d\d\d).*?(\d\d\d\d)(.*)/,"$1-$2-$3$4"),e}function H(e){return/^\d\d\d-\d\d\d-\d\d\d\d$/.test(e)}function P(e){return e.replace(/[^0-9]/g,"")}function I(e){return/^\-?\d*\.?\d*$/.test(e)}function Z(e){return e.replace(/[^\-0-9.]/g,"").replace(/(^-)|(-)/g,(n,t)=>t?"-":"").replace(/(\..*)\./g,"$1")}function z(e){return/^\-?\d*$/.test(e)}function F(e){return e=e.trim(),new RegExp("^(?:[a-z+]+:)?//","i").test(e)?e:"https://"+e}function L(e){return new RegExp("^(?:[-a-z+]+:)?//","i").test(e)}function j(e){return e=e.replace(/[^0-9]/g,"").replace(/(.{5})(.*)/,"$1-$2").trim(),e.length===6&&(e=e.replace(/-/,"")),e}function q(e){return new RegExp(/^\d{5}(-\d{4})?$/).test(e)}function W(e){return e=e.toUpperCase().replace(/[^A-Z0-9]/g,"").replace(/(.{3})\s*(.*)/,"$1 $2").trim(),e}function J(e){return new RegExp(/^[ABCEGHJKLMNPRSTVXY][0-9][ABCEGHJKLMNPRSTVWXYZ] ?[0-9][ABCEGHJKLMNPRSTVWXYZ][0-9]$/).test(e)}function U(e){return["transparent","currentColor"].includes(e)?!0:typeof e!="string"||!e.trim()?!1:typeof CSS=="object"&&typeof CSS.supports=="function"?CSS.supports("color",e):V(e)}function V(e){const n=new RegExp(/^rgba?\(\s*(\d{1,3}%?,\s*){2}\d{1,3}%?\s*(?:,\s*(\.\d+|0+(\.\d+)?|1(\.0+)?|0|1\.0|\d{1,2}(\.\d*)?%|100%))?\s*\)$/),t=new RegExp(/^hsla?\(\s*\d+(deg|grad|rad|turn)?,\s*\d{1,3}%,\s*\s*\d{1,3}%(?:,\s*(\.\d+|0+(\.\d+)?|1(\.0+)?|0|1\.0|\d{1,2}(\.\d*)?%|100%))?\s*\)$/),r=new RegExp(/^rgba?\(\s*(\d{1,3}%?\s+){2}\d{1,3}%?\s*(?:\s*\/\s*(\.\d+|0+(\.\d+)?|1(\.0+)?|0|1\.0|\d{1,2}(\.\d*)?%|100%))?\s*\)$/),l=new RegExp(/^hsla?\(\s*\d+(deg|grad|rad|turn)?\s+\d{1,3}%\s+\s*\d{1,3}%(?:\s*\/\s*(\.\d+|0+(\.\d+)?|1(\.0+)?|0|1\.0|\d{1,2}(\.\d*)?%|100%))?\s*\)$/),s=new RegExp(/^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i);let a="aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|rebeccapurple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen";const d=new RegExp(`^(${a})$`,"i");return n.test(e)||t.test(e)||r.test(e)||l.test(e)||s.test(e)||d.test(e)}let h=null;const b=new Map;function G(e){if(e=e.trim().toLowerCase(),["transparent","currentcolor"].includes(e))return e;if(b.has(e))return b.get(e);h===null&&(h=document.createElement("canvas"),h.willReadFrequently=!0);let n=h.getContext("2d");if(!n)throw new Error("Can't get context from colorCanvas");n.fillStyle=e,n.fillRect(0,0,1,1);let t=n.getImageData(0,0,1,1).data,r="#"+("000000"+(t[0]<<16|t[1]<<8|t[2]).toString(16)).slice(-6);return b.set(e,r),r}function K(e){let n={valid:!1,error:!1,messages:[]};return typeof e=="boolean"?{valid:e,error:!1,messages:[]}:typeof e=="string"?{valid:!1,error:!1,messages:[e]}:(typeof e.valid=="boolean"&&(n.valid=e.valid),typeof e.message=="string"&&(n.messages=[e.message]),typeof e.messages=="string"&&(n.messages=[e.messages]),Array.isArray(e.messages)&&(n.messages=e.messages),e.error===!0&&(n.error=!0),n)}i.formatDateTime=w,i.isColor=U,i.isDate=x,i.isDateInRange=A,i.isEmail=C,i.isFormControl=f,i.isInteger=z,i.isNANPTel=H,i.isNumber=I,i.isPostalCA=J,i.isTime=E,i.isType=R,i.isUrl=L,i.isZip=q,i.momentToFPFormat=T,i.monthToNumber=M,i.normalizeValidationResult=K,i.parseColor=G,i.parseDate=m,i.parseDateToString=$,i.parseInteger=P,i.parseNANPTel=Y,i.parseNumber=Z,i.parsePostalCA=W,i.parseTime=p,i.parseTimeToString=k,i.parseUrl=F,i.parseZip=j,i.yearToFull=y,Object.defineProperty(i,Symbol.toStringTag,{value:"Module"})});
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@jdlien/validator-utils",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "type": "module",
5
+ "main": "dist/validator-utils.js",
5
6
  "module": "dist/validator-utils.js",
6
7
  "types": "dist/validator-utils.d.ts",
7
8
  "files": [
@@ -10,7 +11,7 @@
10
11
  "description": "Validation and sanitization functions used by @jdlien/Validator.",
11
12
  "scripts": {
12
13
  "dev": "vite",
13
- "build": "vite build && tsc --declaration",
14
+ "build": "vite build && tsc --emitDeclarationOnly",
14
15
  "preview": "vite preview",
15
16
  "test": "vitest",
16
17
  "coverage": "vitest --coverage"