@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.
- package/dist/index.js +4012 -0
- 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
|
+
})));
|