@lemonadejs/calendar 5.0.0 → 5.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,10 +1,3 @@
1
- /**
2
- * render: ()
3
- * valid-ranges: []
4
- * disabled
5
- * dateToNum UTC
6
- * navigation with icons Enter key
7
- */
8
1
  if (! lemonade && typeof (require) === 'function') {
9
2
  var lemonade = require('lemonadejs');
10
3
  }
@@ -19,130 +12,2873 @@ if (! Modal && typeof (require) === 'function') {
19
12
  global.Calendar = factory();
20
13
  }(this, (function () {
21
14
 
15
+ class CustomEvents extends Event {
16
+ constructor(type, props, options) {
17
+ super(type, {
18
+ bubbles: true,
19
+ composed: true,
20
+ ...options,
21
+ });
22
+
23
+ if (props) {
24
+ for (const key in props) {
25
+ // Avoid assigning if property already exists anywhere on `this`
26
+ if (! (key in this)) {
27
+ this[key] = props[key];
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+
34
+ // Dispatcher
35
+ const Dispatch = function(method, type, options) {
36
+ // Try calling the method directly if provided
37
+ if (typeof method === 'function') {
38
+ let a = Object.values(options);
39
+ return method(...a);
40
+ } else if (this.tagName) {
41
+ this.dispatchEvent(new CustomEvents(type, options));
42
+ }
43
+ }
44
+
45
+ // Translations
46
+ const T = function(t) {
47
+ if (typeof(document) !== "undefined" && document.dictionary) {
48
+ return document.dictionary[t] || t;
49
+ } else {
50
+ return t;
51
+ }
52
+ }
53
+
22
54
  const Helpers = (function() {
23
55
  const component = {};
24
56
 
25
- component.weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
26
- component.months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
57
+ // Excel like dates
58
+ const excelInitialTime = Date.UTC(1900, 0, 0);
59
+ const excelLeapYearBug = Date.UTC(1900, 1, 29);
60
+ const millisecondsPerDay = 86400000;
61
+
62
+ // Transform in two digits
63
+ component.two = function(value) {
64
+ value = '' + value;
65
+ if (value.length === 1) {
66
+ value = '0' + value;
67
+ }
68
+ return value;
69
+ }
70
+
71
+ component.isValidDate = function(d) {
72
+ return d instanceof Date && !isNaN(d.getTime());
73
+ }
74
+
75
+ component.isValidDateFormat = function(date, format) {
76
+ if (typeof date === 'string') {
77
+ // Check format: YYYY-MM-DD using regex
78
+ const match = date.match(/^(\d{4})-(\d{2})-(\d{2})$/);
79
+ if (match) {
80
+ const year = Number(match[1]);
81
+ const month = Number(match[2]) - 1;
82
+ const day = Number(match[3]);
83
+ const parsed = new Date(Date.UTC(year, month, day));
84
+ // Return
85
+ return parsed.getUTCFullYear() === year && parsed.getUTCMonth() === month && parsed.getUTCDate() === day;
86
+ }
87
+ }
88
+
89
+ return false;
90
+ }
91
+
92
+ component.toString = function (date, dateOnly) {
93
+ let y = null;
94
+ let m = null;
95
+ let d = null;
96
+ let h = null;
97
+ let i = null;
98
+ let s = null;
99
+
100
+ if (Array.isArray(date)) {
101
+ y = date[0];
102
+ m = date[1];
103
+ d = date[2];
104
+ h = date[3];
105
+ i = date[4];
106
+ s = date[5];
107
+ } else {
108
+ if (! date) {
109
+ date = new Date();
110
+ }
111
+ y = date.getUTCFullYear();
112
+ m = date.getUTCMonth() + 1;
113
+ d = date.getUTCDate();
114
+ h = date.getUTCHours();
115
+ i = date.getUTCMinutes();
116
+ s = date.getUTCSeconds();
117
+ }
118
+
119
+ if (dateOnly === true) {
120
+ return component.two(y) + '-' + component.two(m) + '-' + component.two(d);
121
+ } else {
122
+ return component.two(y) + '-' + component.two(m) + '-' + component.two(d) + ' ' + component.two(h) + ':' + component.two(i) + ':' + component.two(s);
123
+ }
124
+ }
125
+
126
+ component.toArray = function (value) {
127
+ let date = value.split(((value.indexOf('T') !== -1) ? 'T' : ' '));
128
+ let time = date[1];
129
+
130
+ date = date[0].split('-');
131
+ let y = parseInt(date[0]);
132
+ let m = parseInt(date[1]);
133
+ let d = parseInt(date[2]);
134
+ let h = 0;
135
+ let i = 0;
136
+
137
+ if (time) {
138
+ time = time.split(':');
139
+ h = parseInt(time[0]);
140
+ i = parseInt(time[1]);
141
+ }
142
+ return [y, m, d, h, i, 0];
143
+ }
144
+
145
+ component.arrayToStringDate = function(arr) {
146
+ return component.toString(arr, false);
147
+ }
148
+
149
+ component.dateToNum = function(jsDate) {
150
+ if (typeof(jsDate) === 'string') {
151
+ jsDate = new Date(jsDate + ' GMT+0');
152
+ }
153
+ let jsDateInMilliseconds = jsDate.getTime();
154
+ if (jsDateInMilliseconds >= excelLeapYearBug) {
155
+ jsDateInMilliseconds += millisecondsPerDay;
156
+ }
157
+ jsDateInMilliseconds -= excelInitialTime;
158
+
159
+ return jsDateInMilliseconds / millisecondsPerDay;
160
+ }
161
+
162
+ component.numToDate = function(excelSerialNumber, asArray) {
163
+ // allow 0; only bail on null/undefined/empty
164
+ if (excelSerialNumber === null || excelSerialNumber === undefined || excelSerialNumber === '') {
165
+ return '';
166
+ }
167
+
168
+ const MS_PER_DAY = 86_400_000;
169
+ const SEC_PER_DAY = 86_400;
170
+
171
+ // Excel day 0 is 1899-12-31 (with the fake 1900-02-29 at serial 60)
172
+ const EXCEL_DAY0_UTC_MS = Date.UTC(1899, 11, 31);
173
+
174
+ let wholeDays = Math.floor(excelSerialNumber);
175
+ let fractionalDay = excelSerialNumber - wholeDays;
176
+
177
+ // Fix the 1900 leap-year bug: shift serials >= 60 back one day
178
+ if (wholeDays >= 60) wholeDays -= 1;
179
+
180
+ // Build midnight UTC of the day
181
+ let ms = EXCEL_DAY0_UTC_MS + wholeDays * MS_PER_DAY;
182
+
183
+ // Add time part using integer seconds to avoid FP jitter
184
+ const seconds = Math.round(fractionalDay * SEC_PER_DAY);
185
+ ms += seconds * 1000;
186
+
187
+ const d = new Date(ms);
188
+
189
+ const arr = [
190
+ d.getUTCFullYear(),
191
+ component.two(d.getUTCMonth() + 1),
192
+ component.two(d.getUTCDate()),
193
+ component.two(d.getUTCHours()),
194
+ component.two(d.getUTCMinutes()),
195
+ component.two(d.getUTCSeconds()),
196
+ ];
197
+
198
+ return asArray ? arr : component.toString(arr, false);
199
+ }
200
+
201
+ component.prettify = function (d, texts) {
202
+ if (! texts) {
203
+ texts = {
204
+ justNow: 'Just now',
205
+ xMinutesAgo: '{0}m ago',
206
+ xHoursAgo: '{0}h ago',
207
+ xDaysAgo: '{0}d ago',
208
+ xWeeksAgo: '{0}w ago',
209
+ xMonthsAgo: '{0} mon ago',
210
+ xYearsAgo: '{0}y ago',
211
+ }
212
+ }
213
+
214
+ if (d.indexOf('GMT') === -1 && d.indexOf('Z') === -1) {
215
+ d += ' GMT';
216
+ }
217
+
218
+ let d1 = new Date();
219
+ let d2 = new Date(d);
220
+ let total = parseInt((d1 - d2) / 1000 / 60);
221
+
222
+ const format = (t, o) => {
223
+ return t.replace('{0}', o);
224
+ }
225
+
226
+ if (! total) {
227
+ return texts.justNow;
228
+ } else if (total < 90) {
229
+ return format(texts.xMinutesAgo, total);
230
+ } else if (total < 1440) { // One day
231
+ return format(texts.xHoursAgo, Math.round(total / 60));
232
+ } else if (total < 20160) { // 14 days
233
+ return format(texts.xDaysAgo, Math.round(total / 1440));
234
+ } else if (total < 43200) { // 30 days
235
+ return format(texts.xWeeksAgo, Math.round(total / 10080));
236
+ } else if (total < 1036800) { // 24 months
237
+ return format(texts.xMonthsAgo, Math.round(total / 43200));
238
+ } else { // 24 months+
239
+ return format(texts.xYearsAgo, Math.round(total / 525600));
240
+ }
241
+ }
242
+
243
+ component.prettifyAll = function () {
244
+ let elements = document.querySelectorAll('.prettydate');
245
+ for (let i = 0; i < elements.length; i++) {
246
+ if (elements[i].getAttribute('data-date')) {
247
+ elements[i].innerHTML = component.prettify(elements[i].getAttribute('data-date'));
248
+ } else {
249
+ if (elements[i].innerHTML) {
250
+ elements[i].setAttribute('title', elements[i].innerHTML);
251
+ elements[i].setAttribute('data-date', elements[i].innerHTML);
252
+ elements[i].innerHTML = component.prettify(elements[i].innerHTML);
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ // Compatibility with jSuites
259
+ component.now = component.toString;
260
+
261
+ const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
262
+ const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
263
+
264
+ const translate = function(t) {
265
+ if (typeof(document) !== "undefined" && document.dictionary) {
266
+ return document.dictionary[t] || t;
267
+ } else {
268
+ return t;
269
+ }
270
+ }
271
+
272
+ Object.defineProperty(component, 'weekdays', {
273
+ get: function () {
274
+ return weekdays.map(function(v) {
275
+ return translate(v);
276
+ });
277
+ },
278
+ });
279
+
280
+ Object.defineProperty(component, 'weekdaysShort', {
281
+ get: function () {
282
+ return weekdays.map(function(v) {
283
+ return translate(v).substring(0,3);
284
+ });
285
+ },
286
+ });
287
+
288
+ Object.defineProperty(component, 'months', {
289
+ get: function () {
290
+ return months.map(function(v) {
291
+ return translate(v);
292
+ });
293
+ },
294
+ });
295
+
296
+ Object.defineProperty(component, 'monthsShort', {
297
+ get: function () {
298
+ return months.map(function(v) {
299
+ return translate(v).substring(0,3);
300
+ });
301
+ },
302
+ });
303
+
304
+ return component;
305
+ })();
306
+
307
+ const Mask = (function Mask() {
308
+ // Currency
309
+ const tokens = {
310
+ // Text
311
+ text: [ '@', '&' ],
312
+ // Number
313
+ fraction: [ '#{0,1}.*?\\?+\\/[0-9?]+' ],
314
+ // Currency tokens
315
+ currency: [ '#(.{1})##0?(.{1}0+)?( ?;(.*)?)?' ],
316
+ // Scientific
317
+ scientific: [ '[0#]+([.,]{1}0*#*)?E{1}\\+0+' ],
318
+ // Percentage
319
+ percentage: [ '[0#]+([.,]{1}0*#*)?%' ],
320
+ // Number
321
+ numeric: [ '[0#]+([.,]{1}0*#*)?', '#+' ],
322
+ // Data tokens
323
+ datetime: [ 'YYYY', 'YYY', 'YY', 'MMMMM', 'MMMM', 'MMM', 'MM', 'DDDDD', 'DDDD', 'DDD', 'DD', 'DY', 'DAY', 'WD', 'D', 'Q', 'MONTH', 'MON', 'HH24', 'HH12', 'HH', '\\[H\\]', 'H', 'AM/PM', 'MI', 'SS', 'MS', 'S', 'Y', 'M', 'I' ],
324
+ // Other
325
+ general: [ 'A', '0', '\\?', '\\*', ',,M', ',,,B', '[0-9a-zA-Z\\$]+', '_\\(', '_\\)', '\\(', '\\)', '_-', '.']
326
+ }
327
+
328
+ // All expressions
329
+ const allExpressions = [].concat(tokens.fraction, tokens.currency, tokens.datetime, tokens.percentage, tokens.scientific, tokens.numeric, tokens.text, tokens.general).join('|');
330
+
331
+ // Pre-compile all regexes once at initialization for better performance
332
+ const compiledTokens = {};
333
+ const tokenPriority = ['fraction', 'currency', 'scientific', 'percentage', 'numeric', 'datetime', 'text', 'general'];
334
+
335
+ // Initialize compiled regexes
336
+ for (const type of tokenPriority) {
337
+ compiledTokens[type] = tokens[type].map(pattern => ({
338
+ regex: new RegExp('^' + pattern + '$', 'gi'),
339
+ method: pattern
340
+ }));
341
+ }
342
+
343
+ // Pre-compile regex for getTokens function
344
+ const allExpressionsRegex = new RegExp(allExpressions, 'gi');
345
+
346
+ // Pre-compile currency symbol regexes for autoCastingCurrency
347
+ const knownSymbols = ['$', '€', '£', '¥', '₹', '₽', '₩', '₫', 'R$', 'CHF', 'AED'];
348
+ const currencyRegexes = knownSymbols.map(s => ({
349
+ symbol: s,
350
+ regex: new RegExp(`^${s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')}(\\s?)`)
351
+ }));
352
+
353
+ const hiddenCaret = "\u200B";
354
+
355
+ // Labels
356
+ const weekDaysFull = Helpers.weekdays;
357
+ const weekDays = Helpers.weekdaysShort;
358
+ const monthsFull = Helpers.months;
359
+ const months = Helpers.monthsShort;
360
+
361
+ // Helpers
362
+
363
+ const focus = function(el) {
364
+ if (el.textContent.length) {
365
+ // Handle contenteditable elements
366
+ const range = document.createRange();
367
+ const sel = window.getSelection();
368
+
369
+ let node = el;
370
+ // Go as deep as possible to the last text node
371
+ while (node.lastChild) node = node.lastChild;
372
+ // Ensure it's a text node
373
+ if (node.nodeType === Node.TEXT_NODE) {
374
+ range.setStart(node, node.length);
375
+ } else {
376
+ range.setStart(node, node.childNodes.length);
377
+ }
378
+ range.collapse(true);
379
+ sel.removeAllRanges();
380
+ sel.addRange(range);
381
+
382
+ el.scrollLeft = el.scrollWidth;
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Returns if the given value is considered blank
388
+ */
389
+ const isBlank = function(v) {
390
+ return v === null || v === '' || typeof(v) === 'undefined';
391
+ }
392
+
393
+ /**
394
+ * Receives a string from a method type and returns if it's a numeric method
395
+ */
396
+ const isNumeric = function(t) {
397
+ return t === 'currency' || t === 'percentage' || t === '' || t === 'numeric';
398
+ }
399
+
400
+ const adjustPrecision = function(num) {
401
+ if (typeof num === 'number' && ! Number.isInteger(num)) {
402
+ const v = num.toString().split('.');
403
+
404
+ if (v[1] && v[1].length > 10) {
405
+ let t0 = 0;
406
+ const t1 = v[1][v[1].length - 2];
407
+
408
+ if (t1 == 0 || t1 == 9) {
409
+ for (let i = v[1].length - 2; i > 0; i--) {
410
+ if (t0 >= 0 && v[1][i] == t1) {
411
+ t0++;
412
+ if (t0 > 6) {
413
+ break;
414
+ }
415
+ } else {
416
+ t0 = 0;
417
+ break;
418
+ }
419
+ }
420
+
421
+ if (t0) {
422
+ return parseFloat(parseFloat(num).toFixed(v[1].length - 1));
423
+ }
424
+ }
425
+ }
426
+ }
427
+
428
+ return num;
429
+ }
430
+
431
+ /**
432
+ * Get the decimal defined in the mask configuration
433
+ */
434
+ const getDecimal = function(v) {
435
+ let decimal;
436
+ if (this.decimal) {
437
+ decimal = this.decimal;
438
+ } else {
439
+ if (this.locale) {
440
+ let t = Intl.NumberFormat(this.locale).format(1.1);
441
+ decimal = t[1];
442
+ } else {
443
+ if (! v) {
444
+ v = this.mask;
445
+ }
446
+
447
+ // Fixed regex: 0* means zero or more 0s before decimal separator
448
+ let e = new RegExp('0*([,.])0+', 'ig');
449
+ let t = e.exec(v);
450
+ if (t && t[1] && t[1].length === 1) {
451
+ decimal = t[1];
452
+ } else {
453
+ // Try the second pattern for # formats
454
+ e = new RegExp('#{1}(.{1})#+', 'ig');
455
+ t = e.exec(v);
456
+ if (t && t[1] && t[1].length === 1) {
457
+ if (t[1] === ',') {
458
+ decimal = '.';
459
+ } else if (t[1] === "'" || t[1] === '.') {
460
+ decimal = ',';
461
+ }
462
+ }
463
+ }
464
+
465
+ if (! decimal) {
466
+ decimal = '1.1'.toLocaleString().substring(1, 2);
467
+ }
468
+ }
469
+ }
470
+
471
+ if (decimal) {
472
+ return decimal;
473
+ } else {
474
+ return null;
475
+ }
476
+ }
477
+
478
+ /**
479
+ * Caret position getter
480
+ * `this` in this function should be the element with a caret
481
+ */
482
+ const getCaretPosition = function(editableDiv) {
483
+ let caretPos = 0;
484
+ let sel = window.getSelection();
485
+ if (sel && sel.rangeCount > 0) {
486
+ let range = sel.getRangeAt(0);
487
+ let preRange = range.cloneRange();
488
+ preRange.selectNodeContents(editableDiv);
489
+ preRange.setEnd(range.endContainer, range.endOffset);
490
+ caretPos = preRange.toString().length;
491
+ }
492
+ return caretPos;
493
+ }
494
+
495
+ /**
496
+ * Caret position getter
497
+ * `this` in this function should be the element with a caret
498
+ */
499
+ const getCaret = function(el) {
500
+ if (el.tagName === 'DIV') {
501
+ return getCaretPosition(el);
502
+ } else {
503
+ return el.selectionStart;
504
+ }
505
+ }
506
+
507
+ /**
508
+ * Caret position setter
509
+ * `this` should be the element (input/textarea or contenteditable div)
510
+ */
511
+ const setCaret = function(index) {
512
+ if (typeof index !== 'number') index = Number(index) || 0;
513
+
514
+ if (this.tagName !== 'DIV' || this.isContentEditable !== true) {
515
+ const n = this.value ?? '';
516
+ if (index < 0) index = 0;
517
+ if (index > n.length) index = n.length;
518
+ this.focus();
519
+ this.selectionStart = index;
520
+ this.selectionEnd = index;
521
+ return;
522
+ }
523
+
524
+ // Contenteditable DIV
525
+ const el = /** @type {HTMLElement} */ (this);
526
+ const totalLen = (el.textContent || '').length;
527
+
528
+ if (index < 0) index = 0;
529
+ if (index > totalLen) index = totalLen;
530
+
531
+ const sel = window.getSelection();
532
+ if (!sel) return;
533
+
534
+ const range = document.createRange();
535
+ el.focus();
536
+
537
+ // Empty element → ensure a text node to place the caret into
538
+ if (totalLen === 0) {
539
+ if (!el.firstChild) el.appendChild(document.createTextNode(''));
540
+ // place at start
541
+ range.setStart(el.firstChild, 0);
542
+ range.collapse(true);
543
+ sel.removeAllRanges();
544
+ sel.addRange(range);
545
+ return;
546
+ }
547
+
548
+ // If caret is at the very end, this is fastest/cleanest
549
+ if (index === totalLen) {
550
+ range.selectNodeContents(el);
551
+ range.collapse(false);
552
+ sel.removeAllRanges();
553
+ sel.addRange(range);
554
+ return;
555
+ }
556
+
557
+ // Walk text nodes to find the node that contains the index-th character
558
+ const walker = document.createTreeWalker(
559
+ el,
560
+ NodeFilter.SHOW_TEXT,
561
+ {
562
+ acceptNode(node) {
563
+ // skip empty/whitespace-only nodes if you want; or just accept all text
564
+ return node.nodeValue ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
565
+ }
566
+ }
567
+ );
568
+
569
+ let pos = 0;
570
+ let node = walker.nextNode();
571
+ while (node) {
572
+ const nextPos = pos + node.nodeValue.length;
573
+ if (index <= nextPos) {
574
+ const offset = index - pos; // char offset within this text node
575
+ range.setStart(node, offset);
576
+ range.collapse(true);
577
+ sel.removeAllRanges();
578
+ sel.addRange(range);
579
+ return;
580
+ }
581
+ pos = nextPos;
582
+ node = walker.nextNode();
583
+ }
584
+
585
+ // Fallback: collapse at end if something unexpected happened
586
+ range.selectNodeContents(el);
587
+ range.collapse(false);
588
+ sel.removeAllRanges();
589
+ sel.addRange(range);
590
+ };
591
+
592
+ /**
593
+ * Methods to deal with different types of data
594
+ */
595
+ const parseMethods = {
596
+ 'FIND': function(v, a) {
597
+ if (isBlank(this.values[this.index])) {
598
+ this.values[this.index] = '';
599
+ }
600
+ // TODO: tratar eventos
601
+ if (this.event && this.event.inputType && this.event.inputType.indexOf('delete') > -1) {
602
+ this.values[this.index] += v;
603
+ return;
604
+ }
605
+ let pos = 0;
606
+ let count = 0;
607
+ let value = (this.values[this.index] + v).toLowerCase();
608
+ for (let i = 0; i < a.length; i++) {
609
+ if (a[i].toLowerCase().indexOf(value) === 0) {
610
+ pos = i;
611
+ count++;
612
+ }
613
+ }
614
+ if (count > 1) {
615
+ this.values[this.index] += v;
616
+ } else if (count === 1) {
617
+ // Jump a number of chars
618
+ let t = (a[pos].length - this.values[this.index].length) - 1;
619
+ this.position += t;
620
+ this.values[this.index] = a[pos];
621
+ this.index++;
622
+ return pos;
623
+ }
624
+ },
625
+ 'YEAR': function(v, s) {
626
+ if (isBlank(this.values[this.index])) {
627
+ this.values[this.index] = '';
628
+ }
629
+ if (parseInt(v) >= 0 && parseInt(v) <= 10) {
630
+ if (this.values[this.index].length < s) {
631
+ this.values[this.index] += v;
632
+ }
633
+ }
634
+ if (this.values[this.index].length === s) {
635
+ let y = new Date().getFullYear().toString();
636
+ if (s === 2) {
637
+ y = y.substring(0,2) + this.values[this.index];
638
+ } else if (s === 3) {
639
+ y = y.substring(0,1) + this.values[this.index];
640
+ } else if (s === 4) {
641
+ y = this.values[this.index];
642
+ }
643
+ this.date[0] = y;
644
+ this.index++;
645
+ }
646
+ },
647
+ 'YYYY': function(v) {
648
+ parseMethods.YEAR.call(this, v, 4);
649
+ },
650
+ 'YYY': function(v) {
651
+ parseMethods.YEAR.call(this, v, 3);
652
+ },
653
+ 'YY': function(v) {
654
+ parseMethods.YEAR.call(this, v, 2);
655
+ },
656
+ 'MMMMM': function(v) {
657
+ if (isBlank(this.values[this.index])) {
658
+ this.values[this.index] = '';
659
+ }
660
+ let value = (this.values[this.index] + v).toLowerCase();
661
+ for (var i = 0; i < monthsFull.length; i++) {
662
+ if (monthsFull[i][0].toLowerCase().indexOf(value) === 0) {
663
+ this.values[this.index] = monthsFull[i][0];
664
+ this.date[1] = i + 1;
665
+ this.index++;
666
+ break;
667
+ }
668
+ }
669
+ },
670
+ 'MMMM': function(v) {
671
+ let ret = parseMethods.FIND.call(this, v, monthsFull);
672
+ if (typeof(ret) !== 'undefined') {
673
+ this.date[1] = ret + 1;
674
+ }
675
+ },
676
+ 'MMM': function(v) {
677
+ let ret = parseMethods.FIND.call(this, v, months);
678
+ if (typeof(ret) !== 'undefined') {
679
+ this.date[1] = ret + 1;
680
+ }
681
+ },
682
+ 'MM': function(v, single) {
683
+ const commit = () => {
684
+ this.date[1] = this.values[this.index];
685
+ this.index++;
686
+ }
687
+
688
+ if (isBlank(this.values[this.index])) {
689
+ if (parseInt(v) > 1 && parseInt(v) < 10) {
690
+ if (! single) {
691
+ v = '0' + v;
692
+ }
693
+ this.values[this.index] = v;
694
+ commit();
695
+ } else if (parseInt(v) < 2) {
696
+ this.values[this.index] = v;
697
+ }
698
+ } else {
699
+ if (this.values[this.index] == 1 && parseInt(v) < 3) {
700
+ this.values[this.index] += v;
701
+ commit();
702
+ } else if (this.values[this.index] == 0 && parseInt(v) > 0 && parseInt(v) < 10) {
703
+ this.values[this.index] += v;
704
+ commit();
705
+ } else {
706
+ let test = parseInt(this.values[this.index]);
707
+ if (test > 0 && test <= 12) {
708
+ if (! single) {
709
+ test = '0' + test;
710
+ }
711
+ this.values[this.index] = test;
712
+ commit();
713
+ return false;
714
+ }
715
+ }
716
+ }
717
+ },
718
+ 'M': function(v) {
719
+ return parseMethods['MM'].call(this, v, true);
720
+ },
721
+ 'MONTH': function(v) {
722
+ return parseMethods['MMMM'].call(this, v);
723
+ },
724
+ 'MON': function(v) {
725
+ return parseMethods['MMM'].call(this, v);
726
+ },
727
+ 'DDDD': function(v) {
728
+ return parseMethods.FIND.call(this, v, weekDaysFull);
729
+ },
730
+ 'DDD': function(v) {
731
+ return parseMethods.FIND.call(this, v, weekDays);
732
+ },
733
+ 'DD': function(v, single) {
734
+ const commit = () => {
735
+ this.date[2] = this.values[this.index];
736
+ this.index++;
737
+ }
738
+
739
+ if (isBlank(this.values[this.index])) {
740
+ if (parseInt(v) > 3 && parseInt(v) < 10) {
741
+ if (! single) {
742
+ v = '0' + v;
743
+ }
744
+ this.values[this.index] = v;
745
+ commit();
746
+ } else if (parseInt(v) < 10) {
747
+ this.values[this.index] = v;
748
+ }
749
+ } else {
750
+ if (this.values[this.index] == 3 && parseInt(v) < 2) {
751
+ this.values[this.index] += v;
752
+ commit();
753
+ } else if ((this.values[this.index] == 1 || this.values[this.index] == 2) && parseInt(v) < 10) {
754
+ this.values[this.index] += v;
755
+ commit();
756
+ } else if (this.values[this.index] == 0 && parseInt(v) > 0 && parseInt(v) < 10) {
757
+ this.values[this.index] += v;
758
+ commit();
759
+ } else {
760
+ let test = parseInt(this.values[this.index]);
761
+ if (test > 0 && test <= 31) {
762
+ if (! single) {
763
+ test = '0' + test;
764
+ }
765
+ this.values[this.index] = test;
766
+ commit();
767
+ return false;
768
+ }
769
+ }
770
+ }
771
+ },
772
+ 'D': function(v) {
773
+ return parseMethods['DD'].call(this, v, true);
774
+ },
775
+ 'DY': function(v) {
776
+ return parseMethods['DDD'].call(this, v);
777
+ },
778
+ 'DAY': function(v) {
779
+ return parseMethods['DDDD'].call(this, v);
780
+ },
781
+ 'HH12': function(v, two) {
782
+ let test = false;
783
+ if (isBlank(this.values[this.index])) {
784
+ if (parseInt(v) > 1 && parseInt(v) < 10) {
785
+ if (two) {
786
+ v = 0 + v;
787
+ }
788
+ this.date[3] = this.values[this.index] = v;
789
+ this.index++;
790
+ } else if (parseInt(v) < 10) {
791
+ this.values[this.index] = v;
792
+ }
793
+ } else {
794
+ if (this.values[this.index] == 1 && parseInt(v) < 3) {
795
+ this.date[3] = this.values[this.index] += v;
796
+ this.index++;
797
+ } else if (this.values[this.index] < 1 && parseInt(v) < 10) {
798
+ this.date[3] = this.values[this.index] += v;
799
+ this.index++;
800
+ } else {
801
+ test = true;
802
+ }
803
+ }
804
+
805
+ // Re-test
806
+ if (test === true) {
807
+ var t = parseInt(this.values[this.index]);
808
+ if (t >= 0 && t <= 12) {
809
+ this.date[3] = this.values[this.index];
810
+ this.index++;
811
+ return false;
812
+ }
813
+ }
814
+ },
815
+ 'HH24': function(v, two) {
816
+ let test = false;
817
+ if (parseInt(v) >= 0 && parseInt(v) < 10) {
818
+ if (isBlank(this.values[this.index])) {
819
+ if (parseInt(v) > 2 && parseInt(v) < 10) {
820
+ if (two) {
821
+ v = 0 + v;
822
+ }
823
+ this.date[3] = this.values[this.index] = v;
824
+ this.index++;
825
+ } else if (parseInt(v) < 10) {
826
+ this.values[this.index] = v;
827
+ }
828
+ } else {
829
+ if (this.values[this.index] == 2 && parseInt(v) < 4) {
830
+ if (! two && this.values[this.index] === '0') {
831
+ this.values[this.index] = '';
832
+ }
833
+ this.date[3] = this.values[this.index] += v;
834
+ this.index++;
835
+ } else if (this.values[this.index] < 2 && parseInt(v) < 10) {
836
+ if (! two && this.values[this.index] === '0') {
837
+ this.values[this.index] = '';
838
+ }
839
+ this.date[3] = this.values[this.index] += v;
840
+ this.index++;
841
+ } else {
842
+ test = true;
843
+ }
844
+ }
845
+ } else {
846
+ test = true;
847
+ }
848
+
849
+ // Re-test
850
+ if (test === true) {
851
+ var t = parseInt(this.values[this.index]);
852
+ if (t >= 0 && t < 24) {
853
+ this.date[3] = this.values[this.index];
854
+ this.index++;
855
+ return false;
856
+ }
857
+ }
858
+ },
859
+ 'HH': function(v) {
860
+ parseMethods['HH24'].call(this, v, 1);
861
+ },
862
+ 'H': function(v) {
863
+ parseMethods['HH24'].call(this, v, 0);
864
+ },
865
+ '\\[H\\]': function(v) {
866
+ if (this.values[this.index] == undefined) {
867
+ this.values[this.index] = '';
868
+ }
869
+ if (v.match(/[0-9]/g)) {
870
+ this.date[3] = this.values[this.index] += v;
871
+ } else {
872
+ if (this.values[this.index].match(/[0-9]/g)) {
873
+ this.date[3] = this.values[this.index];
874
+ this.index++;
875
+ return false;
876
+ }
877
+ }
878
+ },
879
+ 'N60': function(v, i, two) {
880
+ let test = false;
881
+ if (parseInt(v) >= 0 && parseInt(v) < 10) {
882
+ if (isBlank(this.values[this.index])) {
883
+ if (parseInt(v) > 5 && parseInt(v) < 10) {
884
+ if (two) {
885
+ v = '0' + v;
886
+ }
887
+ this.date[i] = this.values[this.index] = v;
888
+ this.index++;
889
+ } else if (parseInt(v) < 10) {
890
+ this.values[this.index] = v;
891
+ }
892
+ } else {
893
+ if (this.values[this.index] < 6 && parseInt(v) < 10) {
894
+ if (! two && this.values[this.index] === '0') {
895
+ this.values[this.index] = '';
896
+ }
897
+ this.date[i] = this.values[this.index] += v;
898
+ this.index++;
899
+ } else {
900
+ test = true;
901
+ }
902
+ }
903
+ } else {
904
+ test = true;
905
+ }
906
+
907
+ // Re-test
908
+ if (test === true) {
909
+ var t = parseInt(this.values[this.index]);
910
+ if (t >= 0 && t < 60) {
911
+ this.date[i] = this.values[this.index];
912
+ this.index++;
913
+ return false;
914
+ }
915
+ }
916
+ },
917
+ 'MI': function(v) {
918
+ parseMethods.N60.call(this, v, 4, true);
919
+ },
920
+ 'SS': function(v) {
921
+ parseMethods.N60.call(this, v, 5, true);
922
+ },
923
+ 'I': function(v) {
924
+ parseMethods.N60.call(this, v, 4, false);
925
+ },
926
+ 'S': function(v) {
927
+ parseMethods.N60.call(this, v, 5, false);
928
+ },
929
+ 'AM/PM': function(v) {
930
+ if (typeof(this.values[this.index]) === 'undefined') {
931
+ this.values[this.index] = '';
932
+ }
933
+
934
+ if (this.values[this.index] === '') {
935
+ if (v.match(/a/i) && this.date[3] < 13) {
936
+ this.values[this.index] += 'A';
937
+ } else if (v.match(/p/i)) {
938
+ this.values[this.index] += 'P';
939
+ }
940
+ } else if (this.values[this.index] === 'A' || this.values[this.index] === 'P') {
941
+ this.values[this.index] += 'M';
942
+ this.index++;
943
+ }
944
+ },
945
+ 'WD': function(v) {
946
+ if (typeof(this.values[this.index]) === 'undefined') {
947
+ this.values[this.index] = '';
948
+ }
949
+ if (parseInt(v) >= 0 && parseInt(v) < 7) {
950
+ this.values[this.index] = v;
951
+ }
952
+ if (this.values[this.index].length == 1) {
953
+ this.index++;
954
+ }
955
+ },
956
+ // Numeric Methods
957
+ '[0#]+([.,]{1}0*#*)?': function(v) {
958
+ if (v === '.' && inputIsANumber(this.raw)) {
959
+ v = this.decimal;
960
+ }
961
+
962
+ if (isBlank(this.values[this.index])) {
963
+ this.values[this.index] = '';
964
+ }
965
+
966
+ if (v === '-') {
967
+ // Transform the number into negative if it is not already
968
+ if (this.values[this.index][0] != '-') {
969
+ this.values[this.index] = '-' + this.values[this.index];
970
+ }
971
+ } else if (v === '+') {
972
+ // Transform the number into positive if it is negative
973
+ if (this.values[this.index][0] == '-') {
974
+ this.values[this.index] = this.values[this.index].replace('-', '');
975
+ }
976
+ } else if (v == '0') {
977
+ // Only adds zero if there's a non-zero number before
978
+ if (this.values[this.index] != '0' && this.values[this.index] != '-0') {
979
+ this.values[this.index] += v;
980
+ }
981
+ } else if (v > 0 && v < 10) {
982
+ // Verify if there's a zero to remove it, avoiding left zeros
983
+ if (this.values[this.index] == '0' || this.values[this.index] == '-0') {
984
+ this.values[this.index] = this.values[this.index].replace('0', '');
985
+ }
986
+ this.values[this.index] += v;
987
+ } else if (v === this.decimal) {
988
+ // Only adds decimal when there's a number value on its left
989
+ if (! this.values[this.index].includes(this.decimal)) {
990
+ if (! this.values[this.index].replace('-', '').length) {
991
+ this.values[this.index] += '0';
992
+ }
993
+ this.values[this.index] += this.decimal;
994
+ }
995
+ }
996
+ },
997
+ '[0#]+([.,]{1}0*#*)?%': function(v) {
998
+ parseMethods['[0#]+([.,]{1}0*#*)?'].call(this, v);
999
+
1000
+ // Adds the % only if it has a number value
1001
+ if (this.values[this.index].match(/[\-0-9]/g)) {
1002
+ if (this.values[this.index].indexOf('%') !== -1) {
1003
+ this.values[this.index] = this.values[this.index].replaceAll('%', '');
1004
+ }
1005
+ this.values[this.index] += '%';
1006
+ } else {
1007
+ this.values[this.index] = '';
1008
+ }
1009
+ },
1010
+ '#(.{1})##0?(.{1}0+)?( ?;(.*)?)?': function(v) {
1011
+ // Process first the number
1012
+ parseMethods['[0#]+([.,]{1}0*#*)?'].call(this, v, true);
1013
+ // Create the separators
1014
+ let separator = this.tokens[this.index].substring(1,2);
1015
+
1016
+
1017
+ let currentValue = this.values[this.index];
1018
+ // Remove existing separators and negative sign
1019
+ currentValue = currentValue.replaceAll(separator, '');
1020
+ // Process separators
1021
+ let val = currentValue.split(this.decimal);
1022
+ if (val[0].length > 3) {
1023
+ let number = [];
1024
+ let count = 0;
1025
+ for (var j = val[0].length - 1; j >= 0 ; j--) {
1026
+ let c = val[0][j];
1027
+ if (c >= 0 && c <= 9) {
1028
+ if (count && ! (count % 3)) {
1029
+ number.unshift(separator);
1030
+ }
1031
+ count++;
1032
+ }
1033
+ number.unshift(c);
1034
+ }
1035
+ val[0] = number.join('');
1036
+ }
1037
+ // Reconstruct the value
1038
+ this.values[this.index] = val.join(this.decimal);
1039
+ },
1040
+ '[0#]+([.,]{1}0*#*)?E{1}\\+0+': function(v) {
1041
+ parseMethods['[0#]+([.,]{1}0*#*)?'].call(this, v);
1042
+ },
1043
+ '#{0,1}.*?\\?+\\/[0-9?]+': function (v) {
1044
+ if (isBlank(this.values[this.index])) {
1045
+ this.values[this.index] = '';
1046
+ }
1047
+
1048
+ const token = this.tokens[this.index]; // e.g. "# ?/?", "?/2", "# ??/16"
1049
+ let cur = this.values[this.index];
1050
+
1051
+ // Parse RHS of mask to decide denominator rule
1052
+ const rhsRaw = (token.split('/')[1] || '').replace(/\s+/g, '');
1053
+ const allowDen = /^\d+$/.test(rhsRaw) ? rhsRaw : /^\?+$/.test(rhsRaw) ? '?' : '?';
1054
+
1055
+ // ----- NEW: allow '-' as first char -----
1056
+ if (v === '-') {
1057
+ if (cur.length === 0) {
1058
+ this.values[this.index] = '-';
1059
+ }
1060
+ return; // never return false
1061
+ }
1062
+ // ----------------------------------------
1063
+
1064
+ // Only accept digits / space / slash; ignore everything else
1065
+ if (!(/[0-9\/ ]/.test(v))) {
1066
+ return;
1067
+ }
1068
+
1069
+ // If we already have a slash and denominator is fixed but not yet appended,
1070
+ // auto-complete immediately regardless of what the user typed now.
1071
+ const hasSlashNow = cur.includes('/');
1072
+ if (hasSlashNow && allowDen !== '?') {
1073
+ const afterSlash = cur.slice(cur.indexOf('/') + 1);
1074
+ if (afterSlash.length === 0) {
1075
+ this.values[this.index] = cur + allowDen;
1076
+ this.index++; // move to next token
1077
+ return;
1078
+ }
1079
+ }
1080
+
1081
+ // Empty -> only digits (or a leading '-' handled above)
1082
+ if (cur.length === 0) {
1083
+ if (/\d/.test(v)) this.values[this.index] = v;
1084
+ return;
1085
+ }
1086
+
1087
+ const hasSpace = cur.includes(' ');
1088
+ const hasSlash = cur.includes('/');
1089
+ const last = cur[cur.length - 1];
1090
+
1091
+ // Space rules: only one, must be before slash, must follow a digit
1092
+ if (v === ' ') {
1093
+ if (!hasSpace && !hasSlash && /\d/.test(last)) {
1094
+ this.values[this.index] = cur + ' ';
1095
+ }
1096
+ return;
1097
+ }
1098
+
1099
+ // Slash rules: only one slash, not right after a space, must follow a digit
1100
+ if (v === '/') {
1101
+ if (!hasSlash && last !== ' ' && /\d/.test(last)) {
1102
+ if (allowDen === '?') {
1103
+ this.values[this.index] = cur + '/';
1104
+ } else {
1105
+ this.values[this.index] = cur + '/' + allowDen;
1106
+ this.index++; // conclude this token
1107
+ }
1108
+ }
1109
+ return;
1110
+ }
1111
+
1112
+ // Digit rules
1113
+ if (/\d/.test(v)) {
1114
+ if (!hasSlash) {
1115
+ // Before slash: digits always fine
1116
+ this.values[this.index] = cur + v;
1117
+ return;
1118
+ }
1119
+
1120
+ // After slash
1121
+ if (allowDen === '?') {
1122
+ this.values[this.index] = cur + v;
1123
+ return;
1124
+ }
1125
+
1126
+ // Fixed denominator: enforce prefix and advance when complete
1127
+ const afterSlash = cur.slice(cur.indexOf('/') + 1);
1128
+ const nextDen = afterSlash + v;
1129
+ if (allowDen.startsWith(nextDen)) {
1130
+ this.values[this.index] = cur + v;
1131
+ if (nextDen.length === allowDen.length) {
1132
+ this.index++;
1133
+ }
1134
+ }
1135
+ }
1136
+ },
1137
+ '[0-9a-zA-Z\\$]+': function(v) {
1138
+ // Token to be added to the value
1139
+ let word = this.tokens[this.index];
1140
+ // Value
1141
+ if (typeof(this.values[this.index]) === 'undefined') {
1142
+ this.values[this.index] = '';
1143
+ }
1144
+ if (v === null) {
1145
+ let size = this.values[this.index].length;
1146
+ v = word.substring(size, size+1);
1147
+ }
1148
+ // Add the value
1149
+ this.values[this.index] += v;
1150
+ // Only if caret is before the change
1151
+ let current = this.values[this.index];
1152
+ // Add token to the values
1153
+ if (current !== word.substring(0,current.length)) {
1154
+ this.values[this.index] = word;
1155
+ // Next token to process
1156
+ this.index++;
1157
+ return false;
1158
+ } else if (current === word) {
1159
+ // Next token to process
1160
+ this.index++;
1161
+ }
1162
+ },
1163
+ 'A': function(v) {
1164
+ return parseMethods['[0-9a-zA-Z\\$]+'].call(this, v);
1165
+ },
1166
+ 'a': function(v) {
1167
+ return parseMethods['[0-9a-zA-Z\\$]+'].call(this, v);
1168
+ },
1169
+ '.': function(v) {
1170
+ return parseMethods['[0-9a-zA-Z\\$]+'].call(this, v);
1171
+ },
1172
+ '&': function(v) {
1173
+ if (v.match(/^[a-zA-Z ]+$/)) {
1174
+ this.values[this.index] = v;
1175
+ this.index++;
1176
+ }
1177
+ },
1178
+ '\\*': function() {
1179
+ this.values[this.index] = '';
1180
+ this.index++;
1181
+ return false;
1182
+ },
1183
+ 'C': function(v) {
1184
+ parseMethods['&'].call(this, v);
1185
+ },
1186
+ // General Methods
1187
+ '0': function(v) {
1188
+ if (v.match(/[0-9]/g)) {
1189
+ this.values[this.index] = v;
1190
+ this.index++;
1191
+ }
1192
+ },
1193
+ '9': function(v) {
1194
+ parseMethods['0'].call(this, v);
1195
+ },
1196
+ '#': function(v) {
1197
+ parseMethods['0'].call(this, v);
1198
+ },
1199
+ 'L': function(v) {
1200
+ if (v.match(/[a-zA-Z]/gi)) {
1201
+ this.values[this.index] = v;
1202
+ this.index++;
1203
+ }
1204
+ },
1205
+ '\\?': function(v) {
1206
+ if (v.match(/[1-9]/g)) {
1207
+ this.values[this.index] = v;
1208
+ this.index++;
1209
+ }
1210
+ },
1211
+ '@': function(v) {
1212
+ if (isBlank(this.values[this.index])) {
1213
+ this.values[this.index] = '';
1214
+ }
1215
+ this.values[this.index] += v;
1216
+ },
1217
+ '_\\(': function() {
1218
+ this.values[this.index] = ' ';
1219
+ this.index++;
1220
+ return false;
1221
+ },
1222
+ '_\\)': function() {
1223
+ this.values[this.index] = ' ';
1224
+ this.index++;
1225
+ return false;
1226
+ },
1227
+ '\\(': function() {
1228
+ if (this.type === 'currency' && this.parenthesisForNegativeNumbers) {
1229
+ this.values[this.index] = '';
1230
+ } else {
1231
+ this.values[this.index] = '(';
1232
+ }
1233
+ this.index++;
1234
+ return false;
1235
+ },
1236
+ '\\)': function() {
1237
+ if (this.type === 'currency' && this.parenthesisForNegativeNumbers) {
1238
+ this.values[this.index] = '';
1239
+ } else {
1240
+ this.values[this.index] = ')';
1241
+ }
1242
+ this.index++;
1243
+ return false;
1244
+ },
1245
+ '_-': function() {
1246
+ this.values[this.index] = ' ';
1247
+ this.index++;
1248
+ return false;
1249
+ },
1250
+ ',,M': function() {
1251
+ this.values[this.index] = 'M';
1252
+ this.index++;
1253
+ return false;
1254
+ },
1255
+ ',,,B': function() {
1256
+ this.values[this.index] = 'B';
1257
+ this.index++;
1258
+ return false;
1259
+ }
1260
+ }
1261
+
1262
+ const extractDate = function() {
1263
+ let v = '';
1264
+ if (! (this.date[0] && this.date[1] && this.date[2]) && (this.date[3] || this.date[4])) {
1265
+ if (this.mask.toLowerCase().indexOf('[h]') !== -1) {
1266
+ v = parseInt(this.date[3]);
1267
+ } else {
1268
+ let h = parseInt(this.date[3]);
1269
+ if (h < 13 && this.values.indexOf('PM') !== -1) {
1270
+ v = (h+12) % 24;
1271
+ } else {
1272
+ v = h % 24;
1273
+ }
1274
+ }
1275
+ if (this.date[4]) {
1276
+ v += parseFloat(this.date[4] / 60);
1277
+ }
1278
+ if (this.date[5]) {
1279
+ v += parseFloat(this.date[5] / 3600);
1280
+ }
1281
+ v /= 24;
1282
+ } else if (this.date[0] || this.date[1] || this.date[2] || this.date[3] || this.date[4] || this.date[5]) {
1283
+ if (this.date[0] && this.date[1] && ! this.date[2]) {
1284
+ this.date[2] = 1;
1285
+ }
1286
+ var t = Helpers.now(this.date);
1287
+ v = Helpers.dateToNum(t);
1288
+ }
1289
+
1290
+ if (isNaN(v)) {
1291
+ v = '';
1292
+ }
1293
+
1294
+ return v;
1295
+ }
1296
+
1297
+ // Types TODO: Generate types so we can garantee that text,scientific, numeric,percentage, current are not duplicates. If they are, it will be general or broken.
1298
+
1299
+ const getTokens = function(str) {
1300
+ allExpressionsRegex.lastIndex = 0; // Reset for global regex
1301
+ return str.match(allExpressionsRegex);
1302
+ }
1303
+
1304
+ /**
1305
+ * Get the method of one given token
1306
+ */
1307
+ const getMethod = function(str, temporary) {
1308
+ str = str.toString().toUpperCase();
1309
+
1310
+ // Check for datetime mask
1311
+ const datetime = temporary.every(t => t.type === 'datetime' || t.type === 'general');
1312
+
1313
+ // Use priority order for faster matching with pre-compiled regexes
1314
+ for (const type of tokenPriority) {
1315
+ if (!datetime && type === 'datetime') continue;
1316
+
1317
+ for (const compiled of compiledTokens[type]) {
1318
+ let regex = compiled.regex;
1319
+ regex.lastIndex = 0; // Reset regex state
1320
+ if (regex.test(str)) {
1321
+ return { type: type, method: compiled.method };
1322
+ }
1323
+ }
1324
+ }
1325
+ return null;
1326
+ }
1327
+
1328
+ const fixMinuteToken = function(t) {
1329
+ for (let i = 0; i < t.length; i++) {
1330
+ if (t[i] === 'M' || t[i] === 'MM') {
1331
+ // Not a month, correct to minutes
1332
+ if ((t[i - 1] && t[i - 1].indexOf('H') >= 0) ||
1333
+ (t[i - 2] && t[i - 2].indexOf('H') >= 0) ||
1334
+ (t[i + 1] && t[i + 1].indexOf('S') >= 0) ||
1335
+ (t[i + 2] && t[i + 2].indexOf('S') >= 0)) {
1336
+ // Apply minute token
1337
+ t[i] = t[i] === 'M' ? 'I': 'MI';
1338
+ }
1339
+ }
1340
+ }
1341
+ }
1342
+
1343
+ /**
1344
+ * Identify each method for each token
1345
+ */
1346
+ const getMethodsFromTokens = function(t) {
1347
+ // Uppercase
1348
+ t = t.map(v => {
1349
+ return v.toString().toUpperCase();
1350
+ });
1351
+
1352
+ // Compatibility with Excel
1353
+ fixMinuteToken(t);
1354
+
1355
+ let result = [];
1356
+ for (let i = 0; i < t.length; i++) {
1357
+ var m = getMethod(t[i], result);
1358
+ if (m) {
1359
+ result.push(m);
1360
+ } else {
1361
+ result.push(null);
1362
+ }
1363
+ }
1364
+ return result;
1365
+ }
1366
+
1367
+ const getMethodByPosition = function(control) {
1368
+ let methodName;
1369
+ if (control.methods[control.index] && typeof(control.value[control.position]) !== 'undefined') {
1370
+ methodName = control.methods[control.index].method;
1371
+ }
1372
+
1373
+ if (methodName && typeof(parseMethods[methodName]) === 'function') {
1374
+ return parseMethods[methodName];
1375
+ }
1376
+
1377
+ return false;
1378
+ }
1379
+
1380
+ const processPaddingZeros = function(token, value, decimal) {
1381
+ if (! value) {
1382
+ return value;
1383
+ }
1384
+ let m = token.split(decimal);
1385
+ let desiredNumOfPaddingZeros = m[0].match(/[0]+/g);
1386
+ if (desiredNumOfPaddingZeros[0]) {
1387
+ desiredNumOfPaddingZeros = desiredNumOfPaddingZeros[0].length
1388
+ let v = value.toString().split(decimal);
1389
+ let len = v[0].length;
1390
+ if (desiredNumOfPaddingZeros > len) {
1391
+ v[0] = v[0].padStart(desiredNumOfPaddingZeros, '0');
1392
+ return v.join(decimal);
1393
+ }
1394
+ }
1395
+ }
1396
+
1397
+ const processNumOfPaddingZeros = function(control) {
1398
+ let negativeSignal = false;
1399
+ control.methods.forEach((method, k) => {
1400
+ if (method.type === 'numeric' || method.type === 'percentage' || method.type === 'scientific') {
1401
+ let ret = processPaddingZeros(control.tokens[k], control.values[k], control.decimal);
1402
+ if (ret) {
1403
+ control.values[k] = ret;
1404
+ }
1405
+ }
1406
+
1407
+ if (control.type === 'currency' && control.parenthesisForNegativeNumbers === true) {
1408
+ if (method.type === 'currency') {
1409
+ if (control.values[k].toString().includes('-')) {
1410
+ control.values[k] = control.values[k].replace('-', '');
1411
+
1412
+ negativeSignal = true;
1413
+ }
1414
+ }
1415
+ }
1416
+ });
1417
+
1418
+
1419
+ if (control.type === 'currency' && control.parenthesisForNegativeNumbers === true && negativeSignal) {
1420
+ control.methods.forEach((method, k) => {
1421
+ if (! control.values[k] && control.tokens[k] === '(') {
1422
+ control.values[k] = '(';
1423
+ } else if (! control.values[k] && control.tokens[k] === ')') {
1424
+ control.values[k] = ')';
1425
+ }
1426
+ });
1427
+ }
1428
+ }
1429
+
1430
+ const getValue = function(control) {
1431
+ return control.values.join('');
1432
+ }
1433
+
1434
+ const inputIsANumber = function(num) {
1435
+ if (typeof(num) === 'string') {
1436
+ num = num.trim();
1437
+ }
1438
+ return !isNaN(num) && num !== null && num !== '';
1439
+ }
1440
+
1441
+ const getType = function(control) {
1442
+ // Mask type
1443
+ let type = 'general';
1444
+ // Process other types
1445
+ for (var i = 0; i < control.methods.length; i++) {
1446
+ let m = control.methods[i];
1447
+ if (m && m.type !== 'general' && m.type !== type) {
1448
+ if (type === 'general') {
1449
+ type = m.type;
1450
+ } else {
1451
+ type = 'general';
1452
+ break;
1453
+ }
1454
+ }
1455
+ }
1456
+ return type;
1457
+ }
1458
+
1459
+ const isNumber = function(num) {
1460
+ if (typeof(num) === 'string') {
1461
+ num = num.trim();
1462
+ }
1463
+ return !isNaN(num) && num !== null && num !== '';
1464
+ }
1465
+
1466
+ // TODO, get negative mask automatically based on the input sign?
1467
+
1468
+ const getConfig = function(config, value) {
1469
+ // Internal default control of the mask system
1470
+ const control = {
1471
+ // Mask options
1472
+ options: {},
1473
+ // New values for each token found
1474
+ values: [],
1475
+ // Token position
1476
+ index: 0,
1477
+ // Character position
1478
+ position: 0,
1479
+ // Date raw values
1480
+ date: [0,0,0,0,0,0],
1481
+ // Raw number for the numeric values
1482
+ number: 0,
1483
+ }
1484
+
1485
+ if (typeof(value) === 'undefined' || value === null) {
1486
+ value = '';
1487
+ }
1488
+
1489
+ // Value to be masked
1490
+ control.value = value.toString();
1491
+ control.raw = value;
1492
+
1493
+
1494
+ // Options defined by the user
1495
+ if (typeof(config) == 'string') {
1496
+ // Mask
1497
+ control.mask = config;
1498
+ } else if (config) {
1499
+ // Mask
1500
+ let k = Object.keys(config);
1501
+ for (var i = 0; i < k.length; i++) {
1502
+ control[k[i]] = config[k[i]];
1503
+ }
1504
+ }
1505
+
1506
+ // Controls of Excel that should be ignored
1507
+ if (control.mask) {
1508
+ let d = control.mask.split(';');
1509
+ // Mask
1510
+ let mask = d[0];
1511
+
1512
+ if (typeof(value) === 'number' || isNumber(value)) {
1513
+ if (Number(value) < 0 && d[1]) {
1514
+ mask = d[1];
1515
+ } else if (Number(value) === 0 && d[2]) {
1516
+ mask = d[2];
1517
+ }
1518
+ } else {
1519
+ if (d[3]) {
1520
+ mask = d[3];
1521
+ }
1522
+ }
1523
+ // Cleaning the mask
1524
+ mask = mask.replace(new RegExp('"', 'mgi'), "");
1525
+ // Parenthesis
1526
+ let reg = /(?<!_)\((?![^()]*_)([^'"]*?)\)/g;
1527
+ if (mask.match(reg)) {
1528
+ control.parenthesisForNegativeNumbers = true;
1529
+ }
1530
+ // Match brackets that should be removed (NOT the time format codes)
1531
+ reg = /\[(?!(?:s|ss|h|hh|m|mm)\])([^\]]*)\]/g;
1532
+ if (mask.match(reg)) {
1533
+ mask = mask.replace(reg, ''); // Removes brackets and content
1534
+ }
1535
+ // Get only the first mask for now and remove
1536
+ control.mask = mask;
1537
+ // Get tokens which are the methods for parsing
1538
+ control.tokens = getTokens(control.mask);
1539
+ // Get methods from the tokens
1540
+ control.methods = getMethodsFromTokens(control.tokens);
1541
+ // Type
1542
+ control.type = getType(control);
1543
+ }
1544
+
1545
+ // Decimal
1546
+ control.decimal = getDecimal.call(control);
1547
+
1548
+ return control;
1549
+ }
1550
+
1551
+ const toPlainString = function(num) {
1552
+ // Convert number to string if it isn't already
1553
+ num = String(num);
1554
+
1555
+ // If it's not in exponential form, return as-is
1556
+ if (!/e/i.test(num)) return num;
1557
+
1558
+ // Decompose scientific notation
1559
+ const [coefficient, exponent] = num.toLowerCase().split('e');
1560
+ const exp = parseInt(exponent, 10);
1561
+
1562
+ // Handle sign
1563
+ const sign = coefficient[0] === '-' ? '-' : '';
1564
+ const [intPart, fracPart = ''] = coefficient.replace('-', '').split('.');
1565
+
1566
+ const digits = intPart + fracPart;
1567
+ const decimalPos = intPart.length;
1568
+
1569
+ let newPos = decimalPos + exp;
1570
+
1571
+ if (newPos <= 0) {
1572
+ // Decimal point moves left
1573
+ return sign + '0.' + '0'.repeat(-newPos) + digits;
1574
+ } else if (newPos >= digits.length) {
1575
+ // Decimal point moves right, add trailing zeros
1576
+ return sign + digits + '0'.repeat(newPos - digits.length);
1577
+ } else {
1578
+ // Decimal point moves into the number
1579
+ return sign + digits.slice(0, newPos) + '.' + digits.slice(newPos);
1580
+ }
1581
+ };
1582
+
1583
+ const adjustNumberOfDecimalPlaces = function(config, value) {
1584
+ let temp = value;
1585
+ let mask = config.mask;
1586
+ let expo;
1587
+
1588
+ if (config.type === 'scientific') {
1589
+ mask = config.mask.toUpperCase().split('E')[0];
1590
+
1591
+ let numOfDecimalPlaces = mask.split(config.decimal);
1592
+ numOfDecimalPlaces = numOfDecimalPlaces[1].match(/[0#]+/g);
1593
+ numOfDecimalPlaces = numOfDecimalPlaces[0]?.length ?? 0;
1594
+ temp = temp.toExponential(numOfDecimalPlaces);
1595
+ expo = temp.toString().split('e+');
1596
+ temp = Number(expo[0]);
1597
+ }
1598
+
1599
+ if (mask.indexOf(config.decimal) === -1) {
1600
+ // No decimal places
1601
+ if (! Number.isInteger(temp)) {
1602
+ temp = temp.toFixed(0);
1603
+ }
1604
+ } else {
1605
+ // Length of the decimal
1606
+ let mandatoryDecimalPlaces = mask.split(config.decimal);
1607
+ mandatoryDecimalPlaces = mandatoryDecimalPlaces[1].match(/0+/g);
1608
+ if (mandatoryDecimalPlaces) {
1609
+ mandatoryDecimalPlaces = mandatoryDecimalPlaces[0].length;
1610
+ } else {
1611
+ mandatoryDecimalPlaces = 0;
1612
+ }
1613
+ // Amount of decimal
1614
+ let numOfDecimalPlaces = temp.toString().split(config.decimal)
1615
+ numOfDecimalPlaces = numOfDecimalPlaces[1]?.length ?? 0;
1616
+ // Necessary adjustment
1617
+ let necessaryAdjustment = 0;
1618
+ if (numOfDecimalPlaces < mandatoryDecimalPlaces) {
1619
+ necessaryAdjustment = mandatoryDecimalPlaces;
1620
+ } else {
1621
+ // Optional
1622
+ let optionalDecimalPlaces = mask.split(config.decimal);
1623
+ optionalDecimalPlaces = optionalDecimalPlaces[1].match(/[0#]+/g);
1624
+ if (optionalDecimalPlaces) {
1625
+ optionalDecimalPlaces = optionalDecimalPlaces[0].length;
1626
+ if (numOfDecimalPlaces > optionalDecimalPlaces) {
1627
+ necessaryAdjustment = optionalDecimalPlaces;
1628
+ }
1629
+ }
1630
+ }
1631
+ // Adjust decimal numbers if applicable
1632
+ if (necessaryAdjustment) {
1633
+ let t = temp.toFixed(necessaryAdjustment);
1634
+ let n = temp.toString().split('.');
1635
+ let fraction = n[1];
1636
+ if (fraction && fraction.length > necessaryAdjustment && fraction[fraction.length - 1] === '5') {
1637
+ t = parseFloat(n[0] + '.' + fraction + '1').toFixed(necessaryAdjustment);
1638
+ }
1639
+ temp = t;
1640
+ }
1641
+ }
1642
+
1643
+ if (config.type === 'scientific') {
1644
+ let ret = processPaddingZeros(mask, temp, config.decimal);
1645
+ if (ret) {
1646
+ temp = ret;
1647
+ }
1648
+ expo[0] = temp;
1649
+
1650
+ mask = config.mask.toUpperCase().split('E+')[1];
1651
+ ret = processPaddingZeros(mask, expo[1], config.decimal);
1652
+ if (ret) {
1653
+ expo[1] = ret;
1654
+ }
1655
+
1656
+ temp = expo.join('e+');
1657
+ }
1658
+
1659
+ return temp;
1660
+ }
1661
+
1662
+ const formatFraction = function(value, mask) {
1663
+ let maxDenominator;
1664
+ let fixedDenominator = null;
1665
+ let allowWholeNumber = true;
1666
+
1667
+ // Check for fixed denominator like # ?/8 or ?/8
1668
+ const fixed = mask.match(/\/(\d+)/);
1669
+ if (fixed) {
1670
+ fixedDenominator = parseInt(fixed[1], 10);
1671
+ maxDenominator = fixedDenominator;
1672
+ } else {
1673
+ // Determine based on question marks in mask
1674
+ const match = mask.match(/\?\/(\?+)/);
1675
+ if (match) {
1676
+ maxDenominator = Math.pow(10, match[1].length) - 1;
1677
+ } else {
1678
+ maxDenominator = 9; // Default for # ?/? or ?/?
1679
+ }
1680
+ }
1681
+ // Check if mask allows whole number (e.g., ?/? or ?/8 implies no whole number)
1682
+ allowWholeNumber = mask.includes('#');
1683
+
1684
+ // If we have a fixed denominator, use it exactly (don't simplify)
1685
+ if (fixedDenominator) {
1686
+ const isNegative = value < 0;
1687
+ const absValue = Math.abs(value);
1688
+ const numerator = Math.round(absValue * fixedDenominator);
1689
+
1690
+ // For masks like ?/8, always output as pure fraction (no whole number)
1691
+ if (!allowWholeNumber) {
1692
+ return isNegative ? `-${numerator}/${fixedDenominator}` : `${numerator}/${fixedDenominator}`;
1693
+ }
1694
+
1695
+ // For masks like # ?/8, allow whole number
1696
+ const whole = Math.floor(numerator / fixedDenominator);
1697
+ const remainder = numerator % fixedDenominator;
1698
+ if (remainder === 0) {
1699
+ return isNegative ? `-${whole}` : `${whole}`;
1700
+ }
1701
+ if (whole === 0) {
1702
+ return isNegative ? `-${numerator}/${fixedDenominator}` : `${numerator}/${fixedDenominator}`;
1703
+ }
1704
+ return isNegative ? `-${whole} ${remainder}/${fixedDenominator}` : `${whole} ${remainder}/${fixedDenominator}`;
1705
+ }
1706
+
1707
+ // Use continued fractions algorithm for better approximation
1708
+ function continuedFraction(value, maxDenom) {
1709
+ if (value === 0) return [0, 1];
1710
+ let sign = value < 0 ? -1 : 1;
1711
+ value = Math.abs(value);
1712
+ let whole = Math.floor(value);
1713
+ let frac = value - whole;
1714
+ if (frac === 0) return [sign * whole, 1];
1715
+
1716
+ let h1 = 1, h2 = 0;
1717
+ let k1 = 0, k2 = 1;
1718
+ let x = frac;
1719
+ while (k1 <= maxDenom) {
1720
+ let a = Math.floor(x);
1721
+ let h0 = a * h1 + h2;
1722
+ let k0 = a * k1 + k2;
1723
+ if (k0 > maxDenom) break;
1724
+ h2 = h1; h1 = h0;
1725
+ k2 = k1; k1 = k0;
1726
+ if (Math.abs(x - a) < 1e-10) break;
1727
+ x = 1 / (x - a);
1728
+ }
1729
+
1730
+ // Add the whole part back only if allowed
1731
+ let finalNum = sign * (allowWholeNumber ? whole * k1 + h1 : Math.round(value * k1));
1732
+ let finalDen = k1;
1733
+ return [finalNum, finalDen];
1734
+ }
1735
+
1736
+ const [numerator, denominator] = continuedFraction(value, maxDenominator);
1737
+
1738
+ // Handle the result
1739
+ const isNegative = numerator < 0;
1740
+ const absNumerator = Math.abs(numerator);
1741
+ const whole = allowWholeNumber ? Math.floor(absNumerator / denominator) : 0;
1742
+ const remainder = absNumerator % denominator;
1743
+ const sign = isNegative ? '-' : '';
1744
+
1745
+ if (remainder === 0) {
1746
+ return `${sign}${whole || 0}`;
1747
+ }
1748
+ if (whole === 0 || !allowWholeNumber) {
1749
+ return `${sign}${absNumerator}/${denominator}`;
1750
+ }
1751
+ return `${sign}${whole} ${remainder}/${denominator}`;
1752
+ }
1753
+
1754
+ const extractDateAndTime = function(value) {
1755
+ value = value.toString().substring(0,19);
1756
+ let splitStr = (value.indexOf('T') !== -1) ? 'T' : ' ';
1757
+ value = value.split(splitStr);
1758
+
1759
+ let y = null;
1760
+ let m = null;
1761
+ let d = null;
1762
+ let h = '0';
1763
+ let i = '0';
1764
+ let s = '0';
1765
+
1766
+ if (! value[1]) {
1767
+ if (value[0].indexOf(':') !== -1) {
1768
+ value[0] = value[0].split(':');
1769
+ h = value[0][0];
1770
+ i = value[0][1];
1771
+ s = value[0][2];
1772
+ } else {
1773
+ value[0] = value[0].split('-');
1774
+ y = value[0][0];
1775
+ m = value[0][1];
1776
+ d = value[0][2];
1777
+ }
1778
+ } else {
1779
+ value[0] = value[0].split('-');
1780
+ y = value[0][0];
1781
+ m = value[0][1];
1782
+ d = value[0][2];
1783
+
1784
+ value[1] = value[1].split(':');
1785
+ h = value[1][0];
1786
+ i = value[1][1];
1787
+ s = value[1][2];
1788
+ }
1789
+
1790
+ return [y,m,d,h,i,s];
1791
+ }
1792
+
1793
+ const Component = function(str, config, returnObject) {
1794
+ // Get configuration
1795
+ const control = getConfig(config, str);
1796
+
1797
+ if (control.locale) {
1798
+ // Process the locale
1799
+ } else if (control.mask) {
1800
+ // Walk every character on the value
1801
+ let method;
1802
+ while (method = getMethodByPosition(control)) {
1803
+ let char = control.value[control.position];
1804
+ if (char === hiddenCaret) {
1805
+ control.caret = {
1806
+ index: control.index,
1807
+ position: control.values[control.index]?.length ?? 0,
1808
+ }
1809
+ control.position++;
1810
+ } else {
1811
+ // Get the method name to handle the current token
1812
+ let ret = method.call(control, char);
1813
+ // Next position
1814
+ if (ret !== false) {
1815
+ control.position++;
1816
+ }
1817
+ }
1818
+ }
1819
+
1820
+ // Move index
1821
+ if (control.methods[control.index]) {
1822
+ let type = control.methods[control.index].type;
1823
+ if (isNumeric(type) && control.methods[++control.index]) {
1824
+ let next;
1825
+ while (next = control.methods[control.index]) {
1826
+ if (control.methods[control.index].type === 'general') {
1827
+ let method = control.methods[control.index].method;
1828
+ if (method && typeof(parseMethods[method]) === 'function') {
1829
+ parseMethods[method].call(control, null);
1830
+ }
1831
+ } else {
1832
+ break;
1833
+ }
1834
+ }
1835
+ }
1836
+ }
1837
+ }
1838
+ if (control.caret) {
1839
+ let index = control.caret.index;
1840
+ let position = control.caret.position;
1841
+ let value = control.values[index] ?? '';
1842
+ // Re-apply the caret to the original position
1843
+ control.values[index] = value.substring(0, position) + hiddenCaret + value.substring(position);
1844
+ }
1845
+
1846
+ control.value = getValue(control);
1847
+
1848
+ if (returnObject) {
1849
+ return control;
1850
+ } else {
1851
+ return control.value;
1852
+ }
1853
+ }
1854
+
1855
+ // Helper: Compare rendered value to original input
1856
+ const testMask = function(mask, value, original) {
1857
+ const rendered = Component.render(value, { mask }, true);
1858
+ return rendered.replace(/\s/g, '') === original.replace(/\s/g, '');
1859
+ }
1860
+
1861
+ const autoCastingFractions = function(value) {
1862
+ const fractionPattern = /^\s*(-?\d+\s+)?(-?\d+)\/(\d+)\s*$/;
1863
+ const fractionMatch = value.match(fractionPattern);
1864
+ if (fractionMatch) {
1865
+ const sign = value.trim().startsWith('-') ? -1 : 1;
1866
+ const whole = fractionMatch[1] ? Math.abs(parseInt(fractionMatch[1])) : 0;
1867
+ const numerator = Math.abs(parseInt(fractionMatch[2]));
1868
+ const denominator = parseInt(fractionMatch[3]);
1869
+
1870
+ if (denominator === 0) return null;
1871
+
1872
+ const decimalValue = sign * (whole + (numerator / denominator));
1873
+
1874
+ // Determine the mask
1875
+ let mask;
1876
+ if ([2, 4, 8, 16, 32].includes(denominator)) {
1877
+ mask = whole !== 0 ? `# ?/${denominator}` : `?/${denominator}`;
1878
+ } else if (denominator <= 9) {
1879
+ mask = whole !== 0 ? '# ?/?' : '?/?';
1880
+ } else {
1881
+ mask = whole !== 0 ? '# ??/??' : '??/??';
1882
+ }
1883
+
1884
+ if (testMask(mask, decimalValue, value.trim())) {
1885
+ return { mask, value: decimalValue };
1886
+ }
1887
+ }
1888
+ return null;
1889
+ }
1890
+
1891
+ const autoCastingPercent = function(value) {
1892
+ const percentPattern = /^\s*([+-]?\d+(?:[.,]\d+)?)%\s*$/;
1893
+ const percentMatch = value.match(percentPattern);
1894
+ if (percentMatch) {
1895
+ const rawNumber = percentMatch[1].replace(',', '.');
1896
+ const decimalValue = parseFloat(rawNumber) / 100;
1897
+
1898
+ const decimalPart = rawNumber.split('.')[1];
1899
+ const decimalPlaces = decimalPart ? decimalPart.length : 0;
1900
+ const mask = decimalPlaces > 0 ? `0.${'0'.repeat(decimalPlaces)}%` : '0%';
1901
+
1902
+ if (testMask(mask, decimalValue, value.trim())) {
1903
+ return { mask: mask, value: decimalValue };
1904
+ }
1905
+ }
1906
+ return null;
1907
+ }
1908
+
1909
+ const autoCastingDates = function(value) {
1910
+ if (!value || typeof value !== 'string') {
1911
+ return null;
1912
+ }
1913
+
1914
+ // Smart pattern detection based on the structure of the string
1915
+
1916
+ // 1. Analyze the structure to determine possible formats
1917
+ const analyzeStructure = function(str) {
1918
+ const patterns = [];
1919
+
1920
+ // Check for date with forward slashes: XX/XX/XXXX or XX/XX/XX
1921
+ if (str.match(/^\d{1,2}\/\d{1,2}\/\d{2,4}$/)) {
1922
+ const parts = str.split('/');
1923
+ const p1 = parseInt(parts[0]);
1924
+ const p2 = parseInt(parts[1]);
1925
+ const p3 = parseInt(parts[2]);
1926
+
1927
+ // Determine likely format based on values
1928
+ if (p1 <= 12 && p2 <= 31 && p2 > 12) {
1929
+ // Likely mm/dd/yyyy
1930
+ patterns.push('mm/dd/yyyy', 'mm/dd/yy', 'm/d/yyyy', 'm/d/yy');
1931
+ } else if (p1 <= 31 && p2 <= 12 && p1 > 12) {
1932
+ // Likely dd/mm/yyyy
1933
+ patterns.push('dd/mm/yyyy', 'dd/mm/yy', 'd/m/yyyy', 'd/m/yy');
1934
+ } else if (p1 <= 12 && p2 <= 12) {
1935
+ // Ambiguous - could be either, use locale preference
1936
+ const locale = navigator.language || 'en-US';
1937
+ if (locale.startsWith('en-US')) {
1938
+ patterns.push('mm/dd/yyyy', 'dd/mm/yyyy', 'mm/dd/yy', 'dd/mm/yy');
1939
+ } else {
1940
+ patterns.push('dd/mm/yyyy', 'mm/dd/yyyy', 'dd/mm/yy', 'mm/dd/yy');
1941
+ }
1942
+ }
1943
+
1944
+ // Add variations
1945
+ if (p3 < 100) {
1946
+ patterns.push('dd/mm/yy', 'mm/dd/yy', 'd/m/yy', 'm/d/yy');
1947
+ } else {
1948
+ patterns.push('dd/mm/yyyy', 'mm/dd/yyyy', 'd/m/yyyy', 'm/d/yyyy');
1949
+ }
1950
+ }
1951
+
1952
+ // Check for date with dashes: XX-XX-XXXX
1953
+ else if (str.match(/^\d{1,2}-\d{1,2}-\d{2,4}$/)) {
1954
+ const parts = str.split('-');
1955
+ const p1 = parseInt(parts[0]);
1956
+ const p2 = parseInt(parts[1]);
1957
+
1958
+ if (p1 <= 12 && p2 <= 31 && p2 > 12) {
1959
+ patterns.push('mm-dd-yyyy', 'mm-dd-yy', 'm-d-yyyy', 'm-d-yy');
1960
+ } else if (p1 <= 31 && p2 <= 12 && p1 > 12) {
1961
+ patterns.push('dd-mm-yyyy', 'dd-mm-yy', 'd-m-yyyy', 'd-m-yy');
1962
+ } else {
1963
+ patterns.push('dd-mm-yyyy', 'mm-dd-yyyy', 'dd-mm-yy', 'mm-dd-yy');
1964
+ }
1965
+ }
1966
+
1967
+ // Check for ISO format: YYYY-MM-DD
1968
+ else if (str.match(/^\d{4}-\d{1,2}-\d{1,2}$/)) {
1969
+ patterns.push('yyyy-mm-dd', 'yyyy-m-d');
1970
+ }
1971
+
1972
+ // Check for format: YYYY/MM/DD
1973
+ else if (str.match(/^\d{4}\/\d{1,2}\/\d{1,2}$/)) {
1974
+ patterns.push('yyyy/mm/dd', 'yyyy/m/d');
1975
+ }
1976
+
1977
+ // Check for dates with month names
1978
+ else if (str.match(/\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i)) {
1979
+ // DD Mon YYYY or DD Month YYYY
1980
+ if (str.match(/^\d{1,2}\s+\w+\s+\d{2,4}$/i)) {
1981
+ patterns.push('dd mmm yyyy', 'dd mmmm yyyy', 'd mmm yyyy', 'd mmmm yyyy',
1982
+ 'dd mmm yy', 'dd mmmm yy', 'd mmm yy', 'd mmmm yy');
1983
+ }
1984
+ // Mon DD, YYYY or Month DD, YYYY
1985
+ else if (str.match(/^\w+\s+\d{1,2},?\s+\d{2,4}$/i)) {
1986
+ patterns.push('mmm dd, yyyy', 'mmmm dd, yyyy', 'mmm d, yyyy', 'mmmm d, yyyy',
1987
+ 'mmm dd yyyy', 'mmmm dd yyyy', 'mmm d yyyy', 'mmmm d yyyy');
1988
+ }
1989
+ // DD-Mon-YYYY
1990
+ else if (str.match(/^\d{1,2}-\w+-\d{2,4}$/i)) {
1991
+ patterns.push('dd-mmm-yyyy', 'dd-mmmm-yyyy', 'd-mmm-yyyy', 'd-mmmm-yyyy',
1992
+ 'dd-mmm-yy', 'dd-mmmm-yy', 'd-mmm-yy', 'd-mmmm-yy');
1993
+ }
1994
+ }
1995
+
1996
+ // Check for weekday formats
1997
+ else if (str.match(/^(mon|tue|wed|thu|fri|sat|sun)/i)) {
1998
+ if (str.match(/^\w+,\s+\d{1,2}\s+\w+\s+\d{4}$/i)) {
1999
+ patterns.push('ddd, dd mmm yyyy', 'ddd, d mmm yyyy',
2000
+ 'dddd, dd mmmm yyyy', 'dddd, d mmmm yyyy');
2001
+ }
2002
+ }
2003
+
2004
+ // Check for datetime formats
2005
+ else if (str.includes(' ') && str.match(/\d{1,2}:\d{2}/)) {
2006
+ const parts = str.split(' ');
2007
+ if (parts.length >= 2) {
2008
+ const datePart = parts[0];
2009
+ const timePart = parts.slice(1).join(' ');
2010
+
2011
+ // Determine date format
2012
+ let dateMasks = [];
2013
+ if (datePart.includes('/')) {
2014
+ dateMasks = ['dd/mm/yyyy', 'mm/dd/yyyy', 'd/m/yyyy', 'm/d/yyyy'];
2015
+ } else if (datePart.includes('-')) {
2016
+ if (datePart.match(/^\d{4}-/)) {
2017
+ dateMasks = ['yyyy-mm-dd', 'yyyy-m-d'];
2018
+ } else {
2019
+ dateMasks = ['dd-mm-yyyy', 'mm-dd-yyyy', 'd-m-yyyy', 'm-d-yyyy'];
2020
+ }
2021
+ }
2022
+
2023
+ // Determine time format
2024
+ let timeMasks = [];
2025
+ if (timePart.match(/\d{1,2}:\d{2}:\d{2}/)) {
2026
+ timeMasks = ['hh:mm:ss', 'h:mm:ss'];
2027
+ } else {
2028
+ timeMasks = ['hh:mm', 'h:mm'];
2029
+ }
2030
+
2031
+ // Add AM/PM variants if present
2032
+ if (timePart.match(/[ap]m/i)) {
2033
+ timeMasks = timeMasks.map(t => t + ' am/pm');
2034
+ }
2035
+
2036
+ // Combine date and time masks
2037
+ for (const dateMask of dateMasks) {
2038
+ for (const timeMask of timeMasks) {
2039
+ patterns.push(`${dateMask} ${timeMask}`);
2040
+ }
2041
+ }
2042
+ }
2043
+ }
2044
+
2045
+ // Check for time-only formats
2046
+ else if (str.match(/^\d{1,2}:\d{2}(:\d{2})?(\s*(am|pm))?$/i)) {
2047
+ if (str.match(/:\d{2}:\d{2}/)) {
2048
+ patterns.push('hh:mm:ss', 'h:mm:ss');
2049
+ if (str.match(/[ap]m/i)) {
2050
+ patterns.push('hh:mm:ss am/pm', 'h:mm:ss am/pm');
2051
+ }
2052
+ } else {
2053
+ patterns.push('hh:mm', 'h:mm');
2054
+ if (str.match(/[ap]m/i)) {
2055
+ patterns.push('hh:mm am/pm', 'h:mm am/pm');
2056
+ }
2057
+ }
2058
+ }
2059
+
2060
+ // Check for extended hour format [h]:mm:ss
2061
+ else if (str.match(/^\[?\d+\]?:\d{2}:\d{2}$/)) {
2062
+ patterns.push('[h]:mm:ss');
2063
+ }
2064
+
2065
+ return [...new Set(patterns)]; // Remove duplicates
2066
+ };
2067
+
2068
+ // Get candidate masks based on the string structure
2069
+ const candidateMasks = analyzeStructure(value);
2070
+
2071
+ // If no patterns detected, try some common formats as fallback
2072
+ if (candidateMasks.length === 0) {
2073
+ const locale = navigator.language || 'en-US';
2074
+ if (locale.startsWith('en-US')) {
2075
+ candidateMasks.push(
2076
+ 'mm/dd/yyyy', 'mm-dd-yyyy', 'yyyy-mm-dd',
2077
+ 'mm/dd/yy', 'mm-dd-yy',
2078
+ 'hh:mm:ss', 'hh:mm', 'h:mm am/pm'
2079
+ );
2080
+ } else {
2081
+ candidateMasks.push(
2082
+ 'dd/mm/yyyy', 'dd-mm-yyyy', 'yyyy-mm-dd',
2083
+ 'dd/mm/yy', 'dd-mm-yy',
2084
+ 'hh:mm:ss', 'hh:mm', 'h:mm'
2085
+ );
2086
+ }
2087
+ }
2088
+
2089
+ // Try each candidate mask
2090
+ for (const mask of candidateMasks) {
2091
+ try {
2092
+ // Use Component.extractDateFromString to parse the date
2093
+ const isoDate = Component.extractDateFromString(value, mask);
2094
+
2095
+ if (isoDate && isoDate !== '') {
2096
+ // Parse the ISO date string to components
2097
+ const parts = isoDate.split(' ');
2098
+ const dateParts = parts[0].split('-');
2099
+ const timeParts = parts[1] ? parts[1].split(':') : ['0', '0', '0'];
2100
+
2101
+ const year = parseInt(dateParts[0]);
2102
+ const month = parseInt(dateParts[1]);
2103
+ const day = parseInt(dateParts[2]);
2104
+ const hour = parseInt(timeParts[0]);
2105
+ const minute = parseInt(timeParts[1]);
2106
+ const second = parseInt(timeParts[2]);
2107
+
2108
+ // Validate the date components
2109
+ if (year > 0 && month >= 1 && month <= 12 && day >= 1 && day <= 31 &&
2110
+ hour >= 0 && hour < 24 && minute >= 0 && minute < 60 && second >= 0 && second < 60) {
2111
+
2112
+ // Convert to Excel serial number
2113
+ const excelNumber = Helpers.dateToNum(isoDate);
2114
+
2115
+ // Verify by rendering back
2116
+ const rendered = Component.render(excelNumber, { mask: mask }, true);
2117
+
2118
+ // Case-insensitive comparison for month names
2119
+ if (rendered.toLowerCase() === value.toLowerCase()) {
2120
+ return {
2121
+ mask: mask,
2122
+ value: excelNumber,
2123
+ };
2124
+ }
2125
+ }
2126
+ }
2127
+ } catch (e) {
2128
+ }
2129
+ }
2130
+
2131
+ // No matching format found
2132
+ return null;
2133
+ };
2134
+
2135
+ const autoCastingCurrency = function (input) {
2136
+ if (typeof input !== 'string') return null;
2137
+
2138
+ const original = input.trim();
2139
+
2140
+ const isNegative = /^\s*[-(]/.test(original);
2141
+ const hasParens = /^\s*\(.+\)\s*$/.test(original);
2142
+ let value = original.replace(/[()\-]/g, '').trim();
2143
+
2144
+ // Use pre-compiled currency regexes
2145
+ let symbol = '';
2146
+
2147
+ for (let {symbol: s, regex} of currencyRegexes) {
2148
+ const match = value.match(regex);
2149
+ if (match) {
2150
+ symbol = s + (match[1] || '');
2151
+ value = value.replace(regex, '');
2152
+ break;
2153
+ }
2154
+ }
2155
+
2156
+ // Generic symbol/prefix (e.g., "U$", "US$")
2157
+ if (!symbol) {
2158
+ const prefixMatch = value.match(/^([^\d\s.,-]{1,4})(\s?)/);
2159
+ if (prefixMatch) {
2160
+ symbol = prefixMatch[1] + (prefixMatch[2] || '');
2161
+ value = value.replace(prefixMatch[0], '');
2162
+ }
2163
+ }
2164
+
2165
+ // Code suffix (e.g., USD, BRL)
2166
+ const codeMatch = value.match(/([A-Z]{3})$/);
2167
+ if (codeMatch) {
2168
+ value = value.replace(codeMatch[1], '').trim();
2169
+ if (!symbol) symbol = codeMatch[1] + ' ';
2170
+ }
2171
+
2172
+ value = value.replace(/\s+/g, '');
2173
+
2174
+ // Infer separators
2175
+ let group = ',', decimal = '.';
2176
+
2177
+ if (value.includes(',') && value.includes('.')) {
2178
+ const lastComma = value.lastIndexOf(',');
2179
+ const lastDot = value.lastIndexOf('.');
2180
+ if (lastComma > lastDot) {
2181
+ group = '.';
2182
+ decimal = ',';
2183
+ } else {
2184
+ group = ',';
2185
+ decimal = '.';
2186
+ }
2187
+ } else if (value.includes('.')) {
2188
+ const parts = value.split('.');
2189
+ const lastPart = parts[parts.length - 1];
2190
+ if (/^\d{3}$/.test(lastPart)) {
2191
+ group = '.';
2192
+ decimal = ',';
2193
+ } else {
2194
+ group = ',';
2195
+ decimal = '.';
2196
+ }
2197
+ } else if (value.includes(',')) {
2198
+ const parts = value.split(',');
2199
+ const lastPart = parts[parts.length - 1];
2200
+ if (/^\d{3}$/.test(lastPart)) {
2201
+ group = ',';
2202
+ decimal = '.';
2203
+ } else {
2204
+ group = '.';
2205
+ decimal = ',';
2206
+ }
2207
+ }
2208
+
2209
+ // Normalize and parse
2210
+ const normalized = value
2211
+ .replace(new RegExp(`\\${group}`, 'g'), '')
2212
+ .replace(decimal, '.');
2213
+
2214
+ const parsed = parseFloat(normalized);
2215
+ if (isNaN(parsed)) return null;
2216
+
2217
+ const finalValue = isNegative ? -parsed : parsed;
2218
+
2219
+ // Build dynamic group + decimal mask
2220
+ const decimalPlaces = normalized.includes('.') ? normalized.split('.')[1].length : 0;
2221
+ const maskDecimal = decimalPlaces ? decimal + '0'.repeat(decimalPlaces) : '';
2222
+ const groupMask = '#' + group + '##0';
2223
+ let mask = `${symbol}${groupMask}${maskDecimal}`;
2224
+
2225
+ if (isNegative) {
2226
+ mask = hasParens ? `(${mask})` : `-${mask}`;
2227
+ }
2228
+
2229
+ return {
2230
+ mask,
2231
+ value: finalValue
2232
+ };
2233
+ }
2234
+
2235
+ const autoCastingNumber = function (input) {
2236
+ // If you currently support numeric inputs directly, keep this:
2237
+ if (typeof input === 'number' && Number.isFinite(input)) {
2238
+ return { mask: '0', value: input };
2239
+ }
2240
+
2241
+ if (typeof input !== 'string') {
2242
+ return null;
2243
+ }
2244
+
2245
+ const sRaw = input.trim();
2246
+ if (!/^[+-]?\d+$/.test(sRaw)) {
2247
+ return null;
2248
+ }
2249
+
2250
+ const sign = /^[+-]/.test(sRaw) ? sRaw[0] : '';
2251
+ const digitsClean = (sign ? sRaw.slice(1) : sRaw); // keep as you already do
2252
+
2253
+ // ***** NEW: mask derived from RAW leading zeros only *****
2254
+ const rawDigits = sign ? sRaw.slice(1) : sRaw; // no extra cleaning here
2255
+ const m = rawDigits.match(/^0+/);
2256
+ const leadingZeros = m ? m[0].length : 0;
2257
+
2258
+ const mask = leadingZeros > 0 ? '0'.repeat(rawDigits.length) : '0';
2259
+
2260
+ // Your existing numeric value (from the cleaned digits)
2261
+ const value = Number(sign + digitsClean);
2262
+
2263
+ return { mask, value };
2264
+ };
2265
+
2266
+ const autoCastingScientific = function(input) {
2267
+ if (typeof input !== 'string') return null;
2268
+
2269
+ const original = input.trim();
2270
+
2271
+ // Match scientific notation: 1e3, -2.5E-4, etc.
2272
+ const sciPattern = /^[-+]?\d*\.?\d+[eE][-+]?\d+$/;
2273
+ if (!sciPattern.test(original)) return null;
2274
+
2275
+ const parsed = parseFloat(original);
2276
+ if (isNaN(parsed)) return null;
2277
+
2278
+ // Extract parts to determine mask
2279
+ const [coefficient, exponent] = original.toLowerCase().split('e');
2280
+ const decimalPlaces = coefficient.includes('.') ? coefficient.split('.')[1].length : 0;
2281
+ const mask = `0${decimalPlaces ? '.' + '0'.repeat(decimalPlaces) : ''}E+00`;
2282
+
2283
+ return {
2284
+ mask,
2285
+ value: parsed
2286
+ };
2287
+ }
2288
+
2289
+ const autoCastingTime = function (input) {
2290
+ if (typeof input !== 'string') return null;
2291
+ const original = input.trim();
2292
+
2293
+ // hh:mm[:ss][ am/pm]
2294
+ const m = original.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?(?:\s*(am|pm))?$/i);
2295
+ if (!m) return null;
2296
+
2297
+ let h = parseInt(m[1], 10);
2298
+ const i = parseInt(m[2], 10);
2299
+ const s = m[3] ? parseInt(m[3], 10) : 0;
2300
+ const mer = m[4] && m[4].toLowerCase();
2301
+
2302
+ // basic range checks
2303
+ if (i > 59 || s > 59) return null;
2304
+ if (mer) {
2305
+ if (h < 1 || h > 12) return null;
2306
+ if (mer === 'pm' && h < 12) h += 12;
2307
+ if (mer === 'am' && h === 12) h = 0;
2308
+ } else {
2309
+ if (h > 23) return null;
2310
+ }
2311
+
2312
+ // Excel serial for time-of-day = hours/24 + minutes/1440 + seconds/86400
2313
+ const excel = (h + i / 60 + s / 3600) / 24;
2314
+
2315
+ // Build mask according to how user typed it
2316
+ const hourToken = m[1].length === 1 ? 'h' : 'hh';
2317
+ const base = s !== 0 || m[3] ? `${hourToken}:mm:ss` : `${hourToken}:mm`;
2318
+ const mask = mer ? `${base} am/pm` : base;
2319
+
2320
+ // Verify we can render back exactly what the user typed
2321
+ if (testMask(mask, excel, original)) { // uses Component.render under the hood
2322
+ return { mask: mask, value: excel};
2323
+ }
2324
+
2325
+ // Try alternate hour width if needed
2326
+ const altHour = hourToken === 'hh' ? 'h' : 'hh';
2327
+ const alt = mer
2328
+ ? `${altHour}${base.slice(hourToken.length)} am/pm`
2329
+ : `${altHour}${base.slice(hourToken.length)}`;
2330
+
2331
+ if (testMask(alt, excel, original)) {
2332
+ return { mask: alt, value: excel };
2333
+ }
2334
+
2335
+ return null;
2336
+ };
2337
+
2338
+ const ParseValue = function(v, config) {
2339
+ if (v === '') return '';
2340
+
2341
+ const decimal = config.decimal || '.';
2342
+
2343
+ v = ('' + v).split(decimal);
2344
+
2345
+ // Detect negative sign
2346
+ let signal = v[0].includes('-');
2347
+
2348
+ v[0] = v[0].match(/[0-9]+/g);
2349
+ if (v[0]) {
2350
+ if (signal) v[0].unshift('-');
2351
+ v[0] = v[0].join('');
2352
+ } else {
2353
+ v[0] = signal ? '-' : '';
2354
+ }
2355
+
2356
+ if (v[1] !== undefined) {
2357
+ v[1] = v[1].match(/[0-9]+/g);
2358
+ v[1] = v[1] ? v[1].join('') : '';
2359
+ }
2360
+
2361
+ return v[0] || v[1] ? v : '';
2362
+ }
2363
+
2364
+ const Extract = function(v, config) {
2365
+ const parsed = ParseValue(v, config);
2366
+ if (parsed) {
2367
+ if (parsed[0] === '-') {
2368
+ parsed[0] = '-0';
2369
+ }
2370
+ return parseFloat(parsed.join('.'));
2371
+ }
2372
+ return null;
2373
+ }
2374
+
2375
+ /**
2376
+ * Try to get which mask that can transform the number in that format
2377
+ */
2378
+ Component.autoCasting = function(value, returnObject) {
2379
+ const methods = [
2380
+ autoCastingDates, // Most structured, the least ambiguous
2381
+ autoCastingTime,
2382
+ autoCastingFractions, // Specific pattern with slashes
2383
+ autoCastingPercent, // Recognizable with "%"
2384
+ autoCastingScientific,
2385
+ autoCastingNumber, // Only picks up basic digits, decimals, leading 0s
2386
+ autoCastingCurrency, // Complex formats, but recognizable
2387
+ ];
2388
+
2389
+ for (let method of methods) {
2390
+ const test = method(value);
2391
+ if (test) {
2392
+ return test;
2393
+ }
2394
+ }
2395
+
2396
+ return null;
2397
+ }
2398
+
2399
+ Component.extract = function(value, options, returnObject) {
2400
+ if (!value || typeof options !== 'object') return value;
2401
+
2402
+ // Get decimal, group, type, etc.
2403
+ const config = getConfig(options, value);
2404
+ const type = config.type;
2405
+
2406
+ let result;
2407
+ let o = options;
2408
+
2409
+ if (type === 'text') {
2410
+ result = value;
2411
+ } else if (type === 'general') {
2412
+ result = Component(value, options);
2413
+ } else if (type === 'datetime') {
2414
+ if (value instanceof Date) {
2415
+ value = Component.getDateString(value, config.mask);
2416
+ }
2417
+
2418
+ o = Component(value, options, true);
2419
+
2420
+ result = typeof o.value === 'number' ? o.value : extractDate.call(o);
2421
+ } else if (type === 'scientific') {
2422
+ result = typeof value === 'string' ? Number(value) : value;
2423
+ } else if (type === 'fraction') {
2424
+ // Parse a fraction string according to the mask (supports mixed "# ?/d" or simple "?/d")
2425
+ const mask = config.mask;
2426
+
2427
+ // Detect fixed denominator (e.g. "# ?/16" or "?/8")
2428
+ const fixedDenMatch = mask.match(/\/\s*(\d+)\s*$/);
2429
+ const fixedDen = fixedDenMatch ? parseInt(fixedDenMatch[1], 10) : null;
2430
+
2431
+ // Whether a mask allows a whole part (e.g. "# ?/?")
2432
+ const allowWhole = mask.includes('#');
2433
+
2434
+ let s = ('' + value).trim();
2435
+ if (! s) {
2436
+ result = null;
2437
+ } else {
2438
+ // Allow leading parentheses or '-' for negatives
2439
+ let sign = 1;
2440
+ if (/^\(.*\)$/.test(s)) {
2441
+ sign = -1;
2442
+ s = s.slice(1, -1).trim();
2443
+ }
2444
+ if (/^\s*-/.test(s)) {
2445
+ sign = -1;
2446
+ s = s.replace(/^\s*-/, '').trim();
2447
+ }
2448
+
2449
+ let out = null;
2450
+
2451
+ if (s.includes('/')) {
2452
+ // sign? (whole )? numerator / denominator
2453
+ // Examples:
2454
+ // "1 1/2" => whole=1, num=1, den=2
2455
+ // "1/2" => whole=undefined, num=1, den=2
2456
+ const m = s.match(/^\s*(?:(\d+)\s+)?(\d+)\s*\/\s*(\d+)\s*$/);
2457
+ if (m) {
2458
+ const whole = allowWhole && m[1] ? parseInt(m[1], 10) : 0;
2459
+ const num = parseInt(m[2], 10);
2460
+ let den = parseInt(m[3], 10);
2461
+
2462
+ // If mask fixes the denominator, enforce it
2463
+ if (fixedDen) den = fixedDen;
2464
+
2465
+ if (den !== 0) {
2466
+ out = sign * (whole + num / den);
2467
+ }
2468
+ }
2469
+ } else {
2470
+ // No slash → treats as a plain number (e.g., whole only)
2471
+ const plain = Number(s.replace(',', '.'));
2472
+ if (!Number.isNaN(plain)) {
2473
+ out = sign * Math.abs(plain);
2474
+ }
2475
+ }
2476
+
2477
+ result = out;
2478
+ }
2479
+ } else {
2480
+ // Default fallback — numeric/currency/percent/etc.
2481
+ result = Extract(value, config);
2482
+ // Adjust percent
2483
+ if (type === 'percentage' && ('' + value).indexOf('%') !== -1) {
2484
+ result = result / 100;
2485
+ }
2486
+ }
2487
+
2488
+ o.value = result;
2489
+
2490
+ if (! o.type && type) {
2491
+ o.type = type;
2492
+ }
2493
+
2494
+ return returnObject ? o : result;
2495
+ };
2496
+
2497
+ Component.render = function(value, options, fullMask) {
2498
+ // Nothing to render
2499
+ if (value === '' || value === undefined || value === null) {
2500
+ return '';
2501
+ }
2502
+
2503
+ // Config
2504
+ const config = getConfig(options, value);
2505
+
2506
+ // Percentage
2507
+ if (config.type === 'datetime') {
2508
+ var t = Component.getDateString(value, config.mask);
2509
+ if (t) {
2510
+ value = t;
2511
+ } else {
2512
+ return '';
2513
+ }
2514
+ } else if (config.type === 'text') {
2515
+ // Parse number
2516
+ if (typeof(value) === 'number') {
2517
+ value = value.toString();
2518
+ }
2519
+ } else {
2520
+ if (config.type === 'percentage') {
2521
+ if (typeof(value) === 'string' && value.indexOf('%') !== -1) {
2522
+ value = value.replace('%', '');
2523
+ } else {
2524
+ value = adjustPrecision(Number(value) * 100);
2525
+ }
2526
+ } else {
2527
+ if (config.mask.includes(',,M')) {
2528
+ if (typeof(value) === 'string' && value.indexOf('M') !== -1) {
2529
+ value = value.replace('M', '');
2530
+ } else {
2531
+ value = Number(value) / 1000000;
2532
+ }
2533
+ } else if (config.mask.includes(',,,B')) {
2534
+ if (typeof(value) === 'string' && value.indexOf('B') !== -1) {
2535
+ value = value.replace('B', '');
2536
+ } else {
2537
+ value = Number(value) / 1000000000;
2538
+ }
2539
+ }
2540
+ }
2541
+
2542
+ if (typeof(value) === 'string' && isNumber(value)) {
2543
+ value = Number(value);
2544
+ }
2545
+
2546
+ if (typeof value === 'number') {
2547
+ // Temporary value
2548
+ let temp = value;
2549
+
2550
+ if (config.type === 'fraction') {
2551
+ temp = formatFraction(value, config.mask);
2552
+ } else {
2553
+ if (fullMask) {
2554
+ temp = adjustNumberOfDecimalPlaces(config, value);
2555
+
2556
+ if (config.type === 'scientific') {
2557
+ return temp;
2558
+ }
2559
+ }
2560
+ }
2561
+
2562
+ value = toPlainString(temp);
2563
+
2564
+ if (config.decimal === ',') {
2565
+ value = value.replace('.', config.decimal);
2566
+ }
2567
+ }
2568
+ }
2569
+
2570
+ // Process mask
2571
+ let control = Component(value, options, true);
2572
+ // Complement render
2573
+ if (fullMask) {
2574
+ processNumOfPaddingZeros(control);
2575
+ }
27
2576
 
28
- // Excel like dates
29
- const excelInitialTime = Date.UTC(1900, 0, 0);
30
- const excelLeapYearBug = Date.UTC(1900, 1, 29);
31
- const millisecondsPerDay = 86400000;
2577
+ value = getValue(control);
32
2578
 
33
- // Transform in two digits
34
- component.Two = function(value) {
35
- value = '' + value;
36
- if (value.length === 1) {
37
- value = '0' + value;
2579
+ if (options.input && options.input.tagName) {
2580
+ if (options.input.contentEditable) {
2581
+ options.input.textContent = value;
2582
+ } else {
2583
+ options.input.value = value;
2584
+ }
2585
+ focus(options.input);
38
2586
  }
2587
+
39
2588
  return value;
40
2589
  }
41
2590
 
42
- component.isValidDate = function(d) {
43
- return d instanceof Date && !isNaN(d.getTime());
44
- }
2591
+ // Helper to extract date from a string
2592
+ Component.extractDateFromString = function (date, format) {
2593
+ let o = Component(date, { mask: format }, true);
45
2594
 
46
- component.toString = function (date, dateOnly) {
47
- let y = null;
48
- let m = null;
49
- let d = null;
50
- let h = null;
51
- let i = null;
52
- let s = null;
2595
+ // Check if in format Excel (Need difference with format date or type detected is numeric)
2596
+ if (date > 0 && Number(date) == date && (o.values.join("") !== o.value || o.type == "numeric")) {
2597
+ var d = new Date(Math.round((date - 25569) * 86400 * 1000));
2598
+ return d.getFullYear() + "-" + Helpers.two(d.getMonth()) + "-" + Helpers.two(d.getDate()) + ' 00:00:00';
2599
+ }
53
2600
 
54
- if (Array.isArray(date)) {
55
- y = date[0];
56
- m = date[1];
57
- d = date[2];
58
- h = date[3];
59
- i = date[4];
60
- s = date[5];
61
- } else {
62
- if (! date) {
63
- date = new Date();
2601
+ let complete = false;
2602
+
2603
+ if (o.values && o.values.length === o.tokens.length && o.values[o.values.length - 1].length >= o.tokens[o.tokens.length - 1].length) {
2604
+ complete = true;
2605
+ }
2606
+
2607
+ if (o.date[0] && o.date[1] && (o.date[2] || complete)) {
2608
+ if (!o.date[2]) {
2609
+ o.date[2] = 1;
64
2610
  }
65
- y = date.getFullYear();
66
- m = date.getMonth() + 1;
67
- d = date.getDate();
68
- h = date.getHours();
69
- i = date.getMinutes();
70
- s = date.getSeconds();
2611
+
2612
+ return o.date[0] + '-' + Helpers.two(o.date[1]) + '-' + Helpers.two(o.date[2]) + ' ' + Helpers.two(o.date[3]) + ':' + Helpers.two(o.date[4]) + ':' + Helpers.two(o.date[5]);
71
2613
  }
72
2614
 
73
- if (dateOnly === true) {
74
- return component.Two(y) + '-' + component.Two(m) + '-' + component.Two(d);
2615
+ return '';
2616
+ }
2617
+
2618
+ // Tokens
2619
+ const dateTokens = ['DAY', 'WD', 'DDDD', 'DDD', 'DD', 'D', 'Q', 'HH24', 'HH12', 'HH', '\\[H\\]', 'H', 'AM/PM', 'MI', 'SS', 'MS', 'YYYY', 'YYY', 'YY', 'Y', 'MONTH', 'MON', 'MMMMM', 'MMMM', 'MMM', 'MM', 'M', '.'];
2620
+ // All date tokens
2621
+ const allDateTokens = dateTokens.join('|')
2622
+
2623
+ Component.getDateString = function(value, options) {
2624
+ if (! options) {
2625
+ options = {};
2626
+ }
2627
+
2628
+ // Labels
2629
+ let format;
2630
+
2631
+ if (options && typeof(options) == 'object') {
2632
+ if (options.format) {
2633
+ format = options.format;
2634
+ } else if (options.mask) {
2635
+ format = options.mask;
2636
+ }
75
2637
  } else {
76
- return component.Two(y) + '-' + component.Two(m) + '-' + component.Two(d) + ' ' + component.Two(h) + ':' + component.Two(i) + ':' + component.Two(s);
2638
+ format = options;
77
2639
  }
78
- }
79
2640
 
80
- component.toArray = function (value) {
81
- let date = value.split(((value.indexOf('T') !== -1) ? 'T' : ' '));
82
- let time = date[1];
2641
+ if (! format) {
2642
+ format = 'YYYY-MM-DD';
2643
+ }
83
2644
 
84
- date = date[0].split('-');
85
- let y = parseInt(date[0]);
86
- let m = parseInt(date[1]);
87
- let d = parseInt(date[2]);
88
- let h = 0;
89
- let i = 0;
2645
+ format = format.toUpperCase();
90
2646
 
91
- if (time) {
92
- time = time.split(':');
93
- h = parseInt(time[0]);
94
- i = parseInt(time[1]);
2647
+ // Date instance
2648
+ if (value instanceof Date) {
2649
+ value = Helpers.now(value);
2650
+ } else if (isNumber(value)) {
2651
+ value = Helpers.numToDate(value);
95
2652
  }
96
- return [y, m, d, h, i, 0];
97
- }
98
2653
 
99
- component.arrayToStringDate = function(arr) {
100
- return component.toString(arr, true);
101
- }
102
2654
 
103
- component.dateToNum = function(jsDate) {
104
- if (typeof(jsDate) === 'string') {
105
- jsDate = new Date(jsDate + ' GMT+0');
2655
+ // Expression to extract all tokens from the string
2656
+ let e = new RegExp(allDateTokens, 'gi');
2657
+ // Extract
2658
+ let t = format.match(e);
2659
+
2660
+ // Compatibility with Excel
2661
+ fixMinuteToken(t);
2662
+
2663
+ // Object
2664
+ const o = {
2665
+ tokens: t
106
2666
  }
107
- let jsDateInMilliseconds = jsDate.getTime();
108
- if (jsDateInMilliseconds >= excelLeapYearBug) {
109
- jsDateInMilliseconds += millisecondsPerDay;
2667
+
2668
+ // Value
2669
+ if (value) {
2670
+ try {
2671
+ // Data
2672
+ o.data = extractDateAndTime(value);
2673
+
2674
+ if (o.data[1] && o.data[1] > 12) {
2675
+ throw new Error('Invalid date');
2676
+ } else if (o.data[4] && o.data[4] > 59) {
2677
+ throw new Error('Invalid date');
2678
+ } else if (o.data[5] && o.data[5] > 59) {
2679
+ throw new Error('Invalid date');
2680
+ } else if (o.data[0] != null && o.data[1] != null) {
2681
+ let day = new Date(o.data[0], o.data[1], 0).getDate();
2682
+ if (o.data[2] > day) {
2683
+ throw new Error('Invalid date');
2684
+ }
2685
+ }
2686
+
2687
+ // Value
2688
+ o.value = [];
2689
+
2690
+ // Calendar instance
2691
+ let calendar = new Date(o.data[0], o.data[1] - 1, o.data[2], o.data[3], o.data[4], o.data[5]);
2692
+
2693
+ // Get method
2694
+ const get = function (i) {
2695
+ // Token
2696
+ let t = this.tokens[i];
2697
+
2698
+ // Case token
2699
+ let s = t.toUpperCase();
2700
+ let v = null;
2701
+
2702
+ if (s === 'YYYY') {
2703
+ v = this.data[0];
2704
+ } else if (s === 'YYY') {
2705
+ v = this.data[0].substring(1, 4);
2706
+ } else if (s === 'YY') {
2707
+ v = this.data[0].substring(2, 4);
2708
+ } else if (s === 'Y') {
2709
+ v = this.data[0].substring(3, 4);
2710
+ } else if (t === 'MON') {
2711
+ v = Helpers.months[calendar.getMonth()].substr(0, 3).toUpperCase();
2712
+ } else if (t === 'mon') {
2713
+ v = Helpers.months[calendar.getMonth()].substr(0, 3).toLowerCase();
2714
+ } else if (t === 'MONTH') {
2715
+ v = Helpers.months[calendar.getMonth()].toUpperCase();
2716
+ } else if (t === 'month') {
2717
+ v = Helpers.months[calendar.getMonth()].toLowerCase();
2718
+ } else if (s === 'MMMMM') {
2719
+ v = Helpers.months[calendar.getMonth()].substr(0, 1);
2720
+ } else if (s === 'MMMM' || t === 'Month') {
2721
+ v = Helpers.months[calendar.getMonth()];
2722
+ } else if (s === 'MMM' || t == 'Mon') {
2723
+ v = Helpers.months[calendar.getMonth()].substr(0, 3);
2724
+ } else if (s === 'MM') {
2725
+ v = Helpers.two(this.data[1]);
2726
+ } else if (s === 'M') {
2727
+ v = calendar.getMonth() + 1;
2728
+ } else if (t === 'DAY') {
2729
+ v = Helpers.weekdays[calendar.getDay()].toUpperCase();
2730
+ } else if (t === 'day') {
2731
+ v = Helpers.weekdays[calendar.getDay()].toLowerCase();
2732
+ } else if (s === 'DDDD' || t == 'Day') {
2733
+ v = Helpers.weekdays[calendar.getDay()];
2734
+ } else if (s === 'DDD') {
2735
+ v = Helpers.weekdays[calendar.getDay()].substr(0, 3);
2736
+ } else if (s === 'DD') {
2737
+ v = Helpers.two(this.data[2]);
2738
+ } else if (s === 'D') {
2739
+ v = parseInt(this.data[2]);
2740
+ } else if (s === 'Q') {
2741
+ v = Math.floor((calendar.getMonth() + 3) / 3);
2742
+ } else if (s === 'HH24' || s === 'HH') {
2743
+ v = this.data[3]%24;
2744
+ if (this.tokens.indexOf('AM/PM') !== -1) {
2745
+ if (v > 12) {
2746
+ v -= 12;
2747
+ } else if (v == '0' || v == '00') {
2748
+ v = 12;
2749
+ }
2750
+ }
2751
+ v = Helpers.two(v);
2752
+ } else if (s === 'HH12') {
2753
+ v = this.data[3]%24;
2754
+ if (v > 12) {
2755
+ v = Helpers.two(v - 12);
2756
+ } else {
2757
+ v = Helpers.two(v);
2758
+ }
2759
+ } else if (s === 'H') {
2760
+ v = this.data[3]%24;
2761
+ if (this.tokens.indexOf('AM/PM') !== -1) {
2762
+ if (v > 12) {
2763
+ v -= 12;
2764
+ } else if (v == '0' || v == '00') {
2765
+ v = 12;
2766
+ }
2767
+ }
2768
+ } else if (s === '[H]') {
2769
+ v = this.data[3];
2770
+ } else if (s === 'MI') {
2771
+ v = Helpers.two(this.data[4]);
2772
+ } else if (s === 'I') {
2773
+ v = parseInt(this.data[4]);
2774
+ } else if (s === 'SS') {
2775
+ v = Helpers.two(this.data[5]);
2776
+ } else if (s === 'S') {
2777
+ v = parseInt(this.data[5]);
2778
+ } else if (s === 'MS') {
2779
+ v = calendar.getMilliseconds();
2780
+ } else if (s === 'AM/PM') {
2781
+ if (this.data[3] >= 12) {
2782
+ v = 'PM';
2783
+ } else {
2784
+ v = 'AM';
2785
+ }
2786
+ } else if (s === 'WD') {
2787
+ v = Helpers.weekdays[calendar.getDay()];
2788
+ }
2789
+
2790
+ if (v === null) {
2791
+ this.value[i] = this.tokens[i];
2792
+ } else {
2793
+ this.value[i] = v;
2794
+ }
2795
+ }
2796
+
2797
+ for (let i = 0; i < o.tokens.length; i++) {
2798
+ get.call(o, i);
2799
+ }
2800
+
2801
+ value = o.value.join('');
2802
+ } catch (e) {
2803
+ console.log(e)
2804
+ value = '';
2805
+ }
110
2806
  }
111
- jsDateInMilliseconds -= excelInitialTime;
112
2807
 
113
- return jsDateInMilliseconds / millisecondsPerDay;
2808
+ return value;
114
2809
  }
115
2810
 
116
- component.numToDate = function(excelSerialNumber, asString){
117
- if (! excelSerialNumber) {
118
- return '';
2811
+ Component.getDate = function(value, format) {
2812
+ if (! format) {
2813
+ format = 'YYYY-MM-DD';
119
2814
  }
120
2815
 
121
- let jsDateInMilliseconds = excelInitialTime + excelSerialNumber * millisecondsPerDay;
122
- if (jsDateInMilliseconds >= excelLeapYearBug) {
123
- jsDateInMilliseconds -= millisecondsPerDay;
2816
+ let ret = value;
2817
+ if (ret && Number(ret) == ret) {
2818
+ ret = Helpers.numToDate(ret);
124
2819
  }
125
2820
 
126
- const d = new Date(jsDateInMilliseconds);
2821
+ // Try a formatted date
2822
+ if (! Helpers.isValidDateFormat(ret)) {
2823
+ let tmp = Component.extractDateFromString(ret, format);
2824
+ if (tmp) {
2825
+ ret = tmp;
2826
+ }
2827
+ }
127
2828
 
128
- let arr = [
129
- d.getUTCFullYear(),
130
- component.Two(d.getUTCMonth() + 1),
131
- component.Two(d.getUTCDate()),
132
- component.Two(d.getUTCHours()),
133
- component.Two(d.getUTCMinutes()),
134
- component.Two(d.getUTCSeconds()),
135
- ];
2829
+ return Component.getDateString(ret, format);
2830
+ }
136
2831
 
137
- if (asString) {
138
- return component.arrayToStringDate(arr);
139
- } else {
140
- return arr;
2832
+ Component.oninput = function(e, mask) {
2833
+ // Element
2834
+ let element = e.target;
2835
+ // Property
2836
+ let property = 'value';
2837
+ // Get the value of the input
2838
+ if (element.tagName !== 'INPUT') {
2839
+ property = 'textContent';
2840
+ }
2841
+ // Value
2842
+ let value = element[property];
2843
+ // Get the mask
2844
+ if (! mask) {
2845
+ mask = element.getAttribute('data-mask');
2846
+ }
2847
+ // Keep the current caret position
2848
+ let caret = getCaret(element);
2849
+ if (caret) {
2850
+ value = value.substring(0, caret) + hiddenCaret + value.substring(caret);
2851
+ }
2852
+
2853
+ // Run mask
2854
+ let result = Component(value, { mask: mask }, true);
2855
+
2856
+ // New value
2857
+ let newValue = result.values.join('');
2858
+ // Apply the result back to the element
2859
+ if (newValue !== value && ! e.inputType.includes('delete')) {
2860
+ // Set the caret to the position before transformation
2861
+ let caret = newValue.indexOf(hiddenCaret);
2862
+ if (caret !== -1) {
2863
+ // Apply value
2864
+ element[property] = newValue.replace(hiddenCaret, "");
2865
+ // Set caret
2866
+ setCaret.call(element, caret);
2867
+ } else {
2868
+ // Apply value
2869
+ element[property] = newValue;
2870
+ // Make sure the caret is positioned in the end
2871
+ focus(element);
2872
+ }
141
2873
  }
142
2874
  }
143
2875
 
144
- return component;
145
- })();
2876
+ Component.getType = getType;
2877
+
2878
+ Component.adjustPrecision = adjustPrecision;
2879
+
2880
+ return Component;
2881
+ }());
146
2882
 
147
2883
  const isNumber = function (num) {
148
2884
  if (typeof(num) === 'string') {
@@ -156,7 +2892,7 @@ if (! Modal && typeof (require) === 'function') {
156
2892
  */
157
2893
  const views = {
158
2894
  years: function(date) {
159
- let year = date.getFullYear();
2895
+ let year = date.getUTCFullYear();
160
2896
  let result = [];
161
2897
  let start = year % 16;
162
2898
  let complement = 16 - start;
@@ -178,7 +2914,7 @@ if (! Modal && typeof (require) === 'function') {
178
2914
  return result;
179
2915
  },
180
2916
  months: function(date) {
181
- let year = date.getFullYear();
2917
+ let year = date.getUTCFullYear();
182
2918
  let result = [];
183
2919
  for (let i = 0; i < 12; i++) {
184
2920
  let item = {
@@ -199,41 +2935,46 @@ if (! Modal && typeof (require) === 'function') {
199
2935
  return result;
200
2936
  },
201
2937
  days: function(date) {
202
- let year = date.getFullYear();
203
- let month = date.getMonth();
2938
+ let year = date.getUTCFullYear();
2939
+ let month = date.getUTCMonth();
204
2940
  let data = filterData.call(this, year, month);
205
2941
 
206
2942
  // First day
207
- let tmp = new Date(year, month, 1, 0, 0, 0);
208
- let firstDay = tmp.getDay();
2943
+ let tmp = new Date(Date.UTC(year, month, 1, 0, 0, 0));
2944
+ let firstDayOfMonth = tmp.getUTCDay();
2945
+ let firstDayOfWeek = this.startingDay ?? 0;
2946
+
2947
+ // Calculate offset based on desired first day of week
2948
+ // firstDayOfWeek: 0 = Sunday, 1 = Monday, 2 = Tuesday, etc.
2949
+ let offset = (firstDayOfMonth - firstDayOfWeek + 7) % 7;
209
2950
 
210
2951
  let result = [];
211
- for (let i = 1-firstDay; i <= 42-firstDay; i++) {
2952
+ for (let i = -offset; i <= 41-offset; i++) {
212
2953
  // Get the day
213
- tmp = new Date(year, month, i, 0, 0, 0);
2954
+ tmp = new Date(Date.UTC(year, month, i + 1, 0, 0, 0));
214
2955
  // Day
215
- let day = tmp.getDate();
2956
+ let day = tmp.getUTCDate();
216
2957
  // Create the item
217
2958
  let item = {
218
2959
  title: day,
219
- value: i,
220
- number: Helpers.dateToNum(tmp.toString())
2960
+ value: i + 1,
2961
+ number: Helpers.dateToNum(tmp.toISOString().substring(0, 10)),
221
2962
  }
222
2963
  // Add the item to the date
223
2964
  result.push(item);
224
2965
  // Check selections
225
- if (tmp.getMonth() !== month) {
2966
+ if (tmp.getUTCMonth() !== month) {
226
2967
  // Days are not in the current month
227
2968
  item.grey = true;
228
2969
  } else {
229
2970
  // Check for data
230
- let d = [ year, Helpers.Two(month+1), Helpers.Two(day)].join('-');
2971
+ let d = [ year, Helpers.two(month+1), Helpers.two(day)].join('-');
231
2972
  if (data && data[d]) {
232
2973
  item.data = data[d];
233
2974
  }
234
2975
  }
235
2976
  // Month
236
- let m = tmp.getMonth();
2977
+ let m = tmp.getUTCMonth();
237
2978
  // Select cursor
238
2979
  if (this.cursor.y === year && this.cursor.m === m && this.cursor.d === day) {
239
2980
  // Select item
@@ -241,6 +2982,36 @@ if (! Modal && typeof (require) === 'function') {
241
2982
  // Cursor
242
2983
  this.cursor.index = result.length - 1;
243
2984
  }
2985
+ // Valid ranges
2986
+ if (this.validRange) {
2987
+ let current = year + '-' + Helpers.two(m+1) + '-' + Helpers.two(day);
2988
+ let test1;
2989
+ let test2;
2990
+
2991
+ if (typeof this.validRange === 'function') {
2992
+ let ret = this.validRange(day,m,year,item);
2993
+ if (typeof ret !== 'undefined') {
2994
+ item.disabled = ret;
2995
+ }
2996
+ } else {
2997
+ if (! this.validRange[0] || current >= this.validRange[0].substr(0, 10)) {
2998
+ test1 = true;
2999
+ } else {
3000
+ test1 = false;
3001
+ }
3002
+
3003
+ if (! this.validRange[1] || current <= this.validRange[1].substr(0, 10)) {
3004
+ test2 = true;
3005
+ } else {
3006
+ test2 = false;
3007
+ }
3008
+
3009
+ if (! (test1 && test2)) {
3010
+ item.disabled = true;
3011
+ }
3012
+ }
3013
+ }
3014
+
244
3015
  // Select range
245
3016
  if (this.range && this.rangeValues) {
246
3017
  // Mark the start and end points
@@ -267,7 +3038,7 @@ if (! Modal && typeof (require) === 'function') {
267
3038
  let result = [];
268
3039
  for (let i = 0; i < 24; i++) {
269
3040
  let item = {
270
- title: Helpers.Two(i),
3041
+ title: Helpers.two(i),
271
3042
  value: i
272
3043
  };
273
3044
  result.push(item);
@@ -276,9 +3047,9 @@ if (! Modal && typeof (require) === 'function') {
276
3047
  },
277
3048
  minutes: function() {
278
3049
  let result = [];
279
- for (let i = 0; i < 60; i=i+5) {
3050
+ for (let i = 0; i < 60; i++) {
280
3051
  let item = {
281
- title: Helpers.Two(i),
3052
+ title: Helpers.two(i),
282
3053
  value: i
283
3054
  };
284
3055
  result.push(item);
@@ -292,7 +3063,7 @@ if (! Modal && typeof (require) === 'function') {
292
3063
  let data = {};
293
3064
  if (Array.isArray(this.data)) {
294
3065
  this.data.map(function (v) {
295
- let d = year + '-' + Helpers.Two(month + 1);
3066
+ let d = year + '-' + Helpers.two(month + 1);
296
3067
  if (v.date.substring(0, 7) === d) {
297
3068
  if (!data[v.date]) {
298
3069
  data[v.date] = [];
@@ -305,10 +3076,16 @@ if (! Modal && typeof (require) === 'function') {
305
3076
  }
306
3077
 
307
3078
  // Get the short weekdays name
308
- const getWeekdays = function() {
309
- return Helpers.weekdays.map(w => {
3079
+ const getWeekdays = function(firstDayOfWeek) {
3080
+ const reorderedWeekdays = [];
3081
+ for (let i = 0; i < 7; i++) {
3082
+ const dayIndex = (firstDayOfWeek + i) % 7;
3083
+ reorderedWeekdays.push(Helpers.weekdays[dayIndex]);
3084
+ }
3085
+
3086
+ return reorderedWeekdays.map(w => {
310
3087
  return { title: w.substring(0, 1) };
311
- })
3088
+ });
312
3089
  }
313
3090
 
314
3091
  // Define the hump based on the view
@@ -331,21 +3108,33 @@ if (! Modal && typeof (require) === 'function') {
331
3108
  return position;
332
3109
  }
333
3110
 
334
-
335
- const Calendar = function() {
3111
+ const Calendar = function(children, { onchange, onload }) {
336
3112
  let self = this;
337
3113
 
338
- const onchange = self.onchange;
3114
+ // Event
3115
+ let change = self.onchange;
3116
+ self.onchange = null;
3117
+
3118
+ // Decide the type based on the size of the screen
3119
+ let autoType = self.type === 'auto';
339
3120
 
340
3121
  // Weekdays
341
- self.weekdays = getWeekdays();
3122
+ self.weekdays = getWeekdays(self.startingDay ?? 0);
342
3123
 
343
3124
  // Cursor
344
3125
  self.cursor = {};
345
3126
 
3127
+ // Time
3128
+ self.time = !! self.time;
3129
+
346
3130
  // Calendar date
347
3131
  let date = new Date();
348
3132
 
3133
+ // Format
3134
+ if (! self.format) {
3135
+ self.format = 'YYYY-MM-DD';
3136
+ }
3137
+
349
3138
  // Range
350
3139
  self.rangeValues = null;
351
3140
 
@@ -364,10 +3153,15 @@ if (! Modal && typeof (require) === 'function') {
364
3153
  date = d;
365
3154
  // Update the headers of the calendar
366
3155
  let value = d.toISOString().substring(0,10).split('-');
3156
+ let time = d.toISOString().substring(11,19).split(':');
367
3157
  // Update the month label
368
3158
  self.month = Helpers.months[parseInt(value[1])-1];
369
3159
  // Update the year label
370
3160
  self.year = parseInt(value[0]);
3161
+ // Hour
3162
+ self.hour = parseInt(time[0]);
3163
+ self.minute = parseInt(time[1]);
3164
+
371
3165
  // Load data
372
3166
  if (! self.view) {
373
3167
  // Start on the days view will start the data
@@ -379,10 +3173,14 @@ if (! Modal && typeof (require) === 'function') {
379
3173
  }
380
3174
 
381
3175
  const getDate = function() {
382
- let v = [ self.cursor.y, self.cursor.m, self.cursor.d ];
3176
+ let v = [ self.cursor.y, self.cursor.m, self.cursor.d, self.hour, self.minute ];
383
3177
  let d = new Date(Date.UTC(...v));
384
3178
  // Update the headers of the calendar
385
- return d.toISOString().substring(0, 10);
3179
+ if (self.time) {
3180
+ return d.toISOString().substring(0, 19).replace('T', ' ');
3181
+ } else {
3182
+ return d.toISOString().substring(0, 10);
3183
+ }
386
3184
  }
387
3185
 
388
3186
  /**
@@ -400,9 +3198,9 @@ if (! Modal && typeof (require) === 'function') {
400
3198
  let d = new Date(Date.UTC(...v));
401
3199
  // Update cursor controller
402
3200
  self.cursor = {
403
- y: d.getFullYear(),
404
- m: d.getMonth(),
405
- d: d.getDate(),
3201
+ y: d.getUTCFullYear(),
3202
+ m: d.getUTCMonth(),
3203
+ d: d.getUTCDate(),
406
3204
  };
407
3205
  // Update cursor based on the object position
408
3206
  if (s) {
@@ -412,9 +3210,10 @@ if (! Modal && typeof (require) === 'function') {
412
3210
  self.cursor.index = self.options.indexOf(s);
413
3211
  }
414
3212
 
415
- if (typeof (self.onupdate) === 'function') {
416
- self.onupdate(self, renderValue());
417
- }
3213
+ Dispatch.call(self, self.onupdate, 'update', {
3214
+ instance: self,
3215
+ value: d.toISOString(),
3216
+ });
418
3217
 
419
3218
  return d;
420
3219
  }
@@ -427,7 +3226,7 @@ if (! Modal && typeof (require) === 'function') {
427
3226
  */
428
3227
  const updateDate = function(v, position) {
429
3228
  // Current internal date
430
- let value = [date.getFullYear(), date.getMonth(), date.getDate(),0,0,0];
3229
+ let value = [date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), self.hour, self.minute, 0];
431
3230
  // Update internal date
432
3231
  value[position] = v;
433
3232
  // Return new value
@@ -444,13 +3243,13 @@ if (! Modal && typeof (require) === 'function') {
444
3243
  // Update the new internal date
445
3244
  if (self.view === 'days') {
446
3245
  // Select the new internal date
447
- value = updateDate(date.getMonth()+direction, 1);
3246
+ value = updateDate(date.getUTCMonth()+direction, 1);
448
3247
  } else if (self.view === 'months') {
449
3248
  // Select the new internal date
450
- value = updateDate(date.getFullYear()+direction, 0);
3249
+ value = updateDate(date.getUTCFullYear()+direction, 0);
451
3250
  } else if (self.view === 'years') {
452
3251
  // Select the new internal date
453
- value = updateDate(date.getFullYear()+(direction*16), 0);
3252
+ value = updateDate(date.getUTCDate()+(direction*16), 0);
454
3253
  }
455
3254
 
456
3255
  // Update view
@@ -511,46 +3310,6 @@ if (! Modal && typeof (require) === 'function') {
511
3310
  updateRange(self.options[index])
512
3311
  }
513
3312
 
514
- /**
515
- * Handler blur
516
- * @param e
517
- */
518
- const blur = function(e) {
519
- if (self.modal) {
520
- if (!(e.relatedTarget && self.modal.el.contains(e.relatedTarget))) {
521
- if (self.modal.closed === false) {
522
- self.modal.closed = true
523
- }
524
- }
525
- }
526
- }
527
-
528
- /**
529
- * Set the limits of a range
530
- * @param s
531
- */
532
- const setRange = function(s) {
533
- if (self.view === 'days' && self.range) {
534
- let d = getDate();
535
- // Date to number
536
- let number = Helpers.dateToNum(d);
537
- // Start a new range
538
- if (self.rangeValues && (self.rangeValues[0] >= number || self.rangeValues[1])) {
539
- destroyRange();
540
- }
541
- // Range
542
- s.range = true;
543
- // Update range
544
- if (! self.rangeValues) {
545
- s.start = true;
546
- self.rangeValues = [number, null];
547
- } else {
548
- s.end = true;
549
- self.rangeValues[1] = number;
550
- }
551
- }
552
- }
553
-
554
3313
  /**
555
3314
  * Update the visible range
556
3315
  * @param s
@@ -567,6 +3326,12 @@ if (! Modal && typeof (require) === 'function') {
567
3326
  let v = self.options[i].number;
568
3327
  // Update property condition
569
3328
  self.options[i].range = v >= self.rangeValues[0] && v <= number;
3329
+
3330
+ if (v === number) {
3331
+ self.options[i].last = true;
3332
+ } else {
3333
+ self.options[i].last = false;
3334
+ }
570
3335
  }
571
3336
  }
572
3337
  }
@@ -577,12 +3342,20 @@ if (! Modal && typeof (require) === 'function') {
577
3342
  * Destroy the range
578
3343
  */
579
3344
  const destroyRange = function() {
580
- for (let i = 0; i < self.options.length; i++) {
581
- self.options[i].range = false;
582
- self.options[i].start = false;
583
- self.options[i].end = false;
3345
+ if (self.range) {
3346
+ for (let i = 0; i < self.options.length; i++) {
3347
+ if (self.options[i].range !== false) {
3348
+ self.options[i].range = '';
3349
+ }
3350
+ if (self.options[i].start !== false) {
3351
+ self.options[i].start = '';
3352
+ }
3353
+ if (self.options[i].end !== false) {
3354
+ self.options[i].end = '';
3355
+ }
3356
+ }
3357
+ self.rangeValues = null;
584
3358
  }
585
- self.rangeValues = null;
586
3359
  }
587
3360
 
588
3361
  const renderValue = function() {
@@ -593,8 +3366,8 @@ if (! Modal && typeof (require) === 'function') {
593
3366
  value = self.rangeValues;
594
3367
  } else {
595
3368
  value = [
596
- Helpers.numToDate(self.rangeValues[0], true),
597
- Helpers.numToDate(self.rangeValues[1], true)
3369
+ Helpers.numToDate(self.rangeValues[0]).substring(0, 10),
3370
+ Helpers.numToDate(self.rangeValues[1]).substring(0, 10)
598
3371
  ];
599
3372
  }
600
3373
  }
@@ -611,7 +3384,7 @@ if (! Modal && typeof (require) === 'function') {
611
3384
  if (self.range) {
612
3385
  if (v) {
613
3386
  if (! Array.isArray(v)) {
614
- v = v.split(',');
3387
+ v = v.toString().split(',');
615
3388
  }
616
3389
  self.rangeValues = [...v];
617
3390
 
@@ -628,8 +3401,8 @@ if (! Modal && typeof (require) === 'function') {
628
3401
 
629
3402
  let d;
630
3403
  if (v) {
631
- v = isNumber(v) ? Helpers.numToDate(v, true) : v;
632
- d = new Date(v);
3404
+ v = isNumber(v) ? Helpers.numToDate(v) : v;
3405
+ d = new Date(v + ' GMT+0');
633
3406
  }
634
3407
  // if no date is defined
635
3408
  if (! Helpers.isValidDate(d)) {
@@ -637,48 +3410,80 @@ if (! Modal && typeof (require) === 'function') {
637
3410
  }
638
3411
  // Update my index
639
3412
  self.cursor = {
640
- y: d.getFullYear(),
641
- m: d.getMonth(),
642
- d: d.getDate(),
3413
+ y: d.getUTCFullYear(),
3414
+ m: d.getUTCMonth(),
3415
+ d: d.getUTCDate(),
643
3416
  };
644
3417
  // Update the internal calendar date
645
3418
  setDate(d);
646
3419
  }
647
3420
 
648
- let autoInput = null;
649
-
650
3421
  const getInput = function() {
651
3422
  let input = self.input;
652
3423
  if (input && input.current) {
653
3424
  input = input.current;
654
3425
  } else {
655
- if (input === 'auto') {
656
- if (! autoInput) {
657
- autoInput = document.createElement('input');
658
- autoInput.type = 'text';
659
- if (self.class) {
660
- autoInput.class = self.class;
661
- }
662
- if (self.name) {
663
- autoInput.name = self.name;
664
- }
665
- if (self.placeholder) {
666
- autoInput.placeholder = self.placeholder;
667
- }
668
- self.el.parentNode.insertBefore(autoInput, self.el);
669
- }
670
- input = autoInput;
3426
+ if (self.input) {
3427
+ input = self.input;
671
3428
  }
672
3429
  }
3430
+
673
3431
  return input;
674
3432
  }
675
3433
 
3434
+ const update = function() {
3435
+ self.setValue(renderValue());
3436
+ self.close({ origin: 'button' });
3437
+ }
3438
+
3439
+ const onopen = function(modal) {
3440
+ let isEditable = false;
3441
+ let input = getInput();
3442
+ let value = self.value;
3443
+ if (input) {
3444
+ let v;
3445
+ if (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') {
3446
+ isEditable = !input.hasAttribute('readonly') && !input.hasAttribute('disabled');
3447
+ v = input.value;
3448
+ } else if (input.isContentEditable) {
3449
+ isEditable = true;
3450
+ v = input.textContent;
3451
+ }
3452
+
3453
+ let tmp = Mask.extractDateFromString(v, self.format);
3454
+ if (tmp) {
3455
+ value = tmp;
3456
+ }
3457
+ }
3458
+
3459
+ if (! isEditable) {
3460
+ self.content.focus();
3461
+ }
3462
+
3463
+ // Update the internal date values
3464
+ updateValue(value);
3465
+ // Open event
3466
+ Dispatch.call(self, self.onopen, 'open', {
3467
+ instance: self
3468
+ });
3469
+ }
3470
+
3471
+ const onclose = function(modal, origin) {
3472
+ // Cancel range events
3473
+ destroyRange();
3474
+ // Close event
3475
+ Dispatch.call(self, self.onclose, 'close', {
3476
+ instance: self,
3477
+ origin: origin,
3478
+ });
3479
+ }
3480
+
676
3481
  /**
677
3482
  * Select an item with the enter or mouse
678
3483
  * @param {object} e - mouse event
679
3484
  * @param {object} item - selected cell
680
3485
  */
681
- self.select = function(e, item) {
3486
+ const select = function(e, item) {
682
3487
  // Update cursor generic
683
3488
  let value = setCursor(item);
684
3489
  // Based where was the click
@@ -688,18 +3493,99 @@ if (! Modal && typeof (require) === 'function') {
688
3493
  // Back to the days
689
3494
  self.view = 'days';
690
3495
  } else {
691
- if (! self.range) {
692
- self.update();
3496
+ if (! item.disabled) {
3497
+ if (self.range === true) {
3498
+ let d = getDate();
3499
+ // Date to number
3500
+ let number = Helpers.dateToNum(d);
3501
+ // Start a new range
3502
+ if (self.rangeValues && (self.rangeValues[0] >= number || self.rangeValues[1])) {
3503
+ destroyRange();
3504
+ }
3505
+ // Range
3506
+ item.range = true;
3507
+ // Update range
3508
+ if (! self.rangeValues) {
3509
+ item.start = true;
3510
+ self.rangeValues = [number, null];
3511
+ } else {
3512
+ item.end = true;
3513
+ self.rangeValues[1] = number;
3514
+ }
3515
+ } else {
3516
+ update();
3517
+ }
693
3518
  }
694
3519
  }
695
3520
  }
696
3521
 
697
- self.selectRange = function(e, item) {
698
- if (self.view === 'days' && self.range === true) {
699
- // Update cursor generic
700
- setCursor(item);
701
- // Update range
702
- setRange(item);
3522
+ const isTrue = function(v) {
3523
+ return v === true || v === 'true';
3524
+ }
3525
+
3526
+ const events = {
3527
+ focusin: (e) => {
3528
+ if (self.modal && self.isClosed()) {
3529
+ self.open();
3530
+ }
3531
+ },
3532
+ focusout: (e) => {
3533
+ if (self.modal && ! self.isClosed()) {
3534
+ if (! (e.relatedTarget && self.modal.el.contains(e.relatedTarget))) {
3535
+ self.modal.close({ origin: 'focusout' });
3536
+ }
3537
+ }
3538
+ },
3539
+ click: (e) => {
3540
+ if (e.target.classList.contains('lm-calendar-input')) {
3541
+ self.open();
3542
+ }
3543
+ },
3544
+ keydown: (e) => {
3545
+ if (self.modal) {
3546
+ if (e.code === 'ArrowUp' || e.code === 'ArrowDown') {
3547
+ if (! self.isClosed()) {
3548
+ self.content.focus();
3549
+ } else {
3550
+ self.open();
3551
+ }
3552
+ } else if (e.code === 'Enter') {
3553
+ if (! self.isClosed()) {
3554
+ update();
3555
+ } else {
3556
+ self.open();
3557
+ }
3558
+ } else if (e.code === 'Escape') {
3559
+ if (! self.isClosed()) {
3560
+ self.modal.close({origin: 'escape'});
3561
+ }
3562
+ }
3563
+ }
3564
+ },
3565
+ input: (e) => {
3566
+ let input = e.target;
3567
+ if (input.classList.contains('lm-calendar-input')) {
3568
+ let value = null;
3569
+ // Content
3570
+ let content = (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') ? input.value : input.textContent;
3571
+ // Apply mask
3572
+ if (self.format) {
3573
+ Mask.oninput(e, self.format);
3574
+ }
3575
+ // Check if that is a valid date
3576
+ if (Helpers.isValidDateFormat(content)) {
3577
+ value = content;
3578
+ } else {
3579
+ let tmp = Mask.extractDateFromString(content, self.format);
3580
+ if (tmp) {
3581
+ value = tmp;
3582
+ }
3583
+ }
3584
+ // Change the calendar view
3585
+ if (value) {
3586
+ updateValue(value);
3587
+ }
3588
+ }
703
3589
  }
704
3590
  }
705
3591
 
@@ -735,51 +3621,49 @@ if (! Modal && typeof (require) === 'function') {
735
3621
  * Open the modal
736
3622
  */
737
3623
  self.open = function(e) {
738
- if (self.modal && self.modal.closed) {
739
- let input = getInput();
740
- // Open modal
741
- self.modal.closed = false;
742
- // Set the focus on the content to use the keyboard
743
- if (! (input && e.target.getAttribute('readonly') === null)) {
744
- self.content.focus();
3624
+ if (self.modal) {
3625
+ if (autoType) {
3626
+ self.type = window.innerWidth > 640 ? self.type = 'default' : 'picker';
745
3627
  }
746
- // Populate components
747
- self.hours = views.hours();
748
- self.minutes = views.minutes();
749
- // Update the internal date values
750
- updateValue(self.value);
3628
+ self.modal.open();
751
3629
  }
752
3630
  }
753
3631
 
754
3632
  /**
755
3633
  * Close the modal
756
3634
  */
757
- self.close = function() {
758
- if (self.modal && self.modal.closed === false) {
759
- // Close modal
760
- self.modal.closed = true;
761
- // Cancel range events
762
- destroyRange();
3635
+ self.close = function(options) {
3636
+ if (self.modal) {
3637
+ if (options && options.origin) {
3638
+ self.modal.close(options)
3639
+ } else {
3640
+ self.modal.close({ origin: 'button' })
3641
+ }
763
3642
  }
764
3643
  }
765
3644
 
766
- self.reset = function() {
767
- self.setValue('');
768
- self.close();
3645
+ self.isClosed = function() {
3646
+ if (self.modal) {
3647
+ return self.modal.isClosed();
3648
+ }
769
3649
  }
770
3650
 
771
- self.update = function() {
772
- self.setValue(renderValue());
773
- self.close();
3651
+ self.reset = function() {
3652
+ self.setValue('');
3653
+ self.close({ origin: 'button' });
774
3654
  }
775
3655
 
776
3656
  /**
777
3657
  * Change the view
778
3658
  */
779
- self.setView = function() {
780
- let v = this.getAttribute('data-view');
781
- if (v) {
782
- self.view = v;
3659
+ self.setView = function(e) {
3660
+ if (typeof e === 'object') {
3661
+ e = this.getAttribute('data-view');
3662
+ }
3663
+
3664
+ const validViews = ['days', 'months', 'years'];
3665
+ if (validViews.includes(e)) {
3666
+ self.view = e;
783
3667
  }
784
3668
  }
785
3669
 
@@ -788,63 +3672,90 @@ if (! Modal && typeof (require) === 'function') {
788
3672
  * @returns {string}
789
3673
  */
790
3674
  self.getValue = function() {
791
- return self.value;
3675
+ let value = self.value;
3676
+
3677
+ if (isNumber(value)) {
3678
+ if (! isTrue(self.numeric)) {
3679
+ value = Helpers.numToDate(value);
3680
+ }
3681
+ } else {
3682
+ if (isTrue(self.numeric)) {
3683
+ value = Helpers.dateToNum(value);
3684
+ }
3685
+ }
3686
+ return value;
792
3687
  }
793
3688
 
794
3689
  self.setValue = function(v) {
795
- // Update the internal controllers
796
- updateValue(v);
797
3690
  // Destroy range
798
3691
  destroyRange();
799
- // Update input
800
- if (self.input) {
801
- let input = getInput();
802
- input.value = v;
803
- }
804
-
3692
+ // Update the internal controllers
3693
+ updateValue(v);
3694
+ // Events
805
3695
  if (v !== self.value) {
806
3696
  // Update value
807
3697
  self.value = v;
3698
+ // Events
3699
+ Dispatch.call(self, change, 'change', {
3700
+ instance: self,
3701
+ value: self.value,
3702
+ });
3703
+ // Update input
3704
+ let input = getInput();
3705
+ if (input) {
3706
+ if (self.format && v) {
3707
+ if (self.range) {
3708
+ if (v[0]) {
3709
+ v[0] = Mask.render(v[0], self.format);
3710
+ }
3711
+ if (v[1]) {
3712
+ v[1] = Mask.render(v[1], self.format);
3713
+ }
3714
+ } else {
3715
+ v = Mask.render(v, self.format);
3716
+ }
3717
+ }
3718
+ // Update input value
3719
+ input.value = v;
3720
+ // Dispatch event
3721
+ Dispatch.call(input, null, 'change', {
3722
+ instance: self,
3723
+ value: self.value,
3724
+ });
3725
+ }
3726
+ }
3727
+ }
3728
+
3729
+ self.update = update;
3730
+
3731
+ self.onevent = function(e) {
3732
+ if (events[e.type]) {
3733
+ events[e.type](e);
808
3734
  }
809
3735
  }
810
3736
 
811
- self.onchange = function(prop) {
3737
+ onchange(function(prop) {
812
3738
  if (prop === 'view') {
813
3739
  if (typeof(views[self.view]) === 'function') {
814
3740
  // When change the view update the data
815
3741
  self.options = views[self.view].call(self, date);
816
3742
  }
817
3743
  } else if (prop === 'value') {
818
- if (typeof (onchange) === 'function') {
819
- onchange(self, self.value);
820
- }
821
- if (typeof (self.onupdate) === 'function') {
822
- self.onupdate(self, self.value);
823
- }
824
- if (typeof(self.onChange) === 'function') {
825
- let input = getInput();
826
- if (input) {
827
- input.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
828
- }
829
- }
830
-
831
3744
  self.setValue(self.value);
832
- } else if (prop === 'options') {
833
- self.content.focus();
3745
+ } else if (prop === 'startingDay') {
3746
+ self.weekdays = getWeekdays(self.startingDay);
834
3747
  }
835
- }
836
-
837
- self.onload = function() {
838
- // Populate components
839
- self.hours = views.hours();
840
- self.minutes = views.minutes();
3748
+ });
841
3749
 
3750
+ onload(function() {
842
3751
  if (self.type !== "inline") {
843
3752
  // Create modal instance
844
3753
  self.modal = {
845
3754
  width: 300,
846
3755
  closed: true,
847
3756
  focus: false,
3757
+ onopen: onopen,
3758
+ onclose: onclose,
848
3759
  position: 'absolute',
849
3760
  'auto-close': false,
850
3761
  'auto-adjust': true,
@@ -853,16 +3764,31 @@ if (! Modal && typeof (require) === 'function') {
853
3764
  Modal(self.el, self.modal);
854
3765
  }
855
3766
 
3767
+ // Correct casting
3768
+ if (self.range === 'true') {
3769
+ self.range = true;
3770
+ }
3771
+
856
3772
  // Create input controls
857
- if (self.input) {
3773
+ if (self.input && self.initInput !== false) {
3774
+ if (self.input === 'auto') {
3775
+ self.input = document.createElement('input');
3776
+ self.input.type = 'text';
3777
+ self.el.parentNode.insertBefore(self.input, self.el);
3778
+ }
3779
+
858
3780
  let input = getInput();
859
3781
  if (input && input.tagName) {
860
3782
  input.classList.add('lm-input');
861
3783
  input.classList.add('lm-calendar-input');
862
- input.addEventListener('focus', self.open);
863
- input.addEventListener('click', self.open);
864
- input.addEventListener('blur', blur);
865
-
3784
+ input.addEventListener('click', events.click);
3785
+ input.addEventListener('input', events.input);
3786
+ input.addEventListener('keydown', events.keydown);
3787
+ input.addEventListener('focusin', events.focusin);
3788
+ input.addEventListener('focusout', events.focusout);
3789
+ if (self.placeholder) {
3790
+ input.setAttribute('placeholder', self.placeholder);
3791
+ }
866
3792
  if (self.onChange) {
867
3793
  input.addEventListener('change', self.onChange);
868
3794
  }
@@ -883,29 +3809,34 @@ if (! Modal && typeof (require) === 'function') {
883
3809
  * Handler keyboard
884
3810
  * @param {object} e - event
885
3811
  */
886
- self.content.addEventListener('keydown', function(e){
3812
+ self.el.addEventListener('keydown', function(e) {
887
3813
  let prevent = false;
888
3814
  if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
3815
+ if (e.target !== self.content) {
3816
+ self.content.focus();
3817
+ }
889
3818
  self.prev(e);
890
3819
  prevent = true;
891
3820
  } else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
3821
+ if (e.target !== self.content) {
3822
+ self.content.focus();
3823
+ }
892
3824
  self.next(e);
893
3825
  prevent = true;
894
3826
  } else if (e.key === 'Enter') {
895
- // Current view
896
- let view = self.view;
897
- // Select
898
- self.selectRange(e, self.options[self.cursor.index]);
899
- self.select(e, self.options[self.cursor.index]);
900
- // If is range do something different
901
- if (view === 'days' && ! self.range) {
902
- self.update();
3827
+ if (e.target === self.content) {
3828
+ // Item
3829
+ let item = self.options[self.cursor.index];
3830
+ if (item) {
3831
+ // Select
3832
+ select(e, item);
3833
+ prevent = true;
3834
+ }
903
3835
  }
904
- prevent = true;
905
- } else {
906
- if (self.input) {
907
- // TODO: mask
908
- //jSuites.mask(e);
3836
+ } else if (e.key === 'Escape') {
3837
+ if (! self.isClosed()) {
3838
+ self.close({ origin: 'escape' });
3839
+ prevent = true;
909
3840
  }
910
3841
  }
911
3842
 
@@ -928,7 +3859,7 @@ if (! Modal && typeof (require) === 'function') {
928
3859
  }
929
3860
  e.preventDefault();
930
3861
  }
931
- });
3862
+ }, { passive: false });
932
3863
 
933
3864
  /**
934
3865
  * Range handler
@@ -946,33 +3877,37 @@ if (! Modal && typeof (require) === 'function') {
946
3877
  self.el.addEventListener("focusout", (e) => {
947
3878
  let input = getInput();
948
3879
  if (e.relatedTarget !== input && ! self.el.contains(e.relatedTarget)) {
949
- self.close();
3880
+ self.close({ origin: 'focusout' });
950
3881
  }
951
3882
  });
952
- }
3883
+ });
3884
+
3885
+ // Populate components
3886
+ const hours = views.hours();
3887
+ const minutes = views.minutes();
953
3888
 
954
- return `<div class="lm-calendar" :value="self.value" data-grid="{{self.grid}}">
3889
+ return render => render`<div class="lm-calendar" :value="self.value" data-grid="{{self.grid}}" data-type="{{self.type}}" data-disabled="{{self.disabled}}">
955
3890
  <div class="lm-calendar-options">
956
- <button type="button" onclick="self.reset">Reset</button>
957
- <button type="button" onclick="self.update">Done</button>
3891
+ <button type="button" onclick="self.reset">${T('Reset')}</button>
3892
+ <button type="button" onclick="${update}">${T('Done')}</button>
958
3893
  </div>
959
3894
  <div class="lm-calendar-container" data-view="{{self.view}}">
960
3895
  <div class="lm-calendar-header">
961
3896
  <div>
962
3897
  <div class="lm-calendar-labels"><button type="button" onclick="self.setView" data-view="months">{{self.month}}</button> <button type="button" onclick="self.setView" data-view="years">{{self.year}}</button></div>
963
3898
  <div class="lm-calendar-navigation">
964
- <button type="button" class="material-icons lm-ripple" onclick="self.prev" tabindex="0">arrow_drop_up</button>
965
- <button type="button" class="material-icons lm-ripple" onclick="self.next" tabindex="0">arrow_drop_down</button>
3899
+ <button type="button" class="lm-calendar-icon lm-ripple" onclick="self.prev" tabindex="0">expand_less</button>
3900
+ <button type="button" class="lm-calendar-icon lm-ripple" onclick="self.next" tabindex="0">expand_more</button>
966
3901
  </div>
967
3902
  </div>
968
3903
  <div class="lm-calendar-weekdays" :loop="self.weekdays"><div>{{self.title}}</div></div>
969
3904
  </div>
970
3905
  <div class="lm-calendar-content" :loop="self.options" tabindex="0" :ref="self.content">
971
- <div data-start="{{self.start}}" data-end="{{self.end}}" data-range="{{self.range}}" data-event="{{self.data}}" data-grey="{{self.grey}}" data-bold="{{self.bold}}" data-selected="{{self.selected}}" onclick="self.parent.select" onmousedown="self.parent.selectRange">{{self.title}}</div>
3906
+ <div data-start="{{self.start}}" data-end="{{self.end}}" data-last="{{self.last}}" data-range="{{self.range}}" data-event="{{self.data}}" data-grey="{{self.grey}}" data-bold="{{self.bold}}" data-selected="{{self.selected}}" data-disabled="{{self.disabled}}" onclick="${select}">{{self.title}}</div>
972
3907
  </div>
973
3908
  <div class="lm-calendar-footer" data-visible="{{self.footer}}">
974
- <div class="lm-calendar-time" data-visible="{{self.time}}"><select :loop="self.hours"><option value="{{self.value}}">{{self.title}}</option></select>:<select :loop="self.minutes"><option value="{{self.value}}">{{self.title}}</option></select></div>
975
- <div class="lm-calendar-update"><input type="button" value="Update" onclick="self.update" class="lm-ripple"></div>
3909
+ <div class="lm-calendar-time" data-visible="{{self.time}}"><select :loop="${hours}" :bind="self.hour" class="lm-calendar-control"><option value="{{self.value}}">{{self.title}}</option></select>:<select :loop="${minutes}" :bind="self.minute" class="lm-calendar-control"><option value="{{self.value}}">{{self.title}}</option></select></div>
3910
+ <div class="lm-calendar-update"><input type="button" value="${T('Update')}" onclick="${update}" class="lm-ripple"></div>
976
3911
  </div>
977
3912
  </div>
978
3913
  </div>`