@lemonadejs/calendar 3.6.0 → 5.2.0

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