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