PageSourceSearch

https://www.eqstaffingsolutions.com/wp-content/plugins/form-masks-…/assets/js/custom-mask-script.js?ver=2.7.4

js eqstaffingsolutions.com collected 2026-10-05 00:53:14 UTC 37,772 bytes, 1,198 lines download raw bytes

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