@jsuites/utils 6.0.0

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