1(function($) { 2 $(document).ready(function() { 3 // ----------------- Global Settings ----------------- 4 let currencySymbol = "$"; // Change to any currency symbol dynamically 5 let decimalPlaces = 2; // Adjust between 2 or 3 dynamically 6 7 // ----------------- Formatting Functions ----------------- 8 function formatString(digits, pattern) { 9 let formatted = ""; 10 let index = 0; 11 for (let char of pattern) { 12 if (char === "#") { 13 if (index < digits.length) { 14 formatted += digits[index++]; 15 } else { 16 break; 17 } 18 } else { 19 formatted += char; 20 } 21 } 22 return formatted; 23 } 24 25 function stripCNPJ(value) { 26 var s = String(value).toUpperCase().replace(/[^A-Z0-9]/g, ""); 27 if (s.length <= 12) return s; 28 return s.slice(0, 12) + s.slice(12).replace(/\D/g, "").slice(0, 2); 29 } 30 31 // ----------------- Money Mask Formatting ----------------- 32 function formatMoneyInput(value, type, prefix,input) { 33 let decimalSeparator = type === "C" ? "." : ","; 34 let thousandSeparator = type === "C" ? "," : "."; 35 let rawDigits = value.replace(/\D/g, ""); 36 decimalPlaces = 2; 37 if(input !== ''){ 38 decimalPlaces = Number(input.dataset.decimalPlaces) 39 } 40 41 if (rawDigits.length === 0) { 42 return `${prefix}0${decimalSeparator}${"0".repeat(decimalPlaces)}`; 43 } 44 45 while (rawDigits.length < decimalPlaces + 1) { 46 rawDigits = "0" + rawDigits; 47 } 48 49 let cents = rawDigits.slice(-decimalPlaces); 50 let wholeNumber = rawDigits.slice(0, -decimalPlaces).replace(/^0+/, "") || "0"; 51 52 wholeNumber = wholeNumber.replace(/\B(?=(\d{3})+(?!\d))/g, thousandSeparator); 53 return `${prefix}${wholeNumber}${decimalSeparator}${cents}`; 54 } 55 56 function handleMoneyInput(event) { 57 let input = event.target; 58 let oldValue = input.value; 59 let oldCursorPos = input.selectionStart; 60 61 let moneyPrefix = input.dataset.moneymaskPrefix 62 let moneymaskFormat = input.dataset.moneymaskFormat 63 let type = (moneymaskFormat === 'dot' ? 'D' : 'C') 64 let newValue = ''; 65 66 if(input.value != '$0,00'){ 67 68 newValue = formatMoneyInput(input.value, type, moneyPrefix, input); 69 }else{ 70 newValue = ""; 71 } 72 73 if (input.value === newValue) return; 74 75 input.value = newValue; 76 let newCursorPos = oldCursorPos + (newValue.length - oldValue.length); 77 setCaretPosition(input, newCursorPos); 78 } 79 80 // ----------------- Handling Focus & Blur for Money Mask ----------------- 81 $(document).on("focus", ".mask-moneyc", function() { 82 let moneymaskFormat = $(this)[0].dataset.moneymaskFormat 83 let type = (moneymaskFormat === 'dot' ? 'D' : 'C') 84 let decimalSeparator = type === "C" ? "." : ","; 85 let baseFormat = `${currencySymbol}0${decimalSeparator}${"0".repeat(decimalPlaces)}`; 86 87 if ($(this).val().trim() === "") { 88 $(this).val(baseFormat); 89 // setCaretPosition(this, $(this).val().length - (decimalPlaces + 1)); 90 } 91 }); 92 93 $(document).on("blur", ".mask-moneyc", function() { 94 let moneymaskFormat = $(this)[0].dataset.moneymaskFormat 95 let type = (moneymaskFormat === 'dot' ? 'D' : 'C') 96 let decimalSeparator = type === "C" ? "." : ","; 97 let baseFormat = `${currencySymbol}0${decimalSeparator}${"0".repeat(decimalPlaces)}`; 98 99 let val = $(this).val().trim(); 100 101 let numericValue = val.replace(new RegExp(`[^0-9${decimalSeparator}]`, "g"), "").replace(decimalSeparator, "."); 102 103 if (parseFloat(numericValue) === 0 || val === baseFormat) { 104 $(this).val(""); 105 } 106 }); 107 108 109 // ----------------- Formatting Credit Card ----------------- 110 function formatCreditCard(digits, formatType) {
111 let cardType = detectCardType(digits); 112 113 if (cardType === "American Express") { 114 digits = digits.slice(0, 15); 115 return formatType === "space" 116 ? formatString(digits, "#### ###### #####") 117 : formatString(digits, "####-######-#####"); 118 } else { 119 digits = digits.slice(0, 16); 120 return formatType === "space" 121 ? formatString(digits, "#### #### #### ####") 122 : formatString(digits, "####-####-####-####"); 123 } 124 } 125 126 // ----------------- Card Logos ----------------- 127 const cardLogos = { 128 "Visa": fmeData.pluginUrl+ "assets/svg-icons/visa-logo.svg", 129 "MasterCard": fmeData.pluginUrl+ "assets/svg-icons/mastercard-logo.svg", 130 "American Express": fmeData.pluginUrl+ "assets/svg-icons/amex-logo.svg", 131 "Discover": fmeData.pluginUrl+ "assets/svg-icons/discover-logo.svg", 132 "JCB": fmeData.pluginUrl+ "assets/svg-icons/jcb-logo.svg", 133 "Diners Club": fmeData.pluginUrl+ "assets/svg-icons/cc-logo.svg", 134 "Maestro": fmeData.pluginUrl+ "assets/svg-icons/maestro-logo.svg", 135 "UnionPay": fmeData.pluginUrl+ "assets/svg-icons/cc-logo.svg", 136 "RuPay": fmeData.pluginUrl+ "assets/svg-icons/repay-logo.svg", 137 "Unknown": fmeData.pluginUrl+ "assets/svg-icons/cc-logo.svg" 138 }; 139 140 // ----------------- Function to Detect Card Type ----------------- 141 function detectCardType(number) { 142 const cleaned = number.replace(/\D/g, ""); // Remove non-numeric characters 143 144 const cardPatterns = { 145 "Visa": /^4/, 146 "MasterCard": /^5[1-5]/, // MasterCard starts with 51-55 147 "American Express": /^3[47]/, // AmEx starts with 34 or 37 148 "Discover": /^6(?:011|5)/, // Discover starts with 6011 or 65 149 "JCB": /^(?:2131|1800|35)/, // JCB starts with 2131, 1800, or 35 150 "Diners Club": /^3(?:0[0-5]|[689])/, // Diners Club starts with 300-305, 36, or 38-39 151 "UnionPay": /^(62|81)/, // UnionPay starts with 62 or 81 152 "RuPay": /^(60|65|81|82|508)/, // RuPay starts with 60, 65, 81, 82, or 508 153 "Maestro": /^(50|5[6-9]|6[0-9])/ // Maestro starts with 50, 56-59, 60-69 154 }; 155 156 for (let card in cardPatterns) { 157 if (cardPatterns[card].test(cleaned)) { 158 return card; 159 } 160 } 161 162 return "Unknown"; // Default if no match is found 163 } 164 165 // ----------------- Function to Update Card Logo Dynamically ----------------- 166 function updateCardLogo(inputSelector) { 167 let input = $(inputSelector); 168 let logo = input.siblings(".card-logo"); // Select logo next to input 169 let cardNumber = input.val().replace(/\D/g, ''); // Remove non-digit characters 170 171 if (cardNumber === "") { 172 logo.hide(); // Hide the logo if the input is empty 173 } else { 174 let cardType = detectCardType(cardNumber); 175 if (cardType in cardLogos) { 176 logo.attr("src", cardLogos[cardType]).show(); 177 } else { 178 logo.hide(); // Hide logo if card type is unknown 179 } 180 } 181 } 182 183 // ----------------- Universal Field Formatting ----------------- 184 const formatFunctions = { 185 ".mask-cnpj": (digits) => formatString(digits, "##.###.###/####-##"), 186 ".mask-cpf": (digits) => formatString(digits, "###.###.###-##"), 187 ".mask-cep": (digits) => formatString(digits, "#####-###"), 188 ".mask-phus": (digits) => formatString(digits, "(###) ###-####"), 189 ".mask-ph8": (digits) => formatString(digits, "####-####"), 190 ".mask-ddd8": (digits) => formatString(digits, "(##) ####-####"), 191 ".mask-ddd9": (digits) => formatString(digits, "(##) #####-####"), 192 ".mask-dmy": (digits) => formatString(digits, "##/##/####"), 193 ".mask-mdy": (digits) => formatString(digits, "##/##/####"), 194 ".mask-hms": (digits) => formatString(digits, "##:##:##"), 195 ".mask-hm": (digits) => formatString(digits, "##:##"), 196 ".mask-dmyhm": (digits) => formatString(digits, "##/##/#### ##:##"), 197 ".mask-mdyhm": (digits) => formatString(digits, "##/##/#### ##:##"), 198 ".mask-my": (digits) => formatString(digits, "##/####"), 199 ".mask-ccs": (digits) => formatCreditCard(digits, "space"), 200 ".mask-cch": (digits) => formatCreditCard(digits, "hyphen"), 201 ".mask-ccmy": (digits) => formatString(digits, "##/##"), 202 ".mask-ccmyy": (digits) => formatString(digits, "##/####"), 203 ".mask-moneyc": (digits) => formatMoneyInput(digits, "C",'$',''), 204 ".mask-moneyd": (digits) => formatMoneyInput(digits, "D",'$',''), 205 ".mask-ipv4": (digits) => formatString(digits, "###.###.###.###") // New IPv4 Masking 206 }; 207 208 // Apply formatting dynamically for all fields, including money input
208s 209 Object.entries(formatFunctions).forEach(([selector, formatFunction]) => { 210 $(document).on("input focus", selector, function (event) { 211 var input = this; 212 var oldCaret = getCaretPosition(input); 213 214 // Handle money input separately 215 if ($(input).hasClass("mask-moneyc") || $(input).hasClass("mask-moneyd")) { 216 handleMoneyInput(event); 217 return; 218 } 219 220 var isCnpj = $(input).hasClass("mask-cnpj"); 221 var rawDigits = isCnpj ? stripCNPJ(input.value) : input.value.replace(/\D/g, ""); 222 var digitIndex = getDigitIndexFromCaret(input.value, oldCaret, isCnpj); 223 var newVal = formatFunction(rawDigits); 224 var newCaret = mapDigitIndexToCaret(newVal, digitIndex, isCnpj); 225 226 if(newVal != '('){ 227 228 input.value = newVal; 229 }else{ 230 input.value = ""; 231 } 232 233 setCaretPosition(input, newCaret || 0); // Keep caret in place 234 235 // Update card logo dynamically for credit card fields 236 if ($(input).hasClass("mask-ccs") || $(input).hasClass("mask-cch")) { 237 updateCardLogo(input); 238 } 239 }); 240 }); 241 242 // ----------------- Helper Functions for Caret Management ----------------- 243 function getCaretPosition(input) { 244 return input.selectionStart; 245 } 246 function getDigitIndexFromCaret(formattedStr, caretPos, alphanumeric) { 247 var count = 0; 248 var pattern = alphanumeric ? /[A-Za-z0-9]/ : /\d/; 249 for (var i = 0; i < caretPos; i++) { 250 if (pattern.test(formattedStr.charAt(i))) { 251 count++; 252 } 253 } 254 return count; 255 } 256 function mapDigitIndexToCaret(formattedStr, digitIndex, alphanumeric) { 257 var count = 0; 258 var pattern = alphanumeric ? /[A-Za-z0-9]/ : /\d/; 259 for (var i = 0; i < formattedStr.length; i++) { 260 if (pattern.test(formattedStr.charAt(i))) { 261 if (count === digitIndex) { 262 return i; 263 } 264 count++; 265 } 266 } 267 return formattedStr.length; 268 } 269 function setCaretPosition(elem, pos) { 270 if (elem.setSelectionRange) { 271 elem.focus(); 272 elem.setSelectionRange(pos, pos); 273 } else if (elem.createTextRange) { 274 var range = elem.createTextRange(); 275 range.collapse(true); 276 range.moveEnd('character', pos); 277 range.moveStart('character', pos); 278 range.select(); 279 } 280 } 281 282 // ----------------- Backspace Handling ----------------- 283 $(document).on("keydown", "input", function (e) { 284 if (e.key === "Backspace") { 285 var input = this; 286 287 // Special handling for money inputs 288 if ($(input).hasClass("mask-moneyc") || $(input).hasClass("mask-moneyd")) { 289 let decimalSeparator = $(input).hasClass("mask-moneyc") ? "." : ","; 290 let baseFormat = `${currencySymbol}0${decimalSeparator}${"0".repeat(decimalPlaces)}`; 291 292 if (input.value === baseFormat) { 293 e.preventDefault(); // Prevent deletion beyond the base format 294 return; 295 } 296 297 // Handle money input dynamically and prevent default formatting conflict 298 handleMoneyInput(e); 299 return; 300 } 301 302 // Standard backspace handling for all other masked inputs 303 if (input.selectionStart !== input.selectionEnd) { 304 e.preventDefault(); 305 input.value = ""; 306 setCaretPosition(input, 0); 307 return; 308 } 309 310 var caretPos = getCaretPosition(input); 311 var isCnpj = $(input).hasClass("mask-cnpj"); 312 var digitIndex = getDigitIndexFromCaret(input.value, caretPos, isCnpj); 313 if (digitIndex === 0) return; 314 315 var rawDigits = isCnpj ? stripCNPJ(input.value) : input.value.replace(/\D/g, ""); 316 var newDigits = rawDigits.slice(0, digitIndex - 1) + rawDigits.slice(digitIndex); 317 318 // Find matching class for format function 319 let matchedClass = Object.keys(formatFunctions).find(cls => $(input).hasClass(cls.substring(1))); 320 321 if (matchedClass) { 322 var formatted = formatFunctions[matchedClass](newDigits); 323 input.value = formatted; 324 var newCaretPos = mapDigitIndexToCaret(formatted, digitIndex - 1, isCnpj); 325 setCaretPosition(input, newCaretPos); 326 } 327 } 328 }); 329 330 // ----------------- Error Validation ----------------- 331 function validateInput(selector, errorClass, validationFunction, errorMessage) { 332 $(document).on("blur", selector, function () { 333 var input = $(this); 334 if(input.hasClass("hide-fme-mask-input")){ 335 return; 336 } 337 var val = input.val(); 338 var errorElement = input.closest('.elementor-field-group').find("." + errorClass); 339 340 // Remove predefined symbol if it's the ONLY character left 341 if (val.length === 1 && !/\d/.test(val)) { 342 input.val(""); 343 errorElement.hide().text(""); 344 return; 345 } 346 347 // Show error message if validation fails 348 if (val !== "" && !validationFunction(val)) { 349 errorElement.text(errorMessage).css("display", "flex").hide().fadeIn(200); 350 } else { 351 errorElement.fadeOut(100, function () { 352 $(this).css("display", "none"); 353 }); 354 } 355 }); 356 357 $(document).on("input", selector, function () { 358 var input = $(this); 359 nextbtnVisibility(errorClass, input, validationFunction); 360 var errorElement = input.closest('.elementor-field-group').find("." + errorClass); 361 362 if (errorElement.is(":visible")) { 363 var val = input.val(); 364 if (validationFunction(val)) { 365 errorElement.fadeOut(100, function () { 366 $(this).css("display", "none"); 367 }); 368 } 369 } 370 }); 371 } 372 373 // Apply validation dynamically for all fields 374 const validations = { 375 ".mask-cnpj": { errorClass: "error-cnpj", validate: isValidCNPJ, msg: fmeData.errorMessages["mask-cnpj"] }, 376 ".mask-cpf": { errorClass: "error-cpf", validate: isValidCPF, msg: fmeData.errorMessages["mask-cpf"] }, 377 ".mask-cep": { errorClass: "error-cep", validate: isValidCEP, msg: fmeData.errorMessages["mask-cep"] }, 378 ".mask-phus": { errorClass: "error-phus", validate: isValidPhoneUSA, msg: fmeData.errorMessages["mask-phus"] },
379 ".mask-ph8": { errorClass: "error-ph8", validate: isValidPhone8, msg: fmeData.errorMessages["mask-ph8"] }, 380 ".mask-ddd8": { errorClass: "error-ddd8", validate: isValidPhoneDDD8, msg: fmeData.errorMessages["mask-ddd8"] }, 381 ".mask-ddd9": { errorClass: "error-ddd9", validate: isValidPhoneDDD9, msg: fmeData.errorMessages["mask-ddd9"] }, 382 ".mask-dmy": { errorClass: "error-dmy", validate: isValidDateDMY, msg: fmeData.errorMessages["mask-dmy"] }, 383 ".mask-mdy": { errorClass: "error-mdy", validate: isValidDateMDY, msg: fmeData.errorMessages["mask-mdy"] }, 384 ".mask-hms": { errorClass: "error-hms", validate: isValidTimeHMS, msg: fmeData.errorMessages["mask-hms"] }, 385 ".mask-hm": { errorClass: "error-hm", validate: isValidTimeHM, msg: fmeData.errorMessages["mask-hm"] }, 386 ".mask-dmyhm": { errorClass: "error-dmyhm", validate: isValidDateDMYHM, msg: fmeData.errorMessages["mask-dmyhm"] }, 387 ".mask-mdyhm": { errorClass: "error-mdyhm", validate: isValidDateMDYHM, msg: fmeData.errorMessages["mask-mdyhm"] }, 388 ".mask-my": { errorClass: "error-my", validate: isValidDateMY, msg: fmeData.errorMessages["mask-my"] }, 389 ".mask-ccs": { errorClass: "error-ccs", validate: isValidCreditCard, msg: fmeData.errorMessages["mask-ccs"] }, 390 ".mask-cch": { errorClass: "error-cch", validate: isValidCreditCard, msg: fmeData.errorMessages["mask-cch"] },
391 ".mask-ccmy": { errorClass: "error-ccmy", validate: isValidExpiryMMYY, msg: fmeData.errorMessages["mask-ccmy"] }, 392 ".mask-ccmyy": { errorClass: "error-ccmyy", validate: isValidExpiryMMYYYY, msg: fmeData.errorMessages["mask-ccmyy"] }, 393 ".mask-ipv4": { errorClass: "error-ipv4", validate: isValidIPv4, msg: fmeData.errorMessages["mask-ipv4"] } 394 }; 395 396 397 for (const [selector, { errorClass, validate, msg }] of Object.entries(validations)) { 398 validateInput(selector, errorClass, validate, msg); 399 } 400 401 // ----------------- Validation Functions ----------------- 402 function isValidPhoneUSA(phoneStr) { 403 return /^\(\d{3}\) \d{3}-\d{4}$/.test(phoneStr); 404 } 405 function isValidPhone8(phoneStr) { 406 return /^\d{4}-\d{4}$/.test(phoneStr); 407 } 408 function isValidPhoneDDD8(phoneStr) { 409 return /^\(\d{2}\) \d{4}-\d{4}$/.test(phoneStr); 410 } 411 function isValidPhoneDDD9(phoneStr) { 412 return /^\(\d{2}\) 9\d{4}-\d{4}$/.test(phoneStr); 413 } 414 415 // ----------------- Universal Date & Time Validation Function ----------------- 416 function isValidDateTime(value, format) { 417 let regexPattern; 418 let expectedParts; 419 420 switch (format) { 421 case "DMY": // dd/mm/yyyy 422 regexPattern = /^(\d{2})\/(\d{2})\/(\d{4})$/; 423 expectedParts = ["day", "month", "year"]; 424 break; 425 case "MDY": // mm/dd/yyyy 426 regexPattern = /^(\d{2})\/(\d{2})\/(\d{4})$/; 427 expectedParts = ["month", "day", "year"]; 428 break; 429 case "HMS": // hh:mm:ss 430 regexPattern = /^(\d{2}):(\d{2}):(\d{2})$/; 431 expectedParts = ["hour", "minute", "second"]; 432 break; 433 case "HM": // hh:mm 434 regexPattern = /^(\d{2}):(\d{2})$/; 435 expectedParts = ["hour", "minute"]; 436 break; 437 case "DMY-HM": // dd/mm/yyyy hh:mm 438 regexPattern = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2})$/; 439 expectedParts = ["day", "month", "year", "hour", "minute"]; 440 break; 441 case "MDY-HM": // mm/dd/yyyy hh:mm 442 regexPattern = /^(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2})$/; 443 expectedParts = ["month", "day", "year", "hour", "minute"]; 444 break; 445 case "MY": // mm/yyyy 446 regexPattern = /^(\d{2})\/(\d{4})$/; 447 expectedParts = ["month", "year"]; 448 break; 449 default: 450 return false; // Unsupported format 451 } 452 453 // Match input with regex 454 let match = value.match(regexPattern); 455 if (!match) return false; 456 457 // Extract values dynamically 458 let parts = {}; 459 expectedParts.forEach((part, index) => { 460 parts[part] = parseInt(match[index + 1], 10); 461 }); 462 463 // Validate Date (Day, Month, Year) 464 if (parts.year && (parts.year < 1500 || parts.year > 3000)) return false; 465 if (parts.month && (parts.month < 1 || parts.month > 12)) return false; 466 if (parts.day) { 467 let daysInMonth = new Date(parts.year, parts.month, 0).getDate(); 468 if (parts.day < 1 || parts.day > daysInMonth) return false; 469 } 470 471 // Validate Time (Hour, Minute, Second) 472 if (parts.hour && (parts.hour < 0 || parts.hour >= 24)) return false; 473 if (parts.minute && (parts.minute < 0 || parts.minute >= 60)) return false; 474 if (parts.second && (parts.second < 0 || parts.second >= 60)) return false; 475 476 return true; 477 } 478 479 // ----------------- Expiry Date Validation ----------------- 480 function isValidExpiryDate(value, format) { 481 let regexPattern = format === "MM/YY" ? /^(\d{2})\/(\d{2})$/ : /^(\d{2})\/(\d{4})$/; 482 let match = value.match(regexPattern); 483 484 if (!match) return false; // Invalid format 485 486 let month = parseInt(match[1], 10); 487 let year = parseInt(match[2], 10); 488 489 let currentYear = new Date().getFullYear(); 490 let currentMonth = new Date().getMonth() + 1; // JS months are 0-based 491 492 // Adjust year format if MM/YY (Assume 20XX for past/future dates) 493 if (format === "MM/YY") { 494 year += 2000; 495 } 496 497 // Validate month (01-12) 498 if (month < 1 || month > 12) return false;
499 500 // Validate expiry date (must be in the future) 501 if (year < currentYear || (year === currentYear && month < currentMonth)) { 502 return false; 503 } 504 505 return true; 506 } 507 508 // ----------------- Exportable Functions Using Universal Validator ----------------- 509 function isValidDateDMY(dateStr) { return isValidDateTime(dateStr, "DMY"); } 510 function isValidDateMDY(dateStr) { return isValidDateTime(dateStr, "MDY"); } 511 function isValidTimeHMS(timeStr) { return isValidDateTime(timeStr, "HMS"); } 512 function isValidTimeHM(timeStr) { return isValidDateTime(timeStr, "HM"); } 513 function isValidDateDMYHM(dateTimeStr) { return isValidDateTime(dateTimeStr, "DMY-HM"); } 514 function isValidDateMDYHM(dateTimeStr) { return isValidDateTime(dateTimeStr, "MDY-HM"); } 515 function isValidDateMY(dateStr) { return isValidDateTime(dateStr, "MY"); } 516 function isValidExpiryMMYY(dateStr) { return isValidExpiryDate(dateStr, "MM/YY"); } 517 function isValidExpiryMMYYYY(dateStr) { return isValidExpiryDate(dateStr, "MM/YYYY"); } 518 519 // Apply validation for credit card fields 520 function isValidCreditCard(cardNumber) { 521 const cleaned = cardNumber.replace(/\D/g, ''); // Remove non-digits 522 if (cleaned.length < 15 || cleaned.length > 16) return false; // AMEX: 15, Others: 16 523 let sum = 0; 524 let shouldDouble = false; 525 // Luhn Algorithm to validate card number 526 for (let i = cleaned.length - 1; i >= 0; i--) { 527 let digit = parseInt(cleaned.charAt(i), 10); 528 529 if (shouldDouble) { 530 digit *= 2; 531 if (digit > 9) digit -= 9; 532 } 533 534 sum += digit; 535 shouldDouble = !shouldDouble; 536 } 537 return sum % 10 === 0; 538 } 539 540 // Validate CNPJ (Brazilian Business ID - 14 chars, numeric or alphanumeric) 541 function isValidCNPJ(cnpj) { 542 cnpj = cnpj.toUpperCase().replace(/[.\-\/]/g, ''); 543 if (!/^[A-Z0-9]{12}\d{2}$/.test(cnpj)) return false; 544 545 // Eliminate obvious invalid CNPJs (repeated characters) 546 if (/^(.)\1{13}$/.test(cnpj)) return false; 547 548 let calcCheckDigit = (cnpj, length) => { 549 let weights = length === 12 ? [5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2] : [6, 5, 4, 3, 2, 9, 8, 7, 6, 5, 4, 3, 2]; 550 let sum = 0; 551 552 for (let i = 0; i < weights.length; i++) { 553 sum += (cnpj.charCodeAt(i) - 48) * weights[i]; 554 } 555 556 let remainder = sum % 11; 557 return remainder < 2 ? 0 : 11 - remainder; 558 }; 559 560 // Validate both check digits 561 let firstCheck = calcCheckDigit(cnpj, 12); 562 let secondCheck = calcCheckDigit(cnpj.slice(0, 12) + firstCheck, 13); 563 564 return firstCheck === parseInt(cnpj.charAt(12), 10) && secondCheck === parseInt(cnpj.charAt(13), 10); 565 } 566 567 // Validate CPF (Brazilian Individual ID - 11 digits) 568 function isValidCPF(cpf) { 569 cpf = cpf.replace(/\D/g, ''); // Remove non-numeric characters 570 if (cpf.length !== 11) return false; 571 572 // Eliminate obvious invalid CPFs (repeated digits) 573 if (/^(\d)\1+$/.test(cpf)) return false; 574 575 let calculateDigit = (cpf, length) => { 576 let sum = 0; 577 for (let i = 0; i < length; i++) { 578 sum += parseInt(cpf.charAt(i)) * (length + 1 - i); 579 } 580 let result = (sum * 10) % 11; 581 return result === 10 ? 0 : result; 582 }; 583 584 let firstDigit = calculateDigit(cpf, 9); 585 let secondDigit = calculateDigit(cpf, 10); // Now correctly includes first digit in the calculation 586 587 return firstDigit === parseInt(cpf.charAt(9)) && secondDigit === parseInt(cpf.charAt(10)); 588 } 589 590 // Validate CEP (Brazilian Postal Code - XXXXX-XXX) 591 function isValidCEP(cep) { 592 return /^\d{5}
592-\d{3}$/.test(cep); // Matches format XXXXX-XXX 593 } 594 595 // Validate IPv4 (Format: 192.168.001.001) 596 function isValidIPv4(ip) { 597 // Ensure correct format (###.###.###.###) 598 const ipv4Pattern = /^(?:\d{1,3}\.){3}\d{1,3}$/; 599 600 if (!ipv4Pattern.test(ip)) return false; // Check general format 601 602 // Split by dots and validate each octet (0-255) 603 const octets = ip.split("."); 604 return octets.every(octet => { 605 let num = parseInt(octet, 10); // Convert to number 606 return num >= 0 && num <= 255; // Each octet must be between 0-255 607 }); 608 } 609 610 // ----------------- Form Submit ----------------- 611 612 613 // start - step field mask error handling 614 615 let maskErrorArr = {}; 616 let nextBtnOriginalClicks = {}; 617 let recaptchaEvent = {}; 618 let submitBtnEvent = {}; 619 620 621 const nextbtnVisibility = (errorClass, input, validationFunction) => { 622 623 const closesWidget = input.closest(".elementor-widget-form"); 624 const widgetId = closesWidget.data('id'); 625 const inuptId = input.attr('id'); 626 const fieldStep=input.closest(".elementor-field-type-step"); 627 const form = input.closest(".elementor-form"); 628 629 const currectStepFields = form.find(".elementor-form-fields-wrapper.elementor-labels-above").children("div:not(.elementor-hidden)").find('input, textarea, select'); 630 631 const submtBtnTag = form.find("button[type='submit']"); 632 633 if (!closesWidget.length || !fieldStep.length) { 634 return; 635 } 636 637 const nextBtn = fieldStep.find(".e-form__buttons__wrapper__button[data-direction='next']"); 638 639 640 641 if ((nextBtn.length == 0 && submtBtnTag.length > 0 ) && (submtBtnTag.length == 0 && nextBtn.length == 0) ) { 642 return; 643 } 644 645 646 647 let val = input.val(); 648 649 if (val.length === 1 && !/\d/.test(val)) { 650 val = ''; 651 } 652 653 // Show error message if validation fails 654 if ( val !== "" && !validationFunction(val)) { 655 656 657 if (closesWidget.length > 0) { 658 659 660 if (!maskErrorArr[widgetId]) { 661 maskErrorArr[widgetId] = []; 662 } 663 664 if (!maskErrorArr[widgetId].includes(inuptId)) { 665 maskErrorArr[widgetId].push(inuptId); 666 } 667 } 668 669 if (!nextBtnOriginalClicks[widgetId] || !nextBtnOriginalClicks[widgetId].length) { 670 671 672 673 if(nextBtn.length > 0){ 674 675 const origninalClicks = jQuery._data(nextBtn[0], "events"); 676 677 if (origninalClicks && (!nextBtnOriginalClicks[widgetId] || !nextBtnOriginalClicks[widgetId].length === 0)) { 678 nextBtnOriginalClicks[widgetId] = origninalClicks && origninalClicks.click ? origninalClicks.click.map(h => h.handler) : []; 679 680 } 681 } 682 683 684 } 685 } else { 686 if (maskErrorArr[widgetId] && maskErrorArr[widgetId].includes(inuptId)) { 687 maskErrorArr[widgetId] = maskErrorArr[widgetId].filter(item => item !== inuptId); 688 } 689 } 690 691 692 if (maskErrorArr[widgetId] && maskErrorArr[widgetId].length > 0) { 693 694 if(nextBtn.length > 0){ 695 696 const origninalClicks = jQuery._data(nextBtn[0], "events"); 697 698 if (origninalClicks && (!nextBtnOriginalClicks[widgetId] || !nextBtnOriginalClicks[widgetId].length)) { 699 700 701 nextBtnOriginalClicks[widgetId] = origninalClicks && origninalClicks.click ? origninalClicks.click.map(h => h.handler) : []; 702 703 } 704 705 if (nextBtnOriginalClicks[widgetId] && nextBtnOriginalClicks[widgetId].length > 0) { 706 nextBtn.off("click"); 707 } 708 709 710 } 711 712 713 714 } else { 715 716 717 // when no error in mask validation 718 719 if(recaptchaEvent[widgetId]){ 720 submtBtnTag.on("click", recaptchaEvent[widgetId]); 721 } 722 723 if(submitBtnEvent[widgetId]){ 724 form.on("submit", submitBtnEvent[widgetId]); 725 } 726 727 728 729 if (nextBtnOriginalClicks[widgetId] && nextBtnOriginalClicks[widgetId].length > 0) { 730 731 732 733 734 let isfieldsValid = true 735 736 for (let i = 0; i < currectStepFields.length; i++) { 737 738 739 if (currectStepFields[i].checkValidity() == false) { 740 741 isfieldsValid = false;
742 break; 743 744 } 745 746 } 747 748 if (isfieldsValid) { 749 750 751 752 753 754 if(nextBtn.length > 0){ 755 756 // 2ï¸â£ First, clear existing handlers to avoid duplication 757 nextBtn.off("click"); 758 759 // Re-attach original click handlers only once 760 nextBtnOriginalClicks[widgetId].forEach(fn => { 761 nextBtn.one("click", fn); // use .one instead of .on 762 }); 763 } 764 765 766 } 767 768 } 769 770 } 771 } 772 773 774 775 776 // handle next button event before click 777 778 $(document).on("mousedown", ".e-form__buttons__wrapper__button[data-direction='next']", function(e){ 779 780 const form = $(this).closest(".elementor-form"); 781 782 const mask_error_div = form.find(".elementor-form-fields-wrapper.elementor-labels-above").children("div:not(.elementor-hidden)").find('div.mask-error'); 783 784 const closesWidget = $(this).closest(".elementor-widget-form"); 785 const widgetId = closesWidget.data('id'); 786 787 let mask_error = false; 788 789 // finding mask error 790 for(let i=0; i<mask_error_div.length; i++){ 791 if(mask_error_div[i].value != "" && mask_error_div[i].style.display == 'flex'){ 792 mask_error = true; 793 break; 794 } 795 } 796 797 // reattcach next button event when no mask error 798 799 if (maskErrorArr[widgetId] && !maskErrorArr[widgetId].length > 0 && !mask_error) { 800 // Re-attach original click handlers only once 801 802 if (nextBtnOriginalClicks[widgetId] && nextBtnOriginalClicks[widgetId].length > 0) { 803 $(this).off("click"); 804 805 nextBtnOriginalClicks[widgetId].forEach(fn => { 806 $(this).one("click", fn); // use .one instead of .on 807 }); 808 } 809 810 811 } 812 813 }) 814 815 816 // handling next button event when previous button click 817 818 $(document).on("click", ".e-form__buttons__wrapper__button[data-direction='previous']", function(e){ 819 820 const form = $(this).closest(".elementor-form"); 821 822 const closesWidget = form.closest(".elementor-widget-form"); 823 const widgetId = closesWidget.data('id'); 824 maskErrorArr[widgetId] = []; 825 826 const currectStepFields = form.find(".elementor-form-fields-wrapper.elementor-labels-above").children("div:not(.elementor-hidden)") 827 828 const nextBtn = currectStepFields.find(".e-form__buttons__wrapper__button[data-direction='next']"); 829 830 // attaching events to next button 831 832 if (nextBtnOriginalClicks[widgetId] && nextBtnOriginalClicks[widgetId].length > 0) { 833 nextBtn.off("click"); 834 835 nextBtnOriginalClicks[widgetId].forEach(fn => { 836 nextBtn.one("click", fn); // use .one instead of .on 837 }); 838 839 } 840 841 }) 842 843 // handle mask validation on submit button of step field form 844 $(document).on("mousedown", ".elementor-field-type-submit", function (e){ 845 846 var $submitBtn = $(this); 847 848 var $form = $submitBtn.closest("form"); 849 850 const closesWidget = $form.closest(".elementor-widget-form"); 851 const widgetId = closesWidget.data('id'); 852 853 const currectStepFields = $form.find(".elementor-form-fields-wrapper.elementor-labels-above").children("div:not(.elementor-hidden)") 854 855 const previousBtn = currectStepFields.find(".e-form__buttons__wrapper__button[data-direction='previous']"); 856 857 const inputMaskFields = currectStepFields.find("input.fme-mask-input"); 858 859 // run only first time when click on submit button 860 // if maskerror found in the form widget this will remove submit button and form events to prevent submit 861 862 863 if(previousBtn && previousBtn.length && inputMaskFields && inputMaskFields.length && maskErrorArr[widgetId] && maskErrorArr[widgetId].length){ 864 865 var $subBtnTag = $submitBtn.find("button"); 866 867 868 // getting submit button recaptcha and click events 869 870 var $form = $submitBtn.closest("form"); 871 872 const origninalclick = jQuery._data($subBtnTag[0], "events"); 873 874 875 if(origninalclick && origninalclick.click){ 876 877 origninalclick.click.forEach((ele) => { 878 if(ele.handler.toString().trim().includes("onV3FormSubmit")){ 879 880 if(!recaptchaEvent[widgetId]){ 881 882 recaptchaEvent[widgetId] = ele.handler; 883 884 } 885 886 } 887 }) 888 889 } 890 891 892 893 894 // getting form apply step events 895 896 const origninalSubmit = jQuery._data($form[0], "events"); 897 898 if(origninalSubmit && origninalSubmit.submit){ 899 900 origninalSubmit.submit.forEach((ele) => { 901 if(ele.handler.toString().trim().includes("resetForm")){ 902 903 if(!submitBtnEvent[widgetId]){ 904 905 submitBtnEvent[widgetId] = ele.handler; 906 907 } 908 909 } 910 }) 911 912 } 913 914 915 if(submitBtnEvent[widgetId]){ 916 // removing form apply step event 917 $form.off("submit", submitBtnEvent[widgetId]); 918 } 919 920 921 if(recaptchaEvent[widgetId]){ 922 // removing submit button recaptcha click events 923 $subBtnTag.off("click", recaptchaEvent[widgetId]); 924 } 925 926 927 } 928 }) 929 930 // end - step field mask error handling 931 932 933 // handling form submit of mask field in elementor form 934 935 $(document).on("click", ".elementor-field-type-submit", function (e) { 936 937 var $submitBtn = $(this); 938 939 if ($submitBtn.find('button').hasClass('cfkef-prevent-submit') || $submitBtn.find('button').hasClass('confirmation-pending') 940 ) { 941 return 942 } 943 944 var $form = $submitBtn.closest("form"); 945 946 const closesWidget = $form.closest(".elementor-widget-form"); 947 const widgetId = closesWidget.data('id'); 948 const submtBtnTag = $form.find("button[type='submit']"); 949 950 // Prevent double-clicks 951 if ($submitBtn.data("clicked")) { 952 e.preventDefault(); 953 return; 954 } 955 $submitBtn.data("clicked", true); // Mark as clicked 956
957 // Trigger blur on inputs to ensure validation runs 958 $form.find("input").trigger("blur"); 959 960 // Add Elementor waiting class 961 $form[0].classList.add("elementor-form-waiting"); 962 963 // Wait for mask errors or blur logic to complete 964 setTimeout(() => { 965 let hasVisibleMaskError = false; 966 967 // Check for visible mask error messages 968 const $errors = $form.find(".mask-error").filter(function () { 969 return $(this).text().trim() !== "" && $(this).css("display") == "flex"; 970 }); 971 972 if ($errors.length > 0) { 973 hasVisibleMaskError = true; 974 const $firstError = $errors.first(); 975 $("html, body").animate({ 976 scrollTop: $firstError.offset().top - 200 977 }, 300); 978 } 979 980 // â Check for empty required masked fields 981 const $emptyRequiredMasked = $form.find("input[required]").filter(function () { 982 if(!$(this).hasClass('hide-fme-mask-input')){ 983 const val = $(this).val().trim(); 984 const isVisible = $(this).css("display") == "flex"; 985 return (val === "" || /^[\s_\-\(\)\.:/]+$/.test(val)); 986 } 987 }); 988 989 if ($emptyRequiredMasked.length > 0) { 990 hasVisibleMaskError = true; 991 const $firstEmpty = $emptyRequiredMasked.first(); 992 $("html, body").animate({ 993 scrollTop: $firstEmpty.offset().top - 200 994 }, 300); 995 $firstEmpty.focus(); 996 } 997 998 // â Validation failed 999 if (hasVisibleMaskError) { 1000 // $form[0].classList.remove("elementor-form-waiting"); 1001 $submitBtn.data("clicked", false); 1002 e.preventDefault(); 1003 return; 1004 } 1005 1006 if (!hasVisibleMaskError) { 1007 // â All good â submit the form 1008 1009 1010 if(recaptchaEvent[widgetId]){ 1011 submtBtnTag.on("click", recaptchaEvent[widgetId]); 1012 submtBtnTag.trigger("click"); 1013 } 1014 1015 if(submitBtnEvent[widgetId]){ 1016 $form.on("submit", submitBtnEvent[widgetId]); 1017 submtBtnTag.trigger("click"); 1018 1019 } 1020 1021 $form[0].classList.remove("elementor-form-waiting"); 1022 $submitBtn.data("clicked", false); 1023 if(!recaptchaEvent[widgetId]){ 1024 1025 let error_messages = $form.find('.elementor-form-fields-wrapper').find('.elementor-message'); 1026 1027 if(error_messages && error_messages.length == 0){ 1028 1029 $form[0].requestSubmit(); 1030 } 1031 1032 1033 } 1034 } 1035 1036 }, 500); 1037 }); 1038 1039 1040 }); 1041})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.