@bunnix/components 0.9.2 → 0.9.4
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/@types/index.d.ts +203 -5
- package/README.md +73 -103
- package/package.json +1 -1
- package/src/components/ComboBox.mjs +8 -2
- package/src/components/DatePicker.mjs +234 -57
- package/src/components/Dialog.mjs +1 -1
- package/src/components/InputField.mjs +55 -6
- package/src/components/ProgressBar.mjs +81 -0
- package/src/components/TimePicker.mjs +255 -65
- package/src/index.mjs +4 -0
- package/src/styles/controls.css +72 -0
- package/src/utils/maskUtils.mjs +569 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mask Utility for Input Fields
|
|
3
|
+
* Supports various input masks including date, time, email, currency, and custom patterns
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Apply a mask to a value based on the mask type or pattern
|
|
8
|
+
* @param {string} value - The raw input value
|
|
9
|
+
* @param {string|object} mask - The mask type or configuration
|
|
10
|
+
* @returns {string} - The masked value
|
|
11
|
+
*/
|
|
12
|
+
export function applyMask(value, mask) {
|
|
13
|
+
if (!mask) return value || "";
|
|
14
|
+
|
|
15
|
+
// Convert value to string and handle empty/undefined/null
|
|
16
|
+
const stringValue = String(value ?? "");
|
|
17
|
+
if (!stringValue) return "";
|
|
18
|
+
|
|
19
|
+
// Handle object mask configuration
|
|
20
|
+
if (typeof mask === 'object') {
|
|
21
|
+
const { type, pattern, options } = mask;
|
|
22
|
+
if (type) {
|
|
23
|
+
return applyMaskByType(stringValue, type, options);
|
|
24
|
+
}
|
|
25
|
+
if (pattern) {
|
|
26
|
+
return applyCustomMask(stringValue, pattern);
|
|
27
|
+
}
|
|
28
|
+
return stringValue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Handle string mask type
|
|
32
|
+
return applyMaskByType(stringValue, mask);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Apply mask based on predefined type
|
|
37
|
+
*/
|
|
38
|
+
function applyMaskByType(value, type, options = {}) {
|
|
39
|
+
switch (type) {
|
|
40
|
+
case 'date':
|
|
41
|
+
return applyDateMask(value);
|
|
42
|
+
case 'time':
|
|
43
|
+
return applyTimeMask(value);
|
|
44
|
+
case 'email':
|
|
45
|
+
return applyEmailMask(value);
|
|
46
|
+
case 'currency':
|
|
47
|
+
return applyCurrencyMask(value, options);
|
|
48
|
+
case 'decimal':
|
|
49
|
+
return applyDecimalMask(value, options);
|
|
50
|
+
case 'integer':
|
|
51
|
+
return applyIntegerMask(value);
|
|
52
|
+
case 'phone':
|
|
53
|
+
return applyPhoneMask(value, options);
|
|
54
|
+
case 'phone-br':
|
|
55
|
+
return applyPhoneBRMask(value);
|
|
56
|
+
case 'credit-card':
|
|
57
|
+
return applyCreditCardMask(value);
|
|
58
|
+
case 'cpf':
|
|
59
|
+
return applyCPFMask(value);
|
|
60
|
+
case 'cnpj':
|
|
61
|
+
return applyCNPJMask(value);
|
|
62
|
+
case 'cep':
|
|
63
|
+
return applyCEPMask(value);
|
|
64
|
+
default:
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Date mask: DD/MM/YYYY
|
|
71
|
+
*/
|
|
72
|
+
function applyDateMask(value) {
|
|
73
|
+
const digits = value.replace(/\D/g, "");
|
|
74
|
+
let masked = "";
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < digits.length && i < 8; i++) {
|
|
77
|
+
if (i === 2 || i === 4) {
|
|
78
|
+
masked += "/";
|
|
79
|
+
}
|
|
80
|
+
masked += digits[i];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return masked;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Time mask: HH:MM
|
|
88
|
+
*/
|
|
89
|
+
function applyTimeMask(value) {
|
|
90
|
+
const digits = value.replace(/\D/g, "");
|
|
91
|
+
let masked = "";
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < digits.length && i < 4; i++) {
|
|
94
|
+
if (i === 2) {
|
|
95
|
+
masked += ":";
|
|
96
|
+
}
|
|
97
|
+
masked += digits[i];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return masked;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Email mask: lowercase, no spaces
|
|
105
|
+
*/
|
|
106
|
+
function applyEmailMask(value) {
|
|
107
|
+
return value.toLowerCase().replace(/\s/g, "");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Currency mask: $ 1,234.56
|
|
112
|
+
*/
|
|
113
|
+
function applyCurrencyMask(value, options = {}) {
|
|
114
|
+
const {
|
|
115
|
+
prefix = "$",
|
|
116
|
+
thousandsSeparator = ",",
|
|
117
|
+
decimalSeparator = ".",
|
|
118
|
+
decimalPlaces = 2
|
|
119
|
+
} = options;
|
|
120
|
+
|
|
121
|
+
// Ensure value is a string
|
|
122
|
+
const stringValue = String(value ?? "");
|
|
123
|
+
|
|
124
|
+
// Remove everything except digits
|
|
125
|
+
let digits = stringValue.replace(/[^\d]/g, "");
|
|
126
|
+
|
|
127
|
+
if (digits === "") return "";
|
|
128
|
+
|
|
129
|
+
// Convert to number and format
|
|
130
|
+
const number = parseInt(digits, 10);
|
|
131
|
+
const formatted = (number / Math.pow(10, decimalPlaces)).toFixed(decimalPlaces);
|
|
132
|
+
|
|
133
|
+
// Split integer and decimal parts
|
|
134
|
+
const [integer, decimal] = formatted.split(".");
|
|
135
|
+
|
|
136
|
+
// Add thousands separator
|
|
137
|
+
const withSeparators = integer.replace(/\B(?=(\d{3})+(?!\d))/g, thousandsSeparator);
|
|
138
|
+
|
|
139
|
+
return `${prefix} ${withSeparators}${decimalSeparator}${decimal}`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Decimal mask: 123.45
|
|
144
|
+
*/
|
|
145
|
+
function applyDecimalMask(value, options = {}) {
|
|
146
|
+
const { decimalPlaces = 2, allowNegative = false } = options;
|
|
147
|
+
|
|
148
|
+
let cleaned = value.replace(/[^\d.-]/g, "");
|
|
149
|
+
|
|
150
|
+
if (!allowNegative) {
|
|
151
|
+
cleaned = cleaned.replace(/-/g, "");
|
|
152
|
+
} else {
|
|
153
|
+
// Only allow one minus at the start
|
|
154
|
+
const negative = cleaned.startsWith("-");
|
|
155
|
+
cleaned = cleaned.replace(/-/g, "");
|
|
156
|
+
if (negative) cleaned = "-" + cleaned;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Only allow one decimal point
|
|
160
|
+
const parts = cleaned.split(".");
|
|
161
|
+
if (parts.length > 2) {
|
|
162
|
+
cleaned = parts[0] + "." + parts.slice(1).join("");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Limit decimal places
|
|
166
|
+
if (parts.length === 2 && parts[1].length > decimalPlaces) {
|
|
167
|
+
cleaned = parts[0] + "." + parts[1].substring(0, decimalPlaces);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return cleaned;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Integer mask: only digits
|
|
175
|
+
*/
|
|
176
|
+
function applyIntegerMask(value) {
|
|
177
|
+
return value.replace(/\D/g, "");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* International phone mask: +1 (234) 567-8900 or custom format
|
|
182
|
+
*/
|
|
183
|
+
function applyPhoneMask(value, options = {}) {
|
|
184
|
+
const { countryCode = "1", format = "us" } = options;
|
|
185
|
+
const digits = value.replace(/\D/g, "");
|
|
186
|
+
|
|
187
|
+
if (digits.length === 0) return "";
|
|
188
|
+
|
|
189
|
+
if (format === "br") {
|
|
190
|
+
return applyPhoneBRMask(value);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Default US/International format
|
|
194
|
+
let masked = "";
|
|
195
|
+
|
|
196
|
+
// Country code
|
|
197
|
+
if (digits.length >= 1) {
|
|
198
|
+
masked = "+" + digits.substring(0, Math.min(digits.length, countryCode.length));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const ccLength = countryCode.length;
|
|
202
|
+
|
|
203
|
+
// Area code
|
|
204
|
+
if (digits.length > ccLength) {
|
|
205
|
+
masked += " (" + digits.substring(ccLength, Math.min(digits.length, ccLength + 3));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (digits.length >= ccLength + 3) {
|
|
209
|
+
masked += ")";
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// First part
|
|
213
|
+
if (digits.length > ccLength + 3) {
|
|
214
|
+
masked += " " + digits.substring(ccLength + 3, Math.min(digits.length, ccLength + 6));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Second part
|
|
218
|
+
if (digits.length > ccLength + 6) {
|
|
219
|
+
masked += "-" + digits.substring(ccLength + 6, Math.min(digits.length, ccLength + 10));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return masked;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Brazilian phone mask: +55 11 99999-9999 (mobile) or +55 11 9999-9999 (landline)
|
|
227
|
+
*/
|
|
228
|
+
function applyPhoneBRMask(value) {
|
|
229
|
+
const digits = value.replace(/\D/g, "");
|
|
230
|
+
let masked = "";
|
|
231
|
+
|
|
232
|
+
if (digits.length === 0) return "";
|
|
233
|
+
|
|
234
|
+
// Country code: +55
|
|
235
|
+
if (digits.length >= 1) {
|
|
236
|
+
masked = "+" + digits.substring(0, Math.min(digits.length, 2));
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Area code (DDD): 11, 21, etc.
|
|
240
|
+
if (digits.length > 2) {
|
|
241
|
+
masked += " " + digits.substring(2, Math.min(digits.length, 4));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Phone number
|
|
245
|
+
if (digits.length > 4) {
|
|
246
|
+
const areaCodeEnd = 4;
|
|
247
|
+
const remaining = digits.substring(areaCodeEnd);
|
|
248
|
+
|
|
249
|
+
// Check if it's a mobile (9 digits) or landline (8 digits)
|
|
250
|
+
// Mobile: 9XXXX-XXXX
|
|
251
|
+
// Landline: XXXX-XXXX
|
|
252
|
+
|
|
253
|
+
if (remaining.length <= 4) {
|
|
254
|
+
// Still typing the first part
|
|
255
|
+
masked += " " + remaining;
|
|
256
|
+
} else if (remaining.length === 5) {
|
|
257
|
+
// Could be either format, show without dash yet
|
|
258
|
+
masked += " " + remaining;
|
|
259
|
+
} else {
|
|
260
|
+
// Determine format based on length
|
|
261
|
+
// If we have 9+ digits after area code, it's mobile
|
|
262
|
+
const isMobile = remaining.length >= 9 || (remaining.length > 5 && remaining[0] === '9');
|
|
263
|
+
|
|
264
|
+
if (isMobile) {
|
|
265
|
+
// Mobile: +55 11 99999-9999
|
|
266
|
+
masked += " " + remaining.substring(0, 5);
|
|
267
|
+
if (remaining.length > 5) {
|
|
268
|
+
masked += "-" + remaining.substring(5, 9);
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
// Landline: +55 11 9999-9999
|
|
272
|
+
masked += " " + remaining.substring(0, 4);
|
|
273
|
+
if (remaining.length > 4) {
|
|
274
|
+
masked += "-" + remaining.substring(4, 8);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return masked;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Credit card mask: 1234 5678 9012 3456
|
|
285
|
+
*/
|
|
286
|
+
function applyCreditCardMask(value) {
|
|
287
|
+
const digits = value.replace(/\D/g, "");
|
|
288
|
+
const groups = [];
|
|
289
|
+
|
|
290
|
+
for (let i = 0; i < digits.length && i < 16; i += 4) {
|
|
291
|
+
groups.push(digits.substring(i, i + 4));
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return groups.join(" ");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Brazilian CPF mask: 123.456.789-01
|
|
299
|
+
*/
|
|
300
|
+
function applyCPFMask(value) {
|
|
301
|
+
const digits = value.replace(/\D/g, "");
|
|
302
|
+
let masked = "";
|
|
303
|
+
|
|
304
|
+
for (let i = 0; i < digits.length && i < 11; i++) {
|
|
305
|
+
if (i === 3 || i === 6) {
|
|
306
|
+
masked += ".";
|
|
307
|
+
} else if (i === 9) {
|
|
308
|
+
masked += "-";
|
|
309
|
+
}
|
|
310
|
+
masked += digits[i];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return masked;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Brazilian CNPJ mask: 12.345.678/0001-90
|
|
318
|
+
*/
|
|
319
|
+
function applyCNPJMask(value) {
|
|
320
|
+
const digits = value.replace(/\D/g, "");
|
|
321
|
+
let masked = "";
|
|
322
|
+
|
|
323
|
+
for (let i = 0; i < digits.length && i < 14; i++) {
|
|
324
|
+
if (i === 2 || i === 5) {
|
|
325
|
+
masked += ".";
|
|
326
|
+
} else if (i === 8) {
|
|
327
|
+
masked += "/";
|
|
328
|
+
} else if (i === 12) {
|
|
329
|
+
masked += "-";
|
|
330
|
+
}
|
|
331
|
+
masked += digits[i];
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return masked;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Brazilian CEP mask: 12345-678
|
|
339
|
+
*/
|
|
340
|
+
function applyCEPMask(value) {
|
|
341
|
+
const digits = value.replace(/\D/g, "");
|
|
342
|
+
let masked = "";
|
|
343
|
+
|
|
344
|
+
for (let i = 0; i < digits.length && i < 8; i++) {
|
|
345
|
+
if (i === 5) {
|
|
346
|
+
masked += "-";
|
|
347
|
+
}
|
|
348
|
+
masked += digits[i];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return masked;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Apply custom mask pattern
|
|
356
|
+
* Pattern syntax:
|
|
357
|
+
* - 9: digit
|
|
358
|
+
* - A: letter
|
|
359
|
+
* - *: alphanumeric
|
|
360
|
+
* - Other characters are literals
|
|
361
|
+
*
|
|
362
|
+
* Example: "999.999.999-99" for CPF
|
|
363
|
+
*/
|
|
364
|
+
function applyCustomMask(value, pattern) {
|
|
365
|
+
let masked = "";
|
|
366
|
+
let valueIndex = 0;
|
|
367
|
+
|
|
368
|
+
for (let i = 0; i < pattern.length && valueIndex < value.length; i++) {
|
|
369
|
+
const patternChar = pattern[i];
|
|
370
|
+
const valueChar = value[valueIndex];
|
|
371
|
+
|
|
372
|
+
if (patternChar === '9') {
|
|
373
|
+
// Digit only
|
|
374
|
+
if (/\d/.test(valueChar)) {
|
|
375
|
+
masked += valueChar;
|
|
376
|
+
valueIndex++;
|
|
377
|
+
} else {
|
|
378
|
+
valueIndex++;
|
|
379
|
+
i--; // Retry with next value character
|
|
380
|
+
}
|
|
381
|
+
} else if (patternChar === 'A') {
|
|
382
|
+
// Letter only
|
|
383
|
+
if (/[a-zA-Z]/.test(valueChar)) {
|
|
384
|
+
masked += valueChar;
|
|
385
|
+
valueIndex++;
|
|
386
|
+
} else {
|
|
387
|
+
valueIndex++;
|
|
388
|
+
i--; // Retry with next value character
|
|
389
|
+
}
|
|
390
|
+
} else if (patternChar === '*') {
|
|
391
|
+
// Alphanumeric
|
|
392
|
+
if (/[a-zA-Z0-9]/.test(valueChar)) {
|
|
393
|
+
masked += valueChar;
|
|
394
|
+
valueIndex++;
|
|
395
|
+
} else {
|
|
396
|
+
valueIndex++;
|
|
397
|
+
i--; // Retry with next value character
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
// Literal character
|
|
401
|
+
masked += patternChar;
|
|
402
|
+
if (valueChar === patternChar) {
|
|
403
|
+
valueIndex++;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return masked;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Validate a masked value
|
|
413
|
+
* @param {string} value - The masked value
|
|
414
|
+
* @param {string|object} mask - The mask type or configuration
|
|
415
|
+
* @returns {boolean} - Whether the value is valid
|
|
416
|
+
*/
|
|
417
|
+
export function validateMask(value, mask) {
|
|
418
|
+
if (!mask || !value) return true;
|
|
419
|
+
|
|
420
|
+
const maskType = typeof mask === 'object' ? mask.type : mask;
|
|
421
|
+
|
|
422
|
+
switch (maskType) {
|
|
423
|
+
case 'date':
|
|
424
|
+
return validateDate(value);
|
|
425
|
+
case 'time':
|
|
426
|
+
return validateTime(value);
|
|
427
|
+
case 'email':
|
|
428
|
+
return validateEmail(value);
|
|
429
|
+
case 'cpf':
|
|
430
|
+
return validateCPF(value);
|
|
431
|
+
case 'cnpj':
|
|
432
|
+
return validateCNPJ(value);
|
|
433
|
+
default:
|
|
434
|
+
return true; // No validation for other types
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Validate date DD/MM/YYYY
|
|
440
|
+
*/
|
|
441
|
+
function validateDate(value) {
|
|
442
|
+
if (value.length !== 10) return false;
|
|
443
|
+
const parts = value.split("/");
|
|
444
|
+
if (parts.length !== 3) return false;
|
|
445
|
+
const day = parseInt(parts[0], 10);
|
|
446
|
+
const month = parseInt(parts[1], 10) - 1;
|
|
447
|
+
const year = parseInt(parts[2], 10);
|
|
448
|
+
if (isNaN(day) || isNaN(month) || isNaN(year)) return false;
|
|
449
|
+
const date = new Date(year, month, day);
|
|
450
|
+
return date.getDate() === day && date.getMonth() === month && date.getFullYear() === year;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Validate time HH:MM
|
|
455
|
+
*/
|
|
456
|
+
function validateTime(value) {
|
|
457
|
+
if (value.length !== 5) return false;
|
|
458
|
+
const parts = value.split(":");
|
|
459
|
+
if (parts.length !== 2) return false;
|
|
460
|
+
const hours = parseInt(parts[0], 10);
|
|
461
|
+
const minutes = parseInt(parts[1], 10);
|
|
462
|
+
return !isNaN(hours) && !isNaN(minutes) && hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Validate email
|
|
467
|
+
*/
|
|
468
|
+
function validateEmail(value) {
|
|
469
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
470
|
+
return emailRegex.test(value);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Validate Brazilian CPF
|
|
475
|
+
*/
|
|
476
|
+
function validateCPF(value) {
|
|
477
|
+
const digits = value.replace(/\D/g, "");
|
|
478
|
+
if (digits.length !== 11) return false;
|
|
479
|
+
|
|
480
|
+
// Check if all digits are the same
|
|
481
|
+
if (/^(\d)\1+$/.test(digits)) return false;
|
|
482
|
+
|
|
483
|
+
// Validate check digits
|
|
484
|
+
let sum = 0;
|
|
485
|
+
for (let i = 0; i < 9; i++) {
|
|
486
|
+
sum += parseInt(digits[i], 10) * (10 - i);
|
|
487
|
+
}
|
|
488
|
+
let checkDigit = 11 - (sum % 11);
|
|
489
|
+
if (checkDigit >= 10) checkDigit = 0;
|
|
490
|
+
if (checkDigit !== parseInt(digits[9], 10)) return false;
|
|
491
|
+
|
|
492
|
+
sum = 0;
|
|
493
|
+
for (let i = 0; i < 10; i++) {
|
|
494
|
+
sum += parseInt(digits[i], 10) * (11 - i);
|
|
495
|
+
}
|
|
496
|
+
checkDigit = 11 - (sum % 11);
|
|
497
|
+
if (checkDigit >= 10) checkDigit = 0;
|
|
498
|
+
if (checkDigit !== parseInt(digits[10], 10)) return false;
|
|
499
|
+
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Validate Brazilian CNPJ
|
|
505
|
+
*/
|
|
506
|
+
function validateCNPJ(value) {
|
|
507
|
+
const digits = value.replace(/\D/g, "");
|
|
508
|
+
if (digits.length !== 14) return false;
|
|
509
|
+
|
|
510
|
+
// Check if all digits are the same
|
|
511
|
+
if (/^(\d)\1+$/.test(digits)) return false;
|
|
512
|
+
|
|
513
|
+
// Validate first check digit
|
|
514
|
+
let sum = 0;
|
|
515
|
+
let pos = 5;
|
|
516
|
+
for (let i = 0; i < 12; i++) {
|
|
517
|
+
sum += parseInt(digits[i], 10) * pos;
|
|
518
|
+
pos = pos === 2 ? 9 : pos - 1;
|
|
519
|
+
}
|
|
520
|
+
let checkDigit = sum % 11 < 2 ? 0 : 11 - (sum % 11);
|
|
521
|
+
if (checkDigit !== parseInt(digits[12], 10)) return false;
|
|
522
|
+
|
|
523
|
+
// Validate second check digit
|
|
524
|
+
sum = 0;
|
|
525
|
+
pos = 6;
|
|
526
|
+
for (let i = 0; i < 13; i++) {
|
|
527
|
+
sum += parseInt(digits[i], 10) * pos;
|
|
528
|
+
pos = pos === 2 ? 9 : pos - 1;
|
|
529
|
+
}
|
|
530
|
+
checkDigit = sum % 11 < 2 ? 0 : 11 - (sum % 11);
|
|
531
|
+
if (checkDigit !== parseInt(digits[13], 10)) return false;
|
|
532
|
+
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Get the maximum length for a mask
|
|
538
|
+
* @param {string|object} mask - The mask type or configuration
|
|
539
|
+
* @returns {number|null} - The maximum length or null if unlimited
|
|
540
|
+
*/
|
|
541
|
+
export function getMaskMaxLength(mask) {
|
|
542
|
+
if (!mask) return null;
|
|
543
|
+
|
|
544
|
+
const maskType = typeof mask === 'object' ? mask.type || mask.pattern : mask;
|
|
545
|
+
|
|
546
|
+
const lengths = {
|
|
547
|
+
'date': 10, // DD/MM/YYYY
|
|
548
|
+
'time': 5, // HH:MM
|
|
549
|
+
'cpf': 14, // 123.456.789-01
|
|
550
|
+
'cnpj': 18, // 12.345.678/0001-90
|
|
551
|
+
'cep': 9, // 12345-678
|
|
552
|
+
'credit-card': 19, // 1234 5678 9012 3456
|
|
553
|
+
'phone': 18, // +1 (234) 567-8900
|
|
554
|
+
'phone-br': 17, // +55 11 99999-9999
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// Check if it's a predefined mask type first
|
|
558
|
+
if (lengths[maskType]) {
|
|
559
|
+
return lengths[maskType];
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// If not predefined, check if it's a custom pattern
|
|
563
|
+
if (typeof maskType === 'string' && (maskType.includes('.') || maskType.includes('-') || maskType.includes('/') || maskType.includes('9') || maskType.includes('A') || maskType.includes('*'))) {
|
|
564
|
+
// Custom pattern - return pattern length
|
|
565
|
+
return maskType.length;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
return null;
|
|
569
|
+
}
|