1/* global grecaptcha */ 2(function () { 3 const loadingClass = "is-loading"; 4 5 function formatError(error) { 6 if (Array.isArray(error) && error.length) { 7 return error.map((e) => e.message).join("<br />"); 8 } 9 10 if (typeof error === "object") return error.message; 11 } 12 13 function interceptResponse(response) { 14 return response.json().then((body) => { 15 if (!response.ok) { 16 throw new Error(formatError(body.data)); 17 } 18 19 return body; 20 }); 21 } 22 23 function postData(url, data) { 24 const { makeAjaxRequest } = window.BreakdanceFrontend.utils; 25 const payload = { 26 method: "POST", 27 credentials: "same-origin", // Needed in order to store cookies. 28 body: data, 29 }; 30 31 return makeAjaxRequest(url, payload).then(interceptResponse); 32 } 33 34 function createMessage(text, type = "success", onDismiss = null) { 35 const node = document.createElement("div"); 36 node.innerHTML = text; 37 node.classList.add("breakdance-form-message"); 38 node.classList.add("breakdance-form-message--" + type); 39 40 if (typeof onDismiss === "function") { 41 node.classList.add("breakdance-form-message-dismissable"); 42 43 const dismissWrapper = document.createElement("div");
44 dismissWrapper.classList.add("breakdance-form-message-dismiss"); 45 46 const dismissButton = document.createElement("button"); 47 dismissButton.classList.add("breakdance-form-message-dismiss-button"); 48 dismissButton.innerHTML = "×"; 49 dismissButton.addEventListener("click", onDismiss); 50 51 dismissWrapper.appendChild(dismissButton); 52 node.appendChild(dismissWrapper); 53 } 54 55 return node; 56 } 57 58 function createErrorMessage(text) { 59 return createMessage(text, "error"); 60 } 61 62 function resetForm(form) { 63 form.reset(); 64 65 // Reset file fields 66 const fileFields = form.querySelectorAll(".breakdance-form-field--file"); 67 68 fileFields.forEach((field) => { 69 const fileText = field.querySelector(".breakdance-form-file-upload__text"); 70 71 if (fileText) { 72 const emptyText = field.querySelector(".breakdance-form-file-upload__input").dataset.i18nEmptyText; 73 fileText.textContent = emptyText; 74 } 75 76 const list = field.querySelector(".breakdance-form-files-list"); 77 78 if (list) { 79 list.innerHTML = ""; 80 list.classList.remove("is-files-visible"); 81 } 82 }); 83 84 // Reset conditional fields and steps 85 resetConditionalFields(form); 86 if (form.dataset.steps >= 1) resetSteps(form); 87 } 88 89 function safeEval(code, form, formData) { 90 const formValues = { 91 post_id: formData.get("post_id"), 92 form_id: formData.get("form_id"), 93 }; 94 95 for (const [name, value] of formData.entries()) { 96 if (name.startsWith("fields[")) { 97 const parts = name.match(/\[/g).length; 98 const index = name.match(/\[(.*?)\]/)[1]; 99 100 if (parts >= 2) { 101 if (!formValues[index]) { 102 formValues[index] = []; 103 } 104 formValues[index].push(value); 105 } else { 106 formValues[index] = value; 107 } 108 } 109 } 110 111 try { 112 eval(code); 113 } catch (e) { 114 console.warn("Could not run Custom JavaScript.", e); 115 } 116 } 117 118 function getRecaptchaToken(apiKey) { 119 const payload = { action: "breakdance_submit" }; 120 121 return new Promise((resolve, reject) => { 122 grecaptcha.ready(() => { 123 try { 124 const token = grecaptcha.execute(apiKey, payload); 125 return resolve(token); 126 } catch (error) { 127 return reject(error); 128 } 129 }); 130 }); 131 } 132 133 function getOptions(form) { 134 const options = JSON.parse(form.getAttribute("data-options")); 135 136 const defaultOptions = { 137 name: "empty", 138 ajaxUrl: null, 139 successMessage: null, 140 errorMessage: null, 141 clearOnSuccess: true, 142 hideOnSuccess: false, 143 redirectUrl: null, 144 customJavaScript: {}, 145 popupsOnSuccess: [], 146 popupsOnError: [], 147 recaptcha: { 148 key: null, 149 enabled: false, 150 }, 151 }; 152 153 if (!options) { 154 return defaultOptions; 155 } 156 157 return Object.assign({}, defaultOptions, options); 158 } 159 160 async function onSubmit(event) { 161 event.preventDefault(); 162 163 const form = event.currentTarget; 164 const loading = form.classList.contains(loadingClass); 165 const options = getOptions(form); 166 167 if (loading) return; 168 169 const formData = new FormData(form); 170 formData.append("action", `breakdance_form_${options.slug}`); 171 172 beforeSubmit(form); 173 174 if (options.recaptcha.enabled) { 175 try { 176 const token = await getRecaptchaToken(options.recaptcha.key); 177 formData.append("recaptcha_token", token); 178 } catch (error) { 179 console.error(error); 180 } 181 } 182 183 postData(options.ajaxUrl, formData) 184 .then((response) => onRequestSuccess(response, form, formData)) 185 .catch((error) => onRequestError(error, form, formData)) 186 .finally(() => afterSubmit(form)); 187 } 188 189 function beforeSubmit(form) { 190 form.classList.add(loadingClass); 191 192 const messages = form.parentElement.querySelectorAll(".breakdance-form-message"); 193 194 messages.forEach((msg) => { 195 msg.remove(); 196 }) 197 } 198 199 function afterSubmit(form) { 200 form.classList.remove(loadingClass); 201 } 202 203 function onRequestError(error, form, formData) { 204 console.debug("[BREAKDANCE] Received a form error:", error.message); 205 206 const options = getOptions(form); 207 const message = 208 error.message || options.errorMessage || "An unexpected error occurred.";
209 const errorNode = createErrorMessage(message); 210 form.after(errorNode); 211 212 if (options.popupsOnError.length > 0) { 213 options.popupsOnError.forEach((errorPopup) => { 214 if (errorPopup.popup && errorPopup.action) { 215 BreakdancePopup.runAction(errorPopup.popup, errorPopup.action); 216 } 217 }); 218 } 219 220 safeEval(options.customJavaScript?.js_on_error, form, formData); 221 } 222 223 function onRequestSuccess(response, form, formData) { 224 console.debug("[BREAKDANCE] Received form response:", response); 225 226 const options = getOptions(form); 227 const redirectOnSuccess = options.redirect && options.redirectUrl; 228 229 if (options.successMessage && !redirectOnSuccess) { 230 let messageNode = createMessage(options.successMessage); 231 232 if (options.hideOnSuccess) { 233 form.classList.add("breakdance-form--hidden"); 234 messageNode = createMessage(options.successMessage, "success", () => { 235 form.classList.remove("breakdance-form--hidden"); 236 messageNode.remove(); 237 }); 238 } 239 240 form.after(messageNode); 241 } 242 243 safeEval(options.customJavaScript?.js_on_success, form, formData); 244 245 if (options.clearOnSuccess) { 246 resetForm(form); 247 } 248 249 if (options.popupsOnSuccess.length > 0) { 250 options.popupsOnSuccess.forEach((successPopup) => { 251 if (successPopup.popup && successPopup.action) { 252 BreakdancePopup.runAction(successPopup.popup, successPopup.action); 253 } 254 }); 255 } 256 257 if (redirectOnSuccess) { 258 location.href = options.redirectUrl; 259 } 260 } 261 262 function onInputUpdate(event) { 263 const parent = event.currentTarget.closest(".breakdance-form-field"); 264 const activeClass = "breakdance-form-field--filled"; 265 266 if (!parent) return; 267 268 if (event.currentTarget.value.length) { 269 parent.classList.add(activeClass); 270 } else { 271 parent.classList.remove(activeClass); 272 } 273 } 274 275 function removeFile(input, fileToRemove, fileIndex) { 276 const dt = new DataTransfer(); 277 278 for (let file of input.files) { 279 if (file !== fileToRemove) { 280 dt.items.add(file); 281 } 282 } 283 284 input.files = dt.files; 285 onFileInputChange(input); 286 } 287 288 function maybeShowInlineFileValidation({ input, numOfFiles, maxNumOfFiles, i18nError }) { 289 const parentField = input.closest(".breakdance-form-field"); 290 const oldMsg = parentField.querySelector(".breakdance-form-message"); 291 // input.setCustomValidity(""); 292 293 if (oldMsg) oldMsg.remove(); 294 295 if (!maxNumOfFiles) return; // No max value set, unlimited files is allowed. 296 if (numOfFiles <= maxNumOfFiles) return; 297 298 const errorMessage = createErrorMessage(i18nError.replace("%n", `${maxNumOfFiles}`)); // Only %n file(s) allowed. 299 parentField.appendChild(errorMessage); 300 // input.setCustomValidity(i18nError.replace("%n", `${maxNumOfFiles}`)); 301 } 302 303 function updateFilesList(input, files, listNode) { 304 const fragment = document.createDocumentFragment(); 305 306 files.forEach((file, index) => { 307 const li = document.createElement("li"); 308 const size = file.size < 1000000 ? 309 Math.floor(file.size / 1000) + " KB" : 310 Math.floor(file.size / 1000000) + " MB"; 311 312 // Name 313 const nameNode = document.createElement("span"); 314 nameNode.appendChild(document.createTextNode(file.name)); 315 nameNode.classList.add("breakdance-form-files-list-item__name"); 316 317 // Size 318 const sizeNode = document.createElement("span"); 319 sizeNode.appendChild(document.createTextNode(size)); 320 sizeNode.classList.add("breakdance-form-files-list-item__size"); 321 322 // Delete Button 323 const deleteBtn = document.createElement("button"); 324 deleteBtn.classList.add("breakdance-form-files-list-item__delete"); 325 deleteBtn.setAttribute("aria-label", "Delete File"); 326 deleteBtn.setAttribute("type", "button"); 327 328 deleteBtn.addEventListener("click", () => { 329 removeFile(input, file, index); 330 }) 331 332 li.appendChild(nameNode); 333 li.appendChild(sizeNode); 334 li.appendChild(deleteBtn); 335 li.classList.add("breakdance-form-files-list-item"); 336 337 fragment.appendChild(li); 338 }); 339 340 listNode.innerHTML = ""; 341 listNode.appendChild(fragment); 342 listNode.classList.add("is-files-visible"); 343 } 344 345 function onFileInputChange(input) { 346 const files = Array.from(input.files); 347 const names = files.map((f) => f.name); 348 const maxNumOfFiles = Number(input.dataset.maxFiles); 349 const i18nFilledText = input.dataset.i18nFilledText; 350 const i18nError = input.dataset.i18nErrorText; 351 352 maybeShowInlineFileValidation({ 353 input, 354 numOfFiles: files.length, 355 maxNumOfFiles, 356 i18nError 357 }); 358 359 // Inline Upload 360 const textNode = input.parentElement.querySelector( 361 ".breakdance-form-file-upload__text" 362 ); 363 364 if (textNode) { 365 if (names.length > 1) { 366 textNode.innerHTML = i18nFilledText.replace("%n", `${names.length}`); // Text: %n files 367 } else { 368 textNode.innerHTML = names[0]; // Show file name if only one selected 369 } 370 } 371 372 // Drag and Drop 373 const listNode = input.closest(".breakdance-form-field--file").querySelector(".breakdance-form-files-list"); 374 if (!listNode) return; 375 376 updateFilesList(input, files, listNode); 377 } 378 379 function bindEvents(form) { 380 bindFileEvents(form); 381 382 const inputs = Array.from(form.querySelectorAll("input, select, textarea")); 383
384 inputs.forEach((input) => { 385 input.addEventListener("input", onInputUpdate); 386 }); 387 388 form.addEventListener("submit", onSubmit); 389 } 390 391 function bindDropzone(dropzone) { 392 const dragOver = (event) => { 393 event.preventDefault(); 394 event.stopPropagation(); 395 dropzone.classList.add("is-dragging"); 396 }; 397 398 const dragLeave = (event) => { 399 event.preventDefault(); 400 event.stopPropagation(); 401 dropzone.classList.remove("is-dragging"); 402 }; 403 404 const drop = (event) => { 405 event.preventDefault(); 406 event.stopPropagation(); 407 408 const fileInput = dropzone.querySelector("input[type='file']"); 409 410 if (fileInput) { 411 fileInput.files = event.dataTransfer.files; 412 onFileInputChange(fileInput); 413 } 414 }; 415 416 dropzone.addEventListener("dragover", dragOver); 417 dropzone.addEventListener("dragenter", dragOver); 418 419 dropzone.addEventListener("dragleave", dragLeave); 420 dropzone.addEventListener("dragend", dragLeave); 421 dropzone.addEventListener("drop", dragLeave); 422 423 dropzone.addEventListener("drop", drop); 424 } 425 426 function bindFileEvents(form) { 427 const dropZones = Array.from( 428 form.querySelectorAll(".breakdance-form-file-upload") 429 ); 430 const inputs = Array.from( 431 form.querySelectorAll(".breakdance-form-file-upload__input") 432 ); 433 434 inputs.forEach((input) => 435 input.addEventListener("change", () => { 436 if (!input.files.length) return; 437 onFileInputChange(input); 438 }) 439 ); 440 441 dropZones.forEach((dropzone) => bindDropzone(dropzone)); 442 } 443 444 function unbindEvents(form) { 445 const inputs = Array.from(form.querySelectorAll("input, select, textarea")); 446 447 inputs.forEach((input) => { 448 input.removeEventListener("input", onInputUpdate); 449 }); 450 451 form.removeEventListener("submit", onSubmit); 452 453 form.bdMask?.destroy(); 454 } 455 456 function scrollIntoViewIfNeeded(element) { 457 const rect = element.getBoundingClientRect(); 458 const notVisible = rect.top < 0; 459 460 if (notVisible) { 461 element.scrollIntoView({ behavior: "smooth", block: "start" }); 462 } 463 } 464 465 function initConditionalFields(form, attachEventListeners = false) { 466 const inputs = Array.from( 467 form.querySelectorAll("input, select, textarea, .form-input-html") 468 ); 469 470 inputs.forEach((input) => { 471 const { conditionalFieldId, conditionalValue, conditionalOperand } = 472 input.dataset; 473 if (!conditionalFieldId) { 474 return; 475 } 476 const conditionalFields = form.querySelectorAll( 477 `[name="fields[${conditionalFieldId}]"],[name="fields[${conditionalFieldId}][]"]` 478 ); 479 const wrapper = input.closest(".breakdance-form-field"); 480 481 // Show or hide conditional field on change 482 conditionalFields.forEach((conditionalField) => { 483 const runValidation = () => { 484 showOrHideConditionalField( 485 form, 486 input, 487 wrapper, 488 conditionalFieldId, 489 conditionalValue, 490 conditionalOperand 491 ); 492 }; 493 494 if (attachEventListeners) { 495 const useChangeEvent = conditionalField.matches( 496 "input[type=radio], input[type=checkbox], input[type=file], select, input[type=number], input[type=date]" 497 ); 498 499 if (useChangeEvent) { 500 conditionalField.addEventListener("input", () => runValidation()); 501 conditionalField.addEventListener("change", () => runValidation()); 502 } else { 503 conditionalField.addEventListener("keyup", () => runValidation()); 504 } 505 } 506 507 // also run on init to set the initial state 508 runValidation(); 509 }); 510 }); 511 } 512 513 function showOrHideConditionalField( 514 form, 515 inputElement, 516 fieldElement, 517 conditionalFieldId, 518 conditionalValue, 519 conditionalOperand 520 ) { 521 const fieldValue = getConditionalFieldValue(form, conditionalFieldId); 522 523 if (shouldShowField(fieldValue, conditionalOperand, conditionalValue)) { 524 inputElement.removeAttribute("disabled"); 525 fieldElement.classList.remove("breakdance-form-field--condition-hidden"); 526 return; 527 } 528 529 inputElement.disabled = true; 530 fieldElement.classList.add("breakdance-form-field--condition-hidden"); 531 } 532 533 function getConditionalFieldValue(form, conditionalFieldId) { 534 const selector = `[name="fields[${conditionalFieldId}]"],[name="fields[${conditionalFieldId}][]"], [type='file'][name="fields[${conditionalFieldId}][]"]`; 535 const conditionalFields = Array.from(form.querySelectorAll(selector)); 536 537 if (!conditionalFields.length) return null; 538 539 540 const values = conditionalFields.map((field) => { 541 const isCheckboxOrRadio = ["checkbox", "radio"].includes(field.type); 542 543 if (isCheckboxOrRadio && !field.checked) { 544 return null; 545 } 546 547 return field.value; 548 }); 549 550 return values?.filter((v) => v !== null).join(","); 551 } 552 553 function resetConditionalFields(form) {
554 initConditionalFields(form, false); 555 } 556 557 function shouldShowField(aValue, operand, bValue) { 558 const aValueIsEmpty = aValue === "" || aValue === null; 559 const bValueIsEmpty = bValue === "" || bValue === null; 560 if (operand === "equals") { 561 return aValue == bValue; 562 } else if (operand === "not equals") { 563 return aValue != bValue; 564 } else if (operand === "is set") { 565 return aValue; 566 } else if (operand === "is not set") { 567 return !aValue; 568 } else if (operand === "is one of") { 569 const bValueArray = bValue.split(","); 570 return bValueArray.some((x) => x.trim() == aValue); 571 } else if (operand === "is none of") { 572 const bValueArray = bValue.split(","); 573 return !bValueArray.some((x) => x.trim() == aValue); 574 } else if (operand === "contains") { 575 if (typeof aValue === "string" && typeof bValue === "string") { 576 return aValue.toLowerCase().includes(bValue.toLowerCase()); 577 } 578 return false; 579 } else if (operand === "does not contain") { 580 if (typeof aValue === "string" && typeof bValue === "string") { 581 return !aValue.toLowerCase().includes(bValue.toLowerCase()); 582 } 583 return true; 584 } else if (operand === "is greater than") { 585 const aValueInteger = parseInt(aValue); 586 const bValueInteger = parseInt(bValue); 587 if (isNaN(aValueInteger) || isNaN(bValueInteger)) { 588 return false; 589 } 590 return aValueInteger > bValueInteger; 591 } else if (operand === "is less than") { 592 const aValueInteger = parseInt(aValue); 593 const bValueInteger = parseInt(bValue); 594 if (isNaN(aValueInteger) || isNaN(bValueInteger)) { 595 return false; 596 } 597 return aValueInteger < bValueInteger; 598 } else if (operand === "is before date") { 599 if (aValueIsEmpty || bValueIsEmpty) { 600 return false; 601 } 602 const aDateValue = new Date(aValue); 603 const bDateValue = new Date(bValue); 604 const aValueIsValidDate = 605 aDateValue instanceof Date && isFinite(aDateValue); 606 const bValueIsValidDate = 607 bDateValue instanceof Date && isFinite(bDateValue); 608 if (!aValueIsValidDate || !bValueIsValidDate) { 609 return false; 610 } 611 return aDateValue < bDateValue; 612 } else if (operand === "is after date") { 613 if (aValueIsEmpty || bValueIsEmpty) { 614 return false; 615 } 616 const aDateValue = new Date(aValue); 617 const bDateValue = new Date(bValue); 618 const aValueIsValidDate = 619 aDateValue instanceof Date && isFinite(aDateValue); 620 const bValueIsValidDate = 621 bDateValue instanceof Date && isFinite(bDateValue); 622 if (!aValueIsValidDate || !bValueIsValidDate) { 623 return false; 624 } 625 return aDateValue > bDateValue; 626 } else if (operand === "is before time") { 627 if (aValueIsEmpty || bValueIsEmpty) { 628 return false; 629 } 630 const todaysDate = new Date().toDateString(); 631 const aDateValue = new Date(`${todaysDate} ${aValue}`); 632 const bDateValue = new Date(`${todaysDate} ${bValue}`); 633 const aValueIsValidDate = 634 aDateValue instanceof Date && isFinite(aDateValue); 635 const bValueIsValidDate = 636 bDateValue instanceof Date && isFinite(bDateValue); 637 if (!aValueIsValidDate || !bValueIsValidDate) { 638 return false; 639 } 640 return aDateValue < bDateValue; 641 } else if (operand === "is after time") { 642 if (aValueIsEmpty || bValueIsEmpty) { 643 return false; 644 } 645 const todaysDate = new Date().toDateString(); 646 const aDateValue = new Date(`${todaysDate} ${aValue}`); 647 const bDateValue = new Date(`${todaysDate} ${bValue}`); 648 const aValueIsValidDate = 649 aDateValue instanceof Date && isFinite(aDateValue); 650 const bValueIsValidDate = 651 bDateValue instanceof Date && isFinite(bDateValue); 652 if (!aValueIsValidDate || !bValueIsValidDate) { 653 return false; 654 } 655 return aDateValue > bDateValue; 656 } 657 return true; 658 } 659 660 function destroy(selector) { 661 const form = document.querySelector(selector); 662 663 if (!form) {
664 console.warn("[BREAKDANCE] Could not find form to destroy:", selector); 665 return; 666 } 667 668 unbindEvents(form); 669 } 670 671 function initSteps(form, isBuilder = false) { 672 if (form.dataset.steps == 0) return; 673 674 const nextStepButtons = form.querySelectorAll( 675 ".breakdance-form-button__next-step" 676 ); 677 678 nextStepButtons.forEach((button) => 679 button.addEventListener("click", () => { 680 nextStep(form, isBuilder); 681 }) 682 ); 683 684 const previousStepButtons = form.querySelectorAll( 685 ".breakdance-form-button__previous-step" 686 ); 687 688 previousStepButtons.forEach((button) => 689 button.addEventListener("click", () => { 690 previousStep(form); 691 }) 692 ); 693 694 showOrHideSteps(form); 695 } 696 697 function validateStep(form, step) { 698 const inputs = Array.from( 699 form.querySelectorAll( 700 `[data-form-step='${step}'] input,[data-form-step='${step}'] select, [data-form-step='${step}'] textarea` 701 ) 702 ); 703 return inputs.every((input) => { 704 input.reportValidity(); 705 return input.checkValidity(); 706 }); 707 } 708 709 function getFirstInputInStep(form, step) { 710 const firstFieldInStep = form.querySelector( 711 `.breakdance-form-field[data-form-step="${step}"]:not(.breakdance-form-field--hidden, .breakdance-form-field--html, .breakdance-form-field--condition-hidden)` 712 ); 713 714 if (firstFieldInStep) { 715 return firstFieldInStep.querySelector( 716 `input, textarea, select` 717 ); 718 } 719 720 return null; 721 } 722 723 function nextStep(form, isBuilder) { 724 const currentStep = parseInt(form.dataset.currentStep); 725 if (!validateStep(form, currentStep) && !isBuilder) { 726 return; 727 } 728 const nextStep = currentStep + 1; 729 setStep(form, nextStep); 730 731 // Set focus on first element in active step 732 const firstInput = getFirstInputInStep(form, nextStep); 733 734 if (firstInput) { 735 firstInput.focus(); 736 } 737 738 scrollIntoViewIfNeeded(form); 739 } 740 741 function previousStep(form) { 742 const currentStep = parseInt(form.dataset.currentStep); 743 setStep(form, currentStep - 1); 744 scrollIntoViewIfNeeded(form); 745 } 746 747 function setStep(form, step) { 748 form.dataset.currentStep = step.toString(); 749 showOrHideSteps(form); 750 } 751 752 function showOrHideSteps(form) { 753 const currentStep = parseInt(form.dataset.currentStep); 754 const totalSteps = parseInt(form.dataset.steps); 755 const fields = form.querySelectorAll( 756 ".breakdance-form-field:not(.breakdance-form-footer)" 757 ); 758 759 fields.forEach((field) => { 760 const formStep = parseInt(field.dataset.formStep); 761 if (formStep === currentStep) { 762 field.classList.remove("hidden-step"); 763 } else { 764 field.classList.add("hidden-step"); 765 } 766 }); 767 768 const submitButton = form.querySelector(".breakdance-form-button__submit"); 769 const nextStepButton = form.querySelector( 770 `.breakdance-form-field[data-form-step="${currentStep}"] .breakdance-form-button__next-step` 771 ); 772 const previousStepButton = form.querySelector( 773 `.breakdance-form-field[data-form-step="${currentStep}"] .breakdance-form-button__previous-step` 774 ); 775 if (currentStep === totalSteps) { 776 submitButton.classList.remove("hidden"); 777 nextStepButton.classList.add("hidden"); 778 } else { 779 submitButton.classList.add("hidden"); 780 nextStepButton.classList.remove("hidden"); 781 } 782 if (currentStep > 1) { 783 previousStepButton.classList.remove("hidden"); 784 } else { 785 previousStepButton.classList.add("hidden"); 786 } 787 788 const steps = form.querySelectorAll(".breakdance-form-stepper__step"); 789 790 if (steps) { 791 steps.forEach((step) => { 792 const stepperStep = parseInt(step.dataset.stepperStep); 793 if (stepperStep <= currentStep) { 794 step.classList.add("is-active"); 795 } else { 796 step.classList.remove("is-active"); 797 } 798 }); 799 const currentStepper = form.querySelector( 800 `[data-stepper-step="${currentStep}"]` 801 ); 802 if (currentStepper) { 803 currentStepper.classList.add("is-active"); 804 } 805 } 806 } 807 808 function resetSteps(form) { 809 setStep(form, 1); 810 } 811 812 function initMask(form, selector) { 813 if (typeof Maska === "undefined") return; 814 815 const tokens = { 816 'z': { 817 pattern: /[a-z|A-Z]/, 818 multiple: true 819 }, 820 'Z': { 821 pattern: /[A-Z]/, 822 multiple: true, 823 transform: (chr) => chr.toLocaleUpperCase(), 824 }, 825 0: { pattern: /[0-9]/, optional: true }, 826 9: { pattern: /[0-9]/, repeated: true }, 827 } 828 829 form.bdMask = new Maska.MaskInput(`${selector} [data-maska]`, { 830 tokens 831 }); 832 } 833 834 function init(selector) { 835 const form = document.querySelector(selector); 836 837 if (!form) { 838 console.warn("[BREAKDANCE] Could not find form:", selector); 839 return; 840 } 841 842 bindEvents(form);
843 initConditionalFields(form, true); 844 initSteps(form); 845 initMask(form, selector); 846 } 847 848 window.breakdanceForm = { 849 init, 850 destroy, 851 initMask, 852 initConditionalFields, 853 initSteps, 854 }; 855})();
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.