@lemonadejs/calendar 5.3.0 → 5.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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
- 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
163
+ for (let i = 0; i < 12; i++) {
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
+ }
419
+
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
+ }
3150
430
 
3151
- /**
3152
- * Update the internal date
3153
- * @param {Date|string|number[]} d instance of Date
3154
- *
3155
- */
3156
- const setDate = function(d) {
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,99 @@ 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
+ if (isTrue(self.range)) {
521
+ // Start a new range
522
+ if (self.rangeValues && (self.rangeValues[0] >= s.number || self.rangeValues[1])) {
523
+ destroyRange();
524
+ }
525
+ // Range
526
+ s.range = true;
527
+ // Update range
528
+ if (! self.rangeValues) {
529
+ s.start = true;
530
+ self.rangeValues = [s.number, null];
531
+ } else {
532
+ s.end = true;
533
+ self.rangeValues[1] = s.number;
534
+ }
535
+ setCursor(s);
536
+ } else {
537
+ setCursor(s);
538
+
539
+ update();
540
+ }
541
+ }
542
+ }
543
+
544
+ // Update Calendar
545
+ const update = function(e) {
546
+ self.setValue(getValue());
547
+ self.close({ origin: 'button' });
548
+ }
3240
549
 
3241
- return d;
550
+ const reset = function() {
551
+ self.setValue('');
552
+ self.close({ origin: 'button' });
3242
553
  }
3243
554
 
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
555
  const updateDate = function(v, position) {
3251
556
  // Current internal date
3252
557
  let value = [date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), self.hour, self.minute, 0];
@@ -3256,11 +561,11 @@ if (! Modal && typeof (require) === 'function') {
3256
561
  return value;
3257
562
  }
3258
563
 
3259
- /**
3260
- * This method move the data from the view up or down
3261
- * @param direction
3262
- */
3263
564
  const move = function(direction) {
565
+ // Reset visual cursor
566
+ resetCursor();
567
+
568
+ // Value
3264
569
  let value;
3265
570
 
3266
571
  // Update the new internal date
@@ -3272,71 +577,73 @@ if (! Modal && typeof (require) === 'function') {
3272
577
  value = updateDate(date.getUTCFullYear()+direction, 0);
3273
578
  } else if (self.view === 'years') {
3274
579
  // Select the new internal date
3275
- value = updateDate(date.getUTCDate()+(direction*16), 0);
580
+ value = updateDate(date.getUTCFullYear()+(direction*16), 0);
3276
581
  }
3277
582
 
3278
583
  // Update view
3279
- if (value) {
3280
- setDate(value);
3281
- }
584
+ setDate(value);
585
+
586
+ // Reload content of the view
587
+ reloadView();
3282
588
  }
3283
589
 
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 ]);
590
+ const getJump = function(e) {
591
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
592
+ return self.view === 'days' ? 7 : 4;
3297
593
  }
3298
594
 
3299
- // Jump to the index
3300
- let index = self.cursor.index + direction;
595
+ return 1;
596
+ }
3301
597
 
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
- }
598
+ const prev = function(e) {
599
+ if (e && e.type === 'keydown') {
600
+ // Current index
601
+ let total = self.options.length;
602
+ let position = self.options.indexOf(self.cursor.current) - getJump(e);
603
+ if (position < 0) {
604
+ // Next month
605
+ move(-1);
606
+ // New position
607
+ position = total + position;
3323
608
  }
609
+ // Update cursor
610
+ setCursor(self.options[position])
611
+ } else {
612
+ move(-1);
613
+ }
614
+ }
3324
615
 
3325
- // Move the data up or down
3326
- move(direction > 0 ? 1 : -1);
616
+ const next = function(e) {
617
+ if (e && e.type === 'keydown') {
618
+ // Current index
619
+ let total = self.options.length;
620
+ let position = self.options.indexOf(self.cursor.current) + getJump(e);
621
+ if (position >= total) {
622
+ // Next month
623
+ move(1);
624
+ // New position
625
+ position = position - total;
626
+ }
627
+ // Update cursor
628
+ setCursor(self.options[position])
629
+ } else {
630
+ move(1);
3327
631
  }
632
+ }
3328
633
 
3329
- // Update the date based on the click
3330
- setCursor(self.options[index]);
634
+ const getInput = function() {
635
+ let input = self.input;
636
+ if (input && input.current) {
637
+ input = input.current;
638
+ } else {
639
+ if (self.input) {
640
+ input = self.input;
641
+ }
642
+ }
3331
643
 
3332
- // Update ranges
3333
- updateRange(self.options[index])
644
+ return input;
3334
645
  }
3335
646
 
3336
- /**
3337
- * Update the visible range
3338
- * @param s
3339
- */
3340
647
  const updateRange = function(s) {
3341
648
  if (self.range && self.view === 'days' && self.rangeValues) {
3342
649
  // Creating a range
@@ -3345,148 +652,100 @@ if (! Modal && typeof (require) === 'function') {
3345
652
  if (number) {
3346
653
  // Update range properties
3347
654
  for (let i = 0; i < self.options.length; i++) {
3348
- // Item number
3349
655
  let v = self.options[i].number;
3350
656
  // Update property condition
3351
657
  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
- }
658
+ self.options[i].last = (v === number);
3358
659
  }
3359
660
  }
3360
661
  }
3361
662
  }
3362
663
  }
3363
664
 
3364
- /**
3365
- * Destroy the range
3366
- */
3367
665
  const destroyRange = function() {
3368
666
  if (self.range) {
3369
667
  for (let i = 0; i < self.options.length; i++) {
3370
668
  if (self.options[i].range !== false) {
3371
- self.options[i].range = '';
669
+ self.options[i].range = false;
3372
670
  }
3373
671
  if (self.options[i].start !== false) {
3374
- self.options[i].start = '';
672
+ self.options[i].start = false;
3375
673
  }
3376
674
  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]);
675
+ self.options[i].end = false;
3416
676
  }
3417
- if (v[1] && typeof(v[1]) === 'string' && v[1].indexOf('-')) {
3418
- self.rangeValues[1] = Helpers.dateToNum(v[1]);
677
+ if (self.options[i].last !== false) {
678
+ self.options[i].last = false;
3419
679
  }
3420
-
3421
- v = v[0];
3422
680
  }
681
+ self.rangeValues = null;
3423
682
  }
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
683
  }
3443
684
 
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;
685
+ const render = function(v) {
686
+ if (v) {
687
+ if (! Array.isArray(v)) {
688
+ v = v.toString().split(',');
3451
689
  }
3452
- }
3453
690
 
3454
- return input;
691
+ v = v.map(entry => {
692
+ return Mask.render(entry, self.format || 'YYYY-MM-DD');
693
+ }).join(',');
694
+ }
695
+ return v;
3455
696
  }
3456
697
 
3457
- const update = function() {
3458
- self.setValue(renderValue());
3459
- self.close({ origin: 'button' });
698
+ const normalize = function(v) {
699
+ if (! Array.isArray(v)) {
700
+ v = v.toString().split(',');
701
+ }
702
+
703
+ return v.map(item => {
704
+ if (Number(item) == item) {
705
+ return Helpers.numToDate(item);
706
+ } else {
707
+ if (Helpers.isValidDateFormat(item)) {
708
+ return item;
709
+ } else if (self.format) {
710
+ let tmp = Mask.extractDateFromString(item, self.format);
711
+ if (tmp) {
712
+ return tmp;
713
+ }
714
+ }
715
+ }
716
+ })
3460
717
  }
3461
718
 
3462
- const onopen = function(modal) {
3463
- let isEditable = false;
719
+ const extractValueFromInput = function() {
3464
720
  let input = getInput();
3465
- let value = self.value;
3466
721
  if (input) {
3467
- let v = value;
722
+ let v;
3468
723
  if (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') {
3469
- isEditable = !input.hasAttribute('readonly') && !input.hasAttribute('disabled');
3470
724
  v = input.value;
3471
725
  } else if (input.isContentEditable) {
3472
- isEditable = true;
3473
726
  v = input.textContent;
3474
727
  }
3475
-
3476
- let ret = v;
3477
- if (ret && Number(ret) == ret) {
3478
- ret = Helpers.numToDate(ret);
728
+ if (v) {
729
+ return normalize(v).join(',');
3479
730
  }
731
+ return v;
732
+ }
733
+ }
3480
734
 
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
- }
735
+ const onopen = function() {
736
+ let isEditable = false;
737
+ let value = self.value;
738
+
739
+ let input = getInput();
740
+ if (input) {
741
+ if (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') {
742
+ isEditable = !input.hasAttribute('readonly') && !input.hasAttribute('disabled');
743
+ } else if (input.isContentEditable) {
744
+ isEditable = true;
3487
745
  }
3488
746
 
3489
- if (ret !== value) {
747
+ let ret = extractValueFromInput();
748
+ if (ret && ret !== value) {
3490
749
  value = ret;
3491
750
  }
3492
751
  }
@@ -3496,7 +755,8 @@ if (! Modal && typeof (require) === 'function') {
3496
755
  }
3497
756
 
3498
757
  // Update the internal date values
3499
- updateValue(value);
758
+ setValue(value);
759
+
3500
760
  // Open event
3501
761
  Dispatch.call(self, self.onopen, 'open', {
3502
762
  instance: self
@@ -3513,51 +773,29 @@ if (! Modal && typeof (require) === 'function') {
3513
773
  });
3514
774
  }
3515
775
 
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
- }
776
+ const dispatchOnChangeEvent = function() {
777
+ // Destroy range
778
+ destroyRange();
779
+ // Update the internal controllers
780
+ setValue(self.value);
781
+ // Events
782
+ Dispatch.call(self, change, 'change', {
783
+ instance: self,
784
+ value: self.value,
785
+ });
786
+ // Update input
787
+ let input = getInput();
788
+ if (input) {
789
+ // Update input value
790
+ input.value = render(self.value);
791
+ // Dispatch event
792
+ Dispatch.call(input, null, 'change', {
793
+ instance: self,
794
+ value: self.value,
795
+ });
3554
796
  }
3555
797
  }
3556
798
 
3557
- const isTrue = function(v) {
3558
- return v === true || v === 'true';
3559
- }
3560
-
3561
799
  const events = {
3562
800
  focusin: (e) => {
3563
801
  if (self.modal && self.isClosed()) {
@@ -3600,201 +838,35 @@ if (! Modal && typeof (require) === 'function') {
3600
838
  input: (e) => {
3601
839
  let input = e.target;
3602
840
  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;
841
+ if (! isTrue(self.range)) {
842
+ // TODO: process with range
843
+ // Apply mask
844
+ if (self.format) {
845
+ Mask.oninput(e, self.format);
3617
846
  }
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);
847
+ let value = null;
848
+ // Content
849
+ let content = (input.tagName === 'INPUT' || input.tagName === 'TEXTAREA') ? input.value : input.textContent;
850
+ // Check if that is a valid date
851
+ if (Helpers.isValidDateFormat(content)) {
852
+ value = content;
853
+ } else if (self.format) {
854
+ let tmp = Mask.extractDateFromString(content, self.format);
855
+ if (tmp) {
856
+ value = tmp;
857
+ }
3750
858
  }
3751
- if (v[1]) {
3752
- v[1] = Mask.render(v[1], self.format);
859
+ // Change the calendar view
860
+ if (value) {
861
+ setValue(value);
3753
862
  }
3754
- } else {
3755
- v = Mask.render(v, self.format);
3756
863
  }
3757
864
  }
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
865
  }
3789
- });
3790
-
3791
- // Input
3792
- if (self.input === 'auto') {
3793
- self.input = document.createElement('input');
3794
- self.input.type = 'text';
3795
866
  }
3796
867
 
3797
- onload(function() {
868
+ // Onload
869
+ onload(() => {
3798
870
  if (self.type !== "inline") {
3799
871
  // Create modal instance
3800
872
  self.modal = {
@@ -3811,11 +883,6 @@ if (! Modal && typeof (require) === 'function') {
3811
883
  Modal(self.el, self.modal);
3812
884
  }
3813
885
 
3814
- // Correct casting
3815
- if (self.range === 'true') {
3816
- self.range = true;
3817
- }
3818
-
3819
886
  let ret;
3820
887
 
3821
888
  // Create input controls
@@ -3842,24 +909,26 @@ if (! Modal && typeof (require) === 'function') {
3842
909
 
3843
910
  // Retrieve the value
3844
911
  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);
912
+ input.value = render(self.value);
913
+ } else {
914
+ let value = extractValueFromInput();
915
+ if (value && value !== self.value) {
916
+ ret = value;
3850
917
  }
3851
- ret = input.value;
3852
918
  }
3853
919
  }
3854
920
  }
3855
921
 
3856
922
  // Update the internal date values
3857
923
  if (ret) {
3858
- self.value = ret;
924
+ self.setValue(ret);
3859
925
  } else {
3860
- updateValue(self.value);
926
+ setValue(self.value);
3861
927
  }
3862
928
 
929
+ // Reload view
930
+ reloadView(true);
931
+
3863
932
  /**
3864
933
  * Handler keyboard
3865
934
  * @param {object} e - event
@@ -3870,21 +939,20 @@ if (! Modal && typeof (require) === 'function') {
3870
939
  if (e.target !== self.content) {
3871
940
  self.content.focus();
3872
941
  }
3873
- self.prev(e);
942
+ prev(e);
3874
943
  prevent = true;
3875
944
  } else if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
3876
945
  if (e.target !== self.content) {
3877
946
  self.content.focus();
3878
947
  }
3879
- self.next(e);
948
+ next(e);
3880
949
  prevent = true;
3881
950
  } else if (e.key === 'Enter') {
3882
951
  if (e.target === self.content) {
3883
952
  // Item
3884
- let item = self.options[self.cursor.index];
3885
- if (item) {
953
+ if (self.cursor.current) {
3886
954
  // Select
3887
- select(e, item);
955
+ select(e, self.cursor.current);
3888
956
  prevent = true;
3889
957
  }
3890
958
  }
@@ -3908,9 +976,9 @@ if (! Modal && typeof (require) === 'function') {
3908
976
  self.content.addEventListener('wheel', function(e){
3909
977
  if (self.wheel !== false) {
3910
978
  if (e.deltaY < 0) {
3911
- self.prev();
979
+ prev(e);
3912
980
  } else {
3913
- self.next();
981
+ next(e);
3914
982
  }
3915
983
  e.preventDefault();
3916
984
  }
@@ -3937,22 +1005,103 @@ if (! Modal && typeof (require) === 'function') {
3937
1005
  });
3938
1006
  });
3939
1007
 
3940
- // Populate components
3941
- const hours = views.hours();
3942
- const minutes = views.minutes();
1008
+ onchange((prop) => {
1009
+ if (prop === 'view') {
1010
+ reloadView(true);
1011
+ } else if (prop === 'startingDay') {
1012
+ self.weekdays = getWeekdays(self.startingDay ?? 0);
1013
+ } else if (prop === 'value') {
1014
+ dispatchOnChangeEvent();
1015
+ }
1016
+ })
1017
+
1018
+ // Tracking variables
1019
+ track('value');
1020
+
1021
+ // Public methods
1022
+
1023
+ self.open = function(e) {
1024
+ if (self.modal) {
1025
+ if (self.type === 'auto') {
1026
+ self.type = window.innerWidth > 640 ? self.type = 'default' : 'picker';
1027
+ }
1028
+ self.modal.open();
1029
+ }
1030
+ }
1031
+
1032
+ self.close = function(options) {
1033
+ if (self.modal) {
1034
+ if (options && options.origin) {
1035
+ self.modal.close(options)
1036
+ } else {
1037
+ self.modal.close({ origin: 'button' })
1038
+ }
1039
+ }
1040
+ }
1041
+
1042
+ self.isClosed = function() {
1043
+ if (self.modal) {
1044
+ return self.modal.isClosed();
1045
+ }
1046
+ }
1047
+
1048
+ self.getValue = function() {
1049
+ return self.value;
1050
+ }
1051
+
1052
+ self.setValue = function(v) {
1053
+ // Update value
1054
+ if (v) {
1055
+ let ret = normalize(v);
1056
+ if (isTrue(self.numeric)) {
1057
+ ret = ret.map(entry => {
1058
+ return Helpers.dateToNum(entry);
1059
+ })
1060
+ }
1061
+
1062
+ if (! Array.isArray(v)) {
1063
+ ret = ret.join(',');
1064
+ }
1065
+
1066
+ if (ret == Number(ret)) {
1067
+ ret = Number(ret);
1068
+ }
1069
+
1070
+ v = ret;
1071
+ }
1072
+
1073
+ // Events
1074
+ if (v !== self.value) {
1075
+ self.value = v;
1076
+ }
1077
+ }
1078
+
1079
+ self.onevent = function(e) {
1080
+ if (events[e.type]) {
1081
+ events[e.type](e);
1082
+ }
1083
+ }
1084
+
1085
+ self.update = update;
1086
+ self.next = next;
1087
+ self.prev = prev;
1088
+ self.reset = reset;
1089
+ self.setView = setView;
1090
+ self.helpers = Helpers;
1091
+ self.helpers.getDate = Mask.getDate;
3943
1092
 
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}}">
1093
+ 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
1094
  <div class="lm-calendar-options">
3946
- <button type="button" onclick="self.reset">${T('Reset')}</button>
1095
+ <button type="button" onclick="${reset}">${T('Reset')}</button>
3947
1096
  <button type="button" onclick="${update}">${T('Done')}</button>
3948
1097
  </div>
3949
1098
  <div class="lm-calendar-container" data-view="{{self.view}}">
3950
1099
  <div class="lm-calendar-header">
3951
1100
  <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>
1101
+ <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
1102
  <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>
1103
+ <button type="button" class="lm-calendar-icon lm-ripple" onclick="${prev}" tabindex="0">expand_less</button>
1104
+ <button type="button" class="lm-calendar-icon lm-ripple" onclick="${next}" tabindex="0">expand_more</button>
3956
1105
  </div>
3957
1106
  </div>
3958
1107
  <div class="lm-calendar-weekdays" :loop="self.weekdays"><div>{{self.title}}</div></div>