1/* eslint-disable */ //TODO: be aware of unused vars 2function modal_script() { 3 4 const modals = document.querySelectorAll('.fr-modal'); 5 const focusableElements = 'a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'; 6 const higherIndexTriggerClass = 'fr-modal__trigger--higher-index'; 7 let closeSelector; 8 let fadeTime; 9 let isStartModalVideo; 10 let isInQueryBuilder = false; 11 let youtubePlayers = {}; 12 let youtubeApiInitiated = false; 13 // TODO @Hakira-Shymuy remove flag after feature finished and tested. 14 // let flag_enable_modal_exit_intent = 15 // window.hasOwnProperty('frames_modal_obj') && window.frames_modal_obj.hasOwnProperty('flag_enable_modal_exit_intent') ? 16 // window.frames_modal_obj.flag_enable_modal_exit_intent : false 17 // //console.log('enable trigger page load', flag_enable_modal_exit_intent) 18 // let flag_modal_slider_compatibility = window.hasOwnProperty('frames_modal_slider_obj') && window.frames_modal_slider_obj.hasOwnProperty('flag_modal_slider_compatibility') ? window.frames_modal_slider_obj.flag_modal_slider_compatibility : false 19 let flag_enable_modal_exit_intent = window.frames_modal_obj?.flag_enable_modal_exit_intent ?? false; 20 if (flag_enable_modal_exit_intent) { 21 console.log('flag modal_exit_intent is on'); 22 } 23 let flag_modal_slider_compatibility = window.frames_modal_slider_obj?.flag_modal_slider_compatibility ?? false; 24 if (flag_modal_slider_compatibility) { 25 console.log('flag modal_slider_compatibility is on'); 26 } 27 28 /** 29 * Helper function 30 * @param {string} data - The JSON string to parse, safely replaces HTML entities 31 * @returns {Object|null}
31 The parsed JSON object, or null if parsin fails 32 */ 33 function parseJSONData(data) { 34 try { 35 return JSON.parse(data.replace(/"/g, '"')); 36 } catch (e) { 37 console.error('Error parsing JSON data:', e); 38 return null; 39 } 40 } 41 42 /** 43 * Get the data from modal attributes for configuration 44 * @param {HTMLElement} modal - The modal element 45 * @returns {Object} Object containing modal data for configuration 46 */ 47 function getDataAttributesForModal(modal) { 48 // Trigger for modal ( Selector ) 49 let modalTrigger; 50 51 // Trigger for modal ( Actions, Time, Page load ) 52 let modalTriggerConfig = 53 modal.dataset.frModalTriggerConfig ? parseJSONData(modal.dataset.frModalTriggerConfig) : []; 54 // Modal shown repeat rules 55 let modalRepeatActions = 56 modal.dataset.frModalRepeatActions ? parseJSONData(modal.dataset.frModalRepeatActions) : null; 57 58 if (modal.dataset.frModalTrigger) { 59 modalTrigger = modal.dataset.frModalTrigger; 60 61 } 62 if (modal.dataset.frModalTriggerEdge) { 63 edgeToEdge = modal.dataset.frModalTriggerEdge; 64 65 } 66 if (modal.dataset.frModalClose) { 67 closeSelector = modal.dataset.frModalClose; 68 69 } 70 if (modal.dataset.frModalFadeTime) { 71 fadeTime = Number(modal.dataset.frModalFadeTime); 72 73 } 74 if (modal.dataset.frVideoAutoplay) { 75 isStartModalVideo = modal.dataset.frVideoAutoplay === 'true' ? true : false; 76 } 77 78 if (modal.dataset.frModalQueryBuilder) { 79 isInQueryBuilder = modal.dataset.frModalQueryBuilder === 'true' ? true : false; 80 } else { 81 isInQueryBuilder = false; 82 } 83 84 85 return { modalTrigger, modalTriggerConfig, modalRepeatActions }; 86 } 87 88 /** 89 * Initializes modal triggers and maps with the corresponding modal 90 * Processes selector-based and action-based triggers 91 */ 92 function generateModalTriggerConnection() { 93 94 const modalsByTrigger = new Map(); 95 const modalsByAction = new Map(); 96 97 /** 98 * Iterates over modals that are not party of a Loop 99 * Binds the modals to the respective Triggers either by Selector or Actions 100 */ 101 modalsNotInQueryBuilder.forEach(modal => { 102 const { modalTrigger, modalTriggerConfig, modalRepeatActions } = getDataAttributesForModal(modal); 103 104 //handle modals with Trigger type of Selector 105 if (modalTrigger) { 106 if (!modalsByTrigger.has(modalTrigger)) { 107 modalsByTrigger.set(modalTrigger, new Set()); 108 } 109 modalsByTrigger.get(modalTrigger).add(modal); 110 } 111 112 //handle modals with Trigger type of Action 113 if (modalTriggerConfig) { 114 modalTriggerConfig.forEach(config => { 115 const actionType = config.type; 116 if (!modalsByAction.has(actionType)) { 117 modalsByAction.set(actionType, new Set()); 118 } 119 120 modalsByAction.get(actionType).add({ modal, modalRepeatActions, config }); 121 }); 122 } 123 124 //binds the modal with the action and the repeat type for the action 125 modalsByAction.forEach((modalsWithActions, action) => { 126 modalsWithActions.forEach(({ modal, modalRepeatActions, config }) => { 127 handleAction(action, modal, config, modalRepeatActions); 128 }); 129 130 }); 131 132 //changed the type of loop to be the same as modalByAction, more readable 133 modalsByTrigger.forEach((modals, triggerSelector) => { 134 const triggers = document.querySelectorAll(triggerSelector); 135 136 triggers.forEach(trigger => { 137 const modal = Array.from(modals)[0]; 138 const modalID = modal.id; 139 trigger.setAttribute('href', `#${modalID}`); 140 trigger.setAttribute('tabindex', '0'); 141 trigger.setAttribute('role', 'button'); 142 openModalWithTrigger(trigger); 143 focusFirstElementOnOpen(trigger); 144 trapFocusInModal(trigger); 145 initModalRelativeToTrigger(trigger); 146 147 }); 148 149 }); 150 151 }); 152 } 153 154 155 function initModalRelativeToTrigger(trigger) { 156 let windowWidth = window.innerWidth; 157 modalRelativeToTrigger(trigger, windowWidth); 158 159 window.addEventListener('resize', () => { 160 windowWidth = window.innerWidth; 161 modalRelativeToTrigger(trigger, windowWidth); 162 }); 163 164 window.addEventListener('orientationchange', () => { 165 windowWidth = window.innerWidth; 166 modalRelativeToTrigger(trigger, windowWidth); 167 }); 168 169 window.addEventListener('scroll', () => { 170 windowWidth = window.innerWidth; 171 modalRelativeToTrigger(trigger, windowWidth); 172 }); 173 } 174 175 function modalRelativeToTrigger(trigger, windowWidth) { 176 177 const modal = document.querySelector(trigger.getAttribute('href')); 178 if (!modal) { 179 return; 180 } 181 const container = modal.querySelector('.fr-modal__body'); 182 if (!container) { 183 return; 184 } 185 let positionRelatedToTrigger = modal.dataset.frModalPositionRelatedToTrigger; 186 let placeFromTriggers = modal.dataset.frModalPlaceFromTriggers; 187 let xTriggerOffset = modal.dataset.frModalTriggerXoffset; 188 let yTriggerOffset = modal.dataset.frModalTriggerYoffset; 189 const edgeToEdge = modal.dataset.frModalTriggerEdge === 'true'; 190 191 // positionRelatedToTrigger === 'top' || 'bottom' 192 // placeFromTriggers === 'left' || 'right' || 'center' || 'full' || 'container' 193 194 if (xTriggerOffset === '0') { 195 xTriggerOffset = '0px'; 196 } 197 if (yTriggerOffset === '0') { 198 yTriggerOffset = '0px'; 199 } 200 201 const triggerRect = trigger.getBoundingClientRect(); 202 const triggerTopSpace = triggerRect.top; 203 const triggerLeftSpace = triggerRect.left; 204 const triggerWidth = triggerRect.width;
205 const triggerHeight = triggerRect.height; 206 const containerWidth = container.getBoundingClientRect().width; 207 const containerHeight = container.getBoundingClientRect().height; 208 209 let breakpoint; 210 if (modal.dataset.frModalCenterOption) { 211 centerOption = JSON.parse(modal.dataset.frModalCenterOption); 212 breakpoint = centerOption.centerOnBreakpoint; 213 } 214 215 216 if (placeFromTriggers === 'left' && triggerLeftSpace < containerWidth) { 217 placeFromTriggers = 'right'; 218 } 219 220 if (placeFromTriggers === 'right' && windowWidth - (triggerLeftSpace + triggerWidth) < containerWidth) { 221 placeFromTriggers = 'left'; 222 } 223 224 if (positionRelatedToTrigger === 'bottom' && window.innerHeight - triggerTopSpace - triggerHeight < containerHeight) { 225 positionRelatedToTrigger = 'top'; 226 } 227 228 if (positionRelatedToTrigger === 'top' && triggerTopSpace < containerHeight) { 229 positionRelatedToTrigger = 'bottom'; 230 } 231 232 233 container.style.position = 'fixed'; 234 235 if (positionRelatedToTrigger === 'bottom') { 236 container.style.top = `calc(${triggerTopSpace + triggerHeight}px + ${yTriggerOffset})`; 237 container.style.bottom = ''; 238 } else if (positionRelatedToTrigger === 'top') { 239 container.style.bottom = `calc(${window.innerHeight - triggerTopSpace}px + ${yTriggerOffset})`; 240 container.style.top = ''; 241 } 242 243 function positionModal() { 244 let scaleSize = 0; 245 246 if ((placeFromTriggers === 'left' || placeFromTriggers === 'right') && breakpoint && window.innerWidth <= breakpoint) { 247 248 container.style.left = '50%'; 249 container.style.transform = 'translateX(-50%)'; 250 251 } else { 252 if (placeFromTriggers === 'left') { 253 let leftPosition = triggerLeftSpace - containerWidth - scaleSize; 254 if (edgeToEdge) { 255 leftPosition += triggerWidth; 256 } 257 const finalLeftPosition = `calc(${leftPosition}px - ${xTriggerOffset})`; 258 259 container.style.maxWidth = `${triggerLeftSpace}px`; 260 container.style.left = finalLeftPosition; 261 262 } else if (placeFromTriggers === 'right') { 263 let rightPosition = triggerLeftSpace + triggerWidth; 264 const rightSpace = windowWidth - rightPosition; 265 if (edgeToEdge) { 266 rightPosition -= triggerWidth; 267 } 268 const finalRightPosition = `calc(${rightPosition}px + ${xTriggerOffset})`; 269 270 container.style.left = finalRightPosition; 271 container.style.maxWidth = `${rightSpace}px`; 272 273 } else if (placeFromTriggers === 'center') { 274 container.style.left = `calc(${triggerLeftSpace + triggerWidth / 2}px - ${containerWidth / 2}px)`; 275 } else if (placeFromTriggers === 'full') { 276 container.style.left = '0'; 277 container.style.maxWidth = '100%'; 278 container.style.width = '100%'; 279 } else if (placeFromTriggers === 'container') { 280 container.style.left = '50%'; 281 container.style.transform = 'translateX(-50%)'; 282 container.style.maxWidth = 'var(--content-width)'; 283 container.style.width = '100%'; 284 } 285 } 286 } 287 positionModal(); 288 } 289 290 291 292 function openModalWithTrigger(trigger) { 293 if (!trigger.hasEventListener) { 294 trigger.addEventListener('click', (e) => { 295 const modalId = trigger.getAttribute('href'); 296 const modal = document.querySelector(modalId); 297 298 if (modal.classList.contains('fr-modal--open') && modal.dataset.frModalSameCloseTrigger === 'true') { 299 // If the modal is open and sameCloseTrigger is true, close it 300 closeModal(modal, e); 301 } else { 302 // Otherwise, open it 303 openModal(e, trigger); 304 initModalRelativeToTrigger(trigger); 305 } 306 }); 307 308 trigger.addEventListener('keydown', (e) => { 309 if (e.key === 'Enter') { 310 const modalId = trigger.getAttribute('href'); 311 const modal = document.querySelector(modalId); 312 313 if (modal.classList.contains('fr-modal--open') && modal.dataset.frModalSameCloseTrigger === 'true') { 314 // If the modal is open and sameCloseTrigger is true, close it 315 closeModal(modal, e); 316 } else { 317 // Otherwise, open it 318 openModal(e, trigger); 319 initModalRelativeToTrigger(trigger); 320 } 321 } 322 }); 323 324 // Set flag to indicate event listeners are attached 325 trigger.hasEventListener = true; 326 } 327 328 // Get the modal associated with this trigger 329 const modalId = trigger.getAttribute('href'); 330 const modal = document.querySelector(modalId); 331 332 // Stop click events inside the modal from bubbling up to the trigger 333 if (modal) { 334 const modalBody = modal.querySelector('.fr-modal__body'); 335 336 if (modalBody) { 337 modalBody.addEventListener('click', (e) => { 338 // add check for bricks nav menu 339 if (!e.target.closest('.brxe-nav-menu')) { 340 e.stopPropagation(); 341 } 342 }); 343 } 344 } 345 } 346 347 348 349 350 function openModal(e, trigger) { 351 352 e.preventDefault(); 353 const modalId = trigger.getAttribute('href'); 354 const modal = document.querySelector(modalId); 355 // document.body.appendChild(modal); 356 357 if (modal) { 358 modal.triggerElement = trigger; 359 } 360 if (flag_modal_slider_compatibility) { 361 slidesCompatibility(modal); 362 } 363 364 365 if (!modal.closest('header')) { 366 modal.classList.add('fr-modal--open'); 367 368 } else { 369 modal.classList.toggle('fr-modal--open'); 370 } 371 modal.setAttribute('aria-hidden', 'false'); 372 modal.style.display = 'flex'; 373 374 375 if ('ontouchstart' in window) { 376 scrollingToggle(modal); 377 } else { 378 scrollingToggle(modal); 379 } 380 381 if (isTriggerAHamburgerWidget(trigger)) { 382 trigger.classList.toggle(higherIndexTriggerClass); 383 } 384 385 initVideo(modal); 386 initAudio(modal); 387 388 focusFirstElementInModal(e, trigger); 389 } 390 391 function slidesCompatibility(modal) { 392 393 let isInsideSlider = modal.closest('.splide__slide'); 394 395 if (!bricksIsFrontend || !isInsideSlider) return; 396 const mainElement = document.querySelector('main'); 397 if (mainElement) { 398 mainElement.appendChild(modal); 399 } else { 400 document.body.appendChild(modal); 401 } 402 } 403
404 /** 405 * Toggles the visibility of a action-triggered modal 406 * I choose to create a new function to avoid complexity attached with the openModal function 407 * @param {HTMLElement} modal - The mapped modal to reveal or hide. 408 */ 409 function revealModal(modal) { 410 const isOpen = modal.classList.contains('fr-modal--open'); 411 412 if (!isOpen) { 413 modal.classList.toggle('fr-modal--open'); 414 modal.setAttribute('aria-hidden', 'false'); 415 modal.style.display = 'flex'; 416 } else { 417 modal.classList.remove('fr-modal--open'); 418 modal.setAttribute('aria-hidden', 'true'); 419 // modal.style.display = 'none' 420 } 421 422 scrollingToggle(modal); 423 initVideo(modal); 424 initAudio(modal); 425 } 426 427 /** 428 * Execute the appropriate action to trigger a modal 429 * @param {string} action - Type of action to handle (e.g, 'mouseLeaveViewport') 430 * @param {HTMLElement} modal - The modal to apply the action 431 * @param {Object} modalRepeatActions - Configuration for how the modal should show again 432 * @param {Object} config - Settings for the Modal type of Action 433 */ 434 function handleAction(action, modal, config, modalRepeatActions) { 435 switch (action) { 436 case 'mouseLeaveViewport': 437 handleMouseLeaveViewport(modal, modalRepeatActions); 438 break; 439 case 'onPageLoad': 440 handleOnPageLoad(modal, config, modalRepeatActions); 441 break; 442 case 'onInactivity': 443 handleOnInactivity(modal, config, modalRepeatActions); 444 break; 445 case 'onHover': 446 handleOnHover(modal, config, modalRepeatActions); 447 break; 448 449 default: 450 console.log(`no action defined: ${action}`); 451 } 452 } 453 454 // TODO: @Hakira-Shymuy handleOnScrollToEl 455 if (flag_enable_modal_exit_intent) { 456 function handleAction(action, modal, config, modalRepeatActions) { 457 switch (action) { 458 case 'mouseLeaveViewport': 459 handleMouseLeaveViewport(modal, modalRepeatActions); 460 break; 461 case 'onPageLoad': 462 handleOnPageLoad(modal, config, modalRepeatActions); 463 break; 464 case 'onInactivity': 465 handleOnInactivity(modal, config, modalRepeatActions); 466 break; 467 case 'onHover': 468 handleOnHover(modal, config, modalRepeatActions); 469 break; 470 case 'onScrollToEl': 471 handleOnScrollToEl(modal, config, modalRepeatActions); 472 break; 473 474 default: 475 console.log(`no action defined: ${action}`); 476 } 477 } 478 }; 479 480 //TODO: if everything works, remove this it's a duplicate of handleAction 481 // function handleAction(action, modal, config, modalRepeatActions) { 482 // switch (action) { 483 // case 'mouseLeaveViewport': 484 // handleMouseLeaveViewport(modal, modalRepeatActions); 485 // break; 486 // case 'onPageLoad': 487 // handleOnPageLoad(modal, config, modalRepeatActions); 488 // break; 489 // case 'onInactivity': 490 // handleOnInactivity(modal, config, modalRepeatActions); 491 // break; 492 // case 'onHover': 493 // handleOnHover(modal, config, modalRepeatActions); 494 // break; 495 496 // default: 497 // console.log(`no action defined: ${action}`) 498 // } 499 // }; 500 501 /** 502 * Reveals a modal when mouse leave viewport 503 * @param {HTMLElement} modal 504 * @param {Object} modalRepeatActions 505 */ 506 function handleMouseLeaveViewport(modal, modalRepeatActions) { 507 508 const listener = (event) => { 509 if (!event.relatedTarget && !event.toElement) { 510 const action = modalRepeatActions.repeatActions; 511 const shouldReveal = handleRepeatActions(modal, action); 512 513 if (shouldReveal) { 514 revealModal(modal); 515 } 516 } 517 }; 518 519 if (!modal.hasAttribute('data-mouse-listener-added')) { 520 document.body.addEventListener('mouseleave', listener); 521 modal.setAttribute('data-mouse-listener-added', 'true'); 522 } 523 } 524 525 /** 526 * Creates a throttled function that only invokes `func` once per every `limit` milliseconds 527 * this helper function is used to control the rate at we call the functions to check for 528 * the activity events, otherwise we can affect the performance of the site 529 * 530 * @param {Function} func - The function to be throttled 531 * @param {number} limit - The time limit in milliseconds for the throttle 532 * @returns {Function} - A throttled version of the input function 533 */ 534 function throttle(func, limit) { 535 // Hold the timeout for the function to be executed 536 let lastFunc; 537 // Stores the timestamp of the last time that we ran the function 538 let lastRan; 539 540 return function () { 541 // Context from the function can be an object, window, etc, the args are passed on the function call 542 const context = this; 543 const args = arguments; 544 545 if (!lastRan) { 546 // If the function wasnt called then execute it
547 func.apply(context, args); 548 lastRan = Date.now(); 549 } else { 550 clearTimeout(lastFunc); 551 552 // Set a new execution scheduled 553 lastFunc = setTimeout(function () { 554 if ((Date.now() - lastRan) >= limit) { 555 func.apply(context, args); 556 lastRan = Date.now(); 557 } 558 }, Math.max(limit - (Date.now() - lastRan), 0)); 559 } 560 }; 561 } 562 563 /** 564 * Reveals a modal with a delay when the page loads 565 * @param {HTMLElement} modal 566 * @param {Object} modalRepeatActions 567 */ 568 function handleOnPageLoad(modal, config, modalRepeatActions) { 569 570 // was using typeof config.options.afterPageLoad !== 'number') , but we are actually sending a number inside of a string 571 if (!config || !config.options || isNaN(Number(config.options.afterPageLoad))) { 572 console.error('After Page Load option is not a valid number or does not exist', modal); 573 return; 574 } 575 576 const delayOption = config.options.afterPageLoad; 577 const delay = delayOption * 1000; 578 setTimeout(() => { 579 const shouldReveal = handleRepeatActions(modal, modalRepeatActions.repeatActions); 580 if (shouldReveal) { 581 revealModal(modal); 582 } 583 }, delay); 584 } 585 586 /** 587 * Reveals a modal when user hovers over specified elements 588 * @param {HTMLElement} modal - The modal element to be revealed 589 * @param {Object} config - Settings for the Modal type of Action 590 * @param {Object} modalRepeatActions - Configuration for how the modal should show again 591 */ 592 function handleOnHover(modal, config, modalRepeatActions) { 593 if (!config || !config.options || !config.options.hoverSelector) { 594 console.error('Invalid config or hover selector not found in config:', config); 595 return; 596 } 597 const hoverSelector = config.options.hoverSelector; 598 const elements = document.querySelectorAll(hoverSelector); 599 if (!elements.length) { 600 console.warn(`Modal hover trigger: No elements found with selector "${hoverSelector}"`); 601 return; 602 } 603 // Create a function to handle the hover event 604 const handleHover = () => { 605 if (!modal.classList.contains('fr-modal--open')) { 606 const shouldReveal = handleRepeatActions(modal, modalRepeatActions.repeatActions); 607 if (shouldReveal) { 608 revealModal(modal); 609 } 610 } 611 }; 612 // Add hover listeners to all matching elements 613 elements.forEach(element => { 614 element.addEventListener('mouseenter', handleHover); 615 }); 616 } 617 618 619 /** 620 * Handle On Inactivity Action 621 * uses the throttle function to throttle the addEventListeners to dont spam the Page 622 * 623 * @param {HTMLElement} modal 624 * @param {Object} config - Settings for the Modal type of Action 625 * @param {*} modalRepeatActions 626 */ 627 function handleOnInactivity(modal, config, modalRepeatActions) { 628 if (!config || 629 !config.options || 630 !['mouseInactivity', 'keyboardInactivity', 'scrollInactivity'].some(key => config.options[key]) || 631 !config.options.inactivityTime) { 632 633 console.warn('Inactivity Config does not have any (option) or (time) set', config.options); 634 return; 635 } 636 637 const inactivityTime = config.options.inactivityTime * 1000; 638 let inactivityTimeout; 639 640 const resetInactivityTimeout = () => { 641 clearTimeout(inactivityTimeout); 642 643 inactivityTimeout = setTimeout(() => { 644 const shouldReveal = handleRepeatActions(modal, modalRepeatActions.repeatActions); 645 646 if (shouldReveal) { 647 revealModal(modal); 648 } 649 }, inactivityTime); 650 }; 651 652 const throttledResetTimeout = throttle(resetInactivityTimeout, 150); 653 654 if (config.options.mouseInactivity) { 655 document.addEventListener('mousemove', throttledResetTimeout); 656 } 657 if (config.options.keyboardInactivity) { 658 document.addEventListener('keydown', throttledResetTimeout); 659 } 660 if (config.options.scrollInactivity) { 661 document.addEventListener('scroll', throttledResetTimeout); 662 } 663 664 resetInactivityTimeout(); 665 } 666 667 if (flag_enable_modal_exit_intent) { 668 // TODO @Hakira-Shymuy before release put handleOnScrollToEl under Flag Feature, and exclude from release. 669 // TODO: @Hakira-Shymuy not working on sections if it does not have any other container without a Slider, might be Splide.JS 670 /** 671 * IF there is another container than not a slider or contains a slider it is working 672 */ 673 /** 674 * Handle on Scroll To Element 675 * @param {HTMLElement} modal 676 * @param {Object} config 677 * @param {*} modalRepeatActions 678 * @returns 679 */ 680 function handleOnScrollToEl(modal, config, modalRepeatActions) { 681 if (!config || !config.options || typeof config.options.scrollSelector !== 'string') { 682 console.error('Invalid config or scroll Selector not found in config:', config); 683 return; 684 } 685 686 const targetElement = document.querySelector(config.options.scrollSelector); 687 console.log('selector', targetElement); 688 689 if (!targetElement) { 690 console.error(`Selector not found: ${config.options.scrollSelector}`); 691 return; 692 } 693 694 if (bricksIsFrontend) { 695 console.log('is bricks frontend'); 696 const observerCallback = (entries, observer) => { 697 console.log('observer callback');
698 entries.forEach(entry => { 699 console.log('entry', entry); 700 if (entry.isIntersecting) { 701 console.log('is intersecting'); 702 const shouldReveal = handleRepeatActions(modal, modalRepeatActions.repeatActions); 703 704 if (shouldReveal) { 705 console.log('should reveal'); 706 revealModal(modal); 707 observer.unobserve(entry.target); 708 } 709 } 710 }); 711 }; 712 713 const selectorThreshold = config.options.selectorThreshold ? parseInt(config.options.selectorThreshold, 10) / 100 : 0.5; 714 const observer = new IntersectionObserver(observerCallback, { 715 root: null, 716 threshold: selectorThreshold, 717 }); 718 719 observer.observe(targetElement); 720 console.log(observer); 721 console.log(targetElement); 722 } 723 } 724 } 725 // End Flag 726 727 728 /** 729 * Determines if a modal should be shown based on repeat action configuration 730 * @param {HTMLElement} modal 731 * @param {string} action - The type of repeat action to handle 732 * @returns {boolean} True if the modal should be shown 733 */ 734 function handleRepeatActions(modal, action) { 735 switch (action) { 736 case 'neverShowAgain': 737 return handleNeverShowAgain(modal); 738 case 'perPageVisit': 739 return handleOncePerPageVisit(modal); 740 case 'alwaysShow': 741 return true; 742 743 default: 744 console.log('Unexpected modalRepeatActions value:', modalRepeatActions); 745 return false; 746 } 747 } 748 749 /** 750 * Handles the logic for modals that should never be shown again 751 * @param {HTMLElement} modal 752 * @returns {boolean} 753 */ 754 function handleNeverShowAgain(modal) { 755 const modalID = modal.id; 756 const modalKey = `frames_neverShowAgainModal_${modalID}`; 757 const neverShow = localStorage.getItem(modalKey) === 'true'; 758 759 if (!neverShow) { 760 localStorage.setItem(modalKey, 'true'); 761 return true; 762 } 763 return false; 764 } 765 766 /** 767 * @handleOncePerPageVisit 768 * 769 * Helper for Firefox, Firefox persists the running of javascript through back_forward navigation 770 * performance.navigation is deprecated 771 * https://developer.mozilla.org/en-US/docs/Web/API/Performance/navigation 772 * 773 */ 774 function clearFramesSessionStorage() { 775 for (let i = sessionStorage.length - 1; i >= 0; i--) { 776 const key = sessionStorage.key(i); 777 if (key.startsWith('framesModal_')) { 778 sessionStorage.removeItem(key); 779 } 780 } 781 } 782 783 window.addEventListener('pageshow', (event) => { 784 if (event.persisted || window.PerformanceEventTiming.type === 2) { 785 clearFramesSessionStorage(); 786 } 787 }); 788 789 window.addEventListener('load', () => { 790 clearFramesSessionStorage(); 791 }); 792 793 function handleOncePerPageVisit(modal) { 794 const modalId = modal.id; 795 const modalShownKey = `framesModal_oncePerPageVisitModal_${modalId}`; 796 const shownThisVisit = sessionStorage.getItem(modalShownKey) === 'true'; 797 798 if (!shownThisVisit) { 799 sessionStorage.setItem(modalShownKey, true); 800 return true; 801 } 802 803 return false; 804 } 805 806 // audio 807 function initAudio(modal) { 808 if (isHtmlAudio(modal)) { 809 initHtmlAudio(modal); 810 } 811 // Later, you can add checks for other audio types here. 812 } 813 814 function isHtmlAudio(modal) { 815 const audio = modal.querySelector('audio'); 816 return audio?.src !== undefined; 817 } 818 819 function initHtmlAudio(modal) { 820 const audio = modal.querySelector('audio'); 821 console.log('audio', audio); 822 if (modal.dataset.frModalVideoAutoplay === 'true') { 823 audio.play(); 824 } 825 } 826 827 function stopAudio(modal) { 828 stopHtmlAudio(modal); 829 } 830 831 function stopHtmlAudio(modal) { 832 if (!isHtmlAudio(modal)) return; 833 const audio = modal.querySelector('audio'); 834 if (audio) { 835 audio.pause(); 836 } 837 } 838 839 // video 840 function initVideo(modal) { 841 842 if (isYoutubeVideo(modal)) { 843 initYoutubePlayer(modal); 844 } else if (isVimeoVideo(modal)) { 845 initVimeoPlayer(modal); 846 } else if (isHtmlVideo(modal)) { 847 initHtmlVideo(modal); 848 } 849 // Later, you can add checks for other video types here. 850 } 851 852 function isHtmlVideo(modal) { 853 if (!modal.querySelector('video')) return false; 854 const video = modal.querySelector('video'); 855 return video && video.src; 856 } 857 858 function isVimeoVideo(modal) { 859 if (!modal.querySelector('iframe')) return false; 860 const iframe = modal.querySelector('iframe'); 861 862 return iframe && iframe.src.includes('vimeo.com'); 863 } 864 865 function initVimeoPlayer(modal) { 866 const iframe = modal.querySelector('iframe'); 867 if (modal.dataset.frModalVideoAutoplay === 'true') {
868 const iframeSrc = iframe.src; 869 870 iframe.src = `${iframeSrc}&autoplay=1`; 871 } 872 } 873 874 function isYoutubeVideo(modal) { 875 if (!modal.querySelector('iframe')) return false; 876 const iframe = modal.querySelector('iframe'); 877 let isYoutube = false; 878 if (iframe && iframe.src.includes('youtube.com')) { 879 isYoutube = true; 880 } 881 return isYoutube && window.YT && window.YT.Player; 882 } 883 884 885 // Load YouTube API if there are any YouTube videos in the modals 886 887 window.addEventListener('load', () => { 888 let modalsWithYoutubeVideoCounter = 0; 889 modals.forEach(modal => { 890 if (!modal.querySelector('iframe')) return false; 891 const iframe = modal.querySelector('iframe'); 892 if (iframe.src.includes('youtube.com')) { 893 loadYoutubeApi(); 894 modalsWithYoutubeVideoCounter++; 895 } 896 }); 897 }); 898 899 function initHtmlVideo(modal) { 900 const video = modal.querySelector('video'); 901 if (modal.dataset.frModalVideoAutoplay === 'true') { 902 video.play(); 903 } 904 } 905 906 function initYoutubePlayer(modal) { 907 const iframe = modal.querySelector('iframe'); 908 const videoId = iframe.src.split('embed/')[1].split('?')[0]; 909 910 if (!iframe.player) { 911 iframe.player = new YT.Player(iframe, { 912 videoId: videoId, 913 events: { 914 'onReady': function (event) { 915 if (modal.dataset.frModalVideoAutoplay === 'true') { 916 event.target.playVideo(); 917 } 918 } 919 } 920 }); 921 } else if (modal.dataset.frModalVideoAutoplay === 'true') { 922 iframe.player.playVideo(); 923 } 924 } 925 926 function loadYoutubeApi() { 927 if (window.YT && window.YT.Player) { 928 // YouTube API is already loaded 929 return; 930 } 931 932 // This function will be called when the script has finished loading 933 window.onYouTubeIframeAPIReady = function () { 934 // YouTube API is ready to use 935 // console.log('YouTube IFrame API is ready'); 936 // Initialize video players or anything else that depends on the API here 937 }; 938 939 // Create a new script element for the YouTube IFrame API 940 var tag = document.createElement('script'); 941 tag.src = 'https://www.youtube.com/iframe_api'; 942 var firstScriptTag = document.getElementsByTagName('script')[0]; 943 firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); 944 } 945 946 947 function stopVideo(modal) { 948 stopYoutubeVideo(modal); 949 stopVimeoVideo(modal); 950 stopHtmlVideo(modal); 951 } 952 953 function stopHtmlVideo(modal) { 954 if (!isHtmlVideo(modal)) return; 955 const video = modal.querySelector('video'); 956 if (video) { 957 video.pause(); 958 } 959 } 960 961 function stopYoutubeVideo(modal) { 962 if (!isYoutubeVideo(modal)) return; 963 const iframe = modal.querySelector('iframe'); 964 if (iframe && iframe.player) { 965 iframe.player.stopVideo(); 966 } 967 } 968 969 function stopVimeoVideo(modal) { 970 if (!isVimeoVideo(modal)) return; 971 const iframe = modal.querySelector('iframe'); 972 const iframeSrc = iframe.src; 973 iframe.src = iframeSrc.replace('&autoplay=1', ''); 974 } 975 976 function closeModalWithHamburgerWidget(modals) { 977 modals.forEach(modal => { 978 const trigger = document.querySelector(`[href="#${modal.id}"]`); 979 if (isTriggerAHamburgerWidget(trigger)) { 980 981 addEventListenerWithEventInfo(trigger, 'click', (e) => { 982 if (modal.classList.contains('fr-modal--open') && !trigger.classList.contains(higherIndexTriggerClass)) { 983 closeModal(modal, e); 984 } 985 }); 986 987 addEventListenerWithEventInfo(trigger, 'keydown', (e) => { 988 if (e.key === 'Enter') { 989 if (modal.classList.contains('fr-modal--open') && !trigger.classList.contains(higherIndexTriggerClass)) { 990 closeModal(modal, e); 991 } 992 } 993 }); 994 } 995 }); 996 } 997 998 999 function closeModalWithCloseIcon(modals) { 1000 modals.forEach(modal => { 1001 if (modal.querySelector('.fr-modal__close-icon-wrapper')) { 1002 const fadeTime = Number(modal.dataset.frModalFadeTime); 1003 const closeIcon = modal.querySelector('.fr-modal__close-icon-wrapper'); 1004 1005 addEventListenerWithEventInfo(closeIcon, 'click', (e) => { 1006 closeModal(modal, e); 1007 removeActiveBurgerClassOnModalClose(modal); 1008 }); 1009 1010 addEventListenerWithEventInfo(closeIcon, 'keydown', (e) => { 1011 if (e.key === 'Enter') { 1012 closeModal(modal, e); 1013 removeActiveBurgerClassOnModalClose(modal); 1014 } 1015 }); 1016 } 1017 }); 1018 1019 } 1020
1021 function closeModalWithEscKey(modals) { 1022 document.addEventListener('keydown', (e) => { 1023 if (e.key === 'Escape') { 1024 modals.forEach(modal => { 1025 const fadeTime = Number(modal.dataset.frModalFadeTime); 1026 if (modal.classList.contains('fr-modal--open')) { 1027 closeModal(modal, e); 1028 removeActiveBurgerClassOnModalClose(modal); 1029 } 1030 }); 1031 } 1032 1033 1034 }); 1035 } 1036 1037 function closeModalWithACustomSelector(modals) { 1038 modals.forEach(modal => { 1039 getDataAttributesForModal(modal); 1040 if (modal.querySelector(closeSelector)) { 1041 const closeElement = modal.querySelector(closeSelector); 1042 if (closeElement) { 1043 addEventListenerWithEventInfo(closeElement, 'click', (e) => { 1044 closeModal(modal, e); 1045 removeActiveBurgerClassOnModalClose(modal); 1046 }); 1047 } 1048 } 1049 }); 1050 } 1051 1052 1053 1054 function closeModalOnOverlayClick(modals) { 1055 modals.forEach(modal => { 1056 let disableCloseOutside = modal.dataset.frModalDisableCloseOutside === 'true'; 1057 1058 if (!disableCloseOutside) { 1059 const fadeTime = Number(modal.dataset.frModalFadeTime); 1060 const overlay = modal.querySelector('.fr-modal__overlay'); 1061 overlay.addEventListener('click', (e) => { 1062 closeModal(modal, e); 1063 removeActiveBurgerClassOnModalClose(modal); 1064 }); 1065 } 1066 }); 1067 } 1068 1069 1070 1071 1072 /** 1073 * wrapper function for the addEventListener to help get the type of interaction 1074 * 1075 * @param {HTMLElement} element - the DOM element that will receive the event 1076 * @param {string} eventType - represents the type of event (click, keydown) 1077 * @param {Function} callback - the functions to be called when the event is triggered 1078 */ 1079 function addEventListenerWithEventInfo(element, eventType, callback) { 1080 element.addEventListener(eventType, (event) => { 1081 event.isKeyboardEvent = (eventType === 'keydown'); 1082 1083 callback(event); 1084 }); 1085 } 1086 1087 1088 function closeModal(modal, closeTrigger) { 1089 const fadeTime = parseInt(modal.dataset.frModalFadeTime); 1090 const fadeOutTime = parseInt(modal.dataset.frModalFadeOutTime) || fadeTime; 1091 const currentScrollPosition = window.scrollY; 1092 let disableCloseOutside = modal.dataset.frModalDisableCloseOutside === 'true'; 1093 let sameCloseTrigger = modal.dataset.frModalSameCloseTrigger === 'true'; 1094 1095 if (closeTrigger && closeTrigger.target.classList.contains('fr-modal__overlay') && disableCloseOutside) { 1096 return; 1097 } 1098 1099 let isTriggerClose = closeTrigger && closeTrigger.target.closest(modal.dataset.frModalTrigger); 1100 1101 //TODO remove this 1102 // if (!isTriggerClose && sameCloseTrigger && closeTrigger && 1103 // !closeTrigger.target.classList.contains('fr-modal__close-icon') && 1104 // !closeTrigger.target.closest('.fr-modal__close-icon') && 1105 // !closeTrigger.target.closest(modal.dataset.frModalClose)) { 1106 // return; 1107 // } 1108 1109 if (closeTrigger) { 1110 closeTrigger.stopPropagation(); 1111 } 1112 1113 modal.classList.add('fr-modal--closing'); 1114 modal.classList.remove('fr-modal--open'); 1115 modal.setAttribute('aria-hidden', 'true'); 1116 1117 setTimeout(() => { 1118 modal.classList.remove('fr-modal--closing'); 1119 scrollingToggle(modal); 1120 window.scrollTo(0, currentScrollPosition); 1121 }, fadeOutTime); 1122 1123 stopVideo(modal); 1124 stopAudio(modal); 1125 1126 if (isTriggerClose && sameCloseTrigger) { 1127 const trigger = closeTrigger.target.closest(modal.dataset.frModalTrigger); 1128 if (trigger) { 1129 trigger.classList.remove('fr-modal__trigger--higher-index'); 1130 if (trigger.classList.contains('fr-hamburger--active')) { 1131 trigger.classList.remove('fr-hamburger--active'); 1132 } 1133 if (trigger.classList.contains('fr-button-trigger--active')) { 1134 trigger.classList.remove('fr-button-trigger--active'); 1135 } 1136 } 1137 } 1138 1139 if (closeTrigger.isKeyboardEvent) { 1140 setFocusBackToTrigger(modal); 1141 } 1142 } 1143 1144 1145 1146 1147 1148 function setFocusBackToTrigger(modal) { 1149 1150 const modalTrigger = modal.triggerElement; 1151 1152 if (modalTrigger) { 1153 const fadeOutTime = parseInt(modal.dataset.frModalFadeOutTime) || 300; 1154 setTimeout(() => { 1155 modalTrigger.focus(); 1156 }, fadeOutTime); 1157 } 1158 } 1159 1160 1161 1162 1163 1164 1165 // Global Modal Functions 1166 1167 function setTransitionTime(modals) { 1168 modals.forEach(modal => { 1169 const fadeTime = Number(modal.dataset.frModalFadeTime); 1170 const fadeOutTime = Number(modal.dataset.frModalFadeOutTime); 1171 const modalBody = modal.querySelector('.fr-modal__body'); 1172 1173 // modal.style.transition = `all ${fadeTime}ms ease-in-out`; 1174 // modalBody.style.transition = `all ${fadeTime}ms ease-in-out`; 1175 1176 modal.style.setProperty('--fade-in-time', `${fadeTime}ms`); 1177 modal.style.setProperty('--fade-out-time', `${fadeOutTime}ms`); 1178 }); 1179 } 1180 1181 // Focus Functions 1182 1183 function focusFirstElementOnOpen(trigger) { 1184 // trigger.addEventListener('click', (e) => { 1185 // focusFirstElementInModal(e, trigger) 1186 // }); 1187 trigger.addEventListener('keydown', (e) => { 1188 if (e.key === 'Enter') { 1189 e.preventDefault(); 1190 focusFirstElementInModal(e, trigger); 1191 } 1192 }); 1193 } 1194 1195 function focusFirstElementInModal(e, trigger) { 1196 e.preventDefault(); 1197 const modalId = trigger.getAttribute('href'); 1198 const modal = document.querySelector(modalId); 1199 const modalBody = modal.querySelector('.fr-modal__body'); 1200 const fadeTime = Number(modal.dataset.frModalFadeTime); 1201 1202 const setModalFocus = () => { 1203 const focusableContent = modalBody.querySelectorAll(focusableElements); 1204 1205 if (focusableContent.length > 0) { 1206 focusableContent[0].focus(); 1207 } else { 1208 modalBody.setAttribute('tabindex', '-1'); 1209 modalBody.focus(); 1210 } 1211 }; 1212 1213 if (fadeTime >= 100) { 1214 setTimeout(() => { 1215 setModalFocus(); 1216 }, fadeTime); 1217 } else { 1218 setTimeout(() => { 1219 setModalFocus(); 1220 }, 100); 1221 } 1222 } 1223 1224 function trapFocusInModal(trigger) { 1225 const modalId = trigger.getAttribute('href'); 1226 const modal = document.querySelector(modalId); 1227 const modalBody = modal.querySelector('.fr-modal__body'); 1228 const closeButton = modal.querySelector('.fr-modal__close-icon'); 1229 const customCloseButton = modal.dataset.frModalClose ? 1230 modal.querySelector(modal.dataset.frModalClose) : null; 1231 1232 function handleFocusTrap(e) { 1233 // Get all focusable elements, including close buttons 1234 const focusableContent = [ 1235 ...(closeButton ? [closeButton] : []), 1236 ...(customCloseButton ? [customCloseButton] : []), 1237 ...Array.from(modalBody.querySelectorAll(focusableElements)) 1238 ].filter(el => el.offsetParent !== null); // Filter out any hidden elements 1239 1240 if (focusableContent.length === 0) { 1241 focusableContent.push(modalBody); 1242 modalBody.setAttribute('tabindex', '-1'); 1243 } 1244 1245 const firstElement = focusableContent[0]; 1246 const lastElement = focusableContent[focusableContent.length - 1]; 1247 1248 if (e.key === 'Tab') { 1249 if (e.shiftKey && document.activeElement === firstElement) { 1250 e.preventDefault(); 1251 lastElement.focus(); 1252 } else if (!e.shiftKey && document.activeElement === lastElement) { 1253 e.preventDefault(); 1254 firstElement.focus(); 1255 } 1256 } 1257 } 1258 1259 modal.removeEventListener('keydown', handleFocusTrap); 1260 modal.addEventListener('keydown', handleFocusTrap); 1261 } 1262 1263 1264 1265 1266 1267 1268 // Focus Functions Helpers 1269 1270 function loopThroughFocusElements(firstFocusableElement, lastFocusableElement) { 1271 firstFocusableElement.addEventListener('keydown', (e) => { 1272 if (e.key === 'Tab' && e.shiftKey) { 1273 e.preventDefault(); 1274 lastFocusableElement.focus(); 1275 } 1276 }); 1277 1278 lastFocusableElement.addEventListener('keydown', (e) => { 1279 if (e.key === 'Tab' && !e.shiftKey) { 1280 e.preventDefault(); 1281 firstFocusableElement.focus(); 1282 } 1283 }); 1284 } 1285 1286 function preventScrolling() { 1287 document.body.style.overflow = 'hidden'; 1288 } 1289 1290 1291 1292 const scrollBarWidth = window.innerWidth - document.documentElement.clientWidth; 1293 document.documentElement.style.setProperty('--fr-scrollbar-width', `${scrollBarWidth}px`); 1294 1295 function scrollingToggle(modal) { 1296 if (modal.dataset.frModalAllowScroll === 'true') return; 1297 1298 const bodyHeight = document.body.scrollHeight; 1299 const windowHeight = window.innerHeight; 1300 1301 if (bodyHeight > windowHeight) { 1302 if (window.innerWidth > 767 && scrollBarWidth > 0) { 1303 document.body.classList.toggle('fr-modal-body-padding'); 1304 } 1305 } 1306 1307 document.body.classList.toggle('fr-modal-prevent-scroll'); 1308 if (modal.getAttribute('aria-hidden') === 'true' && document.body.classList.contains('fr-modal-prevent-scroll')) { 1309 document.body.classList.remove('fr-modal-prevent-scroll'); 1310 } 1311 } 1312 1313 function removeAllInBuilder() { 1314 const modals = document.querySelectorAll('.iframe .fr-modal'); 1315 modalsInQueryBuilder.forEach(modal => { 1316 if (modal !== modals[0]) { 1317 modal.style.display = 'none'; 1318 } 1319 modals[0].style.display = 'flex'; 1320 }); 1321 } 1322 1323 function setModalBodyToOverflowScroll(modal) { 1324 const modalBody = modal.querySelector('.fr-modal__body'); 1325 if (modal.dataset.frModalScroll === 'true') { 1326 modalBody.style.overflow = 'scroll'; 1327 } 1328 else { 1329 return; 1330 } 1331 } 1332 1333 1334 function removeActiveBurgerClassOnModalClose(modal) { 1335 const trigger = document.querySelector(`[href="#${modal.id}"]`); 1336 if (isTriggerAHamburgerWidget(trigger)) { 1337 if (trigger.classList.contains(higherIndexTriggerClass)) { 1338 trigger.classList.remove(higherIndexTriggerClass); 1339 // if trigger containe fr-hamburger--active 1340 if (trigger.classList.contains('fr-hamburger--active')) { 1341 trigger.classList.remove('fr-hamburger--active'); 1342 } 1343 if (trigger.classList.contains('fr-button-trigger--active')) { 1344 let options = {}; 1345 if (trigger.dataset.frTriggerOptions) { 1346 try { 1347 options = JSON.parse(trigger.dataset.frTriggerOptions); 1348 } catch (error) {
1349 console.error('Error parsing frTriggerOptions', error); 1350 } 1351 } 1352 1353 if (options.useActiveText) { 1354 1355 trigger.querySelector('.fr-button-trigger__text').innerHTML = options.buttonText; 1356 1357 } 1358 trigger.classList.remove('fr-button-trigger--active'); 1359 } 1360 } 1361 } 1362 } 1363 1364 function isTriggerAHamburgerWidget(trigger) { 1365 1366 if (trigger) { 1367 if (trigger.classList.contains('brxe-fr-trigger')) { 1368 return true; 1369 } else { 1370 return false; 1371 } 1372 } 1373 } 1374 1375 function showOnlyFirstModalInBuilderInsideQuery() { 1376 const modals = document.querySelectorAll('.iframe .fr-modal'); 1377 modals.forEach((modal, index) => { 1378 1379 if (modal.dataset.frModalInsideQuery === 'true' && index !== 0) { 1380 modal.remove(); 1381 } 1382 }); 1383 } 1384 1385 // Logic 1386 1387 1388 1389 for (let i = 0; i < modals.length; i++) { 1390 const modal = modals[i]; 1391 modal.classList.remove('fr-modal--hide'); 1392 } 1393 1394 for (let i = 0; i < modals.length; i++) { 1395 const modal = modals[i]; 1396 getDataAttributesForModal(modal); 1397 } 1398 1399 1400 function generateModalTriggerConnectionInQueryBuilder() { 1401 const modalsIDs = []; 1402 modalsInQueryBuilder.forEach((modal, index) => { 1403 const triggerSelector = modal.dataset.frModalTrigger; 1404 modalsIDs.push(modal.id); 1405 const triggers = document.querySelectorAll(triggerSelector); 1406 triggers.forEach((trigger, index) => { 1407 let sharedParent = trigger.closest(`.${modal.dataset.frModalQueryId}`); 1408 1409 if (sharedParent) { 1410 let innerModal, innerTrigger; 1411 if (sharedParent === trigger) { 1412 // Trigger is the sharedParent itself 1413 innerModal = sharedParent.querySelector('.fr-modal'); 1414 innerTrigger = trigger; 1415 } else { 1416 innerModal = sharedParent.querySelector('.fr-modal'); 1417 innerTrigger = sharedParent.querySelector(`${innerModal.dataset.frModalTrigger}`); 1418 } 1419 1420 let newID; 1421 if (document.querySelectorAll(`#${innerModal.id}`).length > 1) { 1422 newID = `${innerModal.id}-${Math.random().toString(36).substr(2, 5)}`; 1423 } else { 1424 newID = innerModal.id; 1425 } 1426 innerModal.id = newID; 1427 innerTrigger.setAttribute('href', `#${innerModal.id}`); 1428 if (innerModal.id === innerTrigger.getAttribute('href').slice(1)) { 1429 innerTrigger.setAttribute('tabindex', '0'); 1430 innerTrigger.setAttribute('role', 'button'); 1431 openModalWithTrigger(innerTrigger); 1432 focusFirstElementOnOpen(innerTrigger); 1433 trapFocusInModal(innerTrigger); 1434 } 1435 } 1436 }); 1437 1438 }); 1439 } 1440 1441 1442 1443 const modalsInQueryBuilder = Array.from(modals).filter(modal => modal.dataset.frModalInsideQuery === 'true'); 1444 const modalsNotInQueryBuilder = Array.from(modals).filter(modal => modal.dataset.frModalInsideQuery === 'false'); 1445 1446 1447 generateModalTriggerConnection(); 1448 generateModalTriggerConnectionInQueryBuilder(); 1449 1450 // modals.forEach(modal => { 1451 // setModalBodyToOverflowScroll(modal) 1452 // }); 1453 1454 1455 1456 setTransitionTime(modals); 1457 1458 // Close Modal Functions 1459 closeModalWithHamburgerWidget(modals); 1460 closeModalWithEscKey(modals); 1461 if (!document.querySelector('.iframe .fr-modal')) { 1462 closeModalOnOverlayClick(modals); 1463 closeModalWithACustomSelector(modals); 1464 closeModalWithCloseIcon(modals); 1465 } 1466 1467 // Builder Functions 1468 showOnlyFirstModalInBuilderInsideQuery(); 1469 1470} 1471 1472 1473function wpgb_modal_script() { 1474 window.WP_Grid_Builder && WP_Grid_Builder.on('init', function (wpgb) { 1475 1476 // console.log(wpgb); // Holds all instances. 1477 // console.log(wpgb.element); // Holds all instances. 1478 1479 if (wpgb.element.classList.contains('wp-grid-builder')) return; 1480 wpgb.facets.on('appended', function (nodes) { 1481 1482 modal_script(); 1483 1484 }); 1485 1486 }); 1487} 1488 1489function runModalScriptsOnBricksFilters() { 1490 if (typeof window.bricksFilters !== 'function') return; 1491 1492 1493 const bricksFilterRun = bricksFiltersFn.run; 1494 bricksFiltersFn.run = function () { 1495 1496 bricksFilterRun.apply(); 1497 modal_script(); 1498 }; 1499 1500} 1501 1502document.addEventListener('DOMContentLoaded', function (e) { 1503 bricksIsFrontend && modal_script(); 1504 bricksIsFrontend && wpgb_modal_script(); 1505 bricksIsFrontend && runModalScriptsOnBricksFilters(); 1506});
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.