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