1( function () { 2 'use strict'; 3 4 var CHEVRON_PREV = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><path d="M15.5 5 8.5 12.5 15.5 20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>'; 5 var CHEVRON_NEXT = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 25 25"><path d="M9.5 5 16.5 12.5 9.5 20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>'; 6 7 // ââ Live mode (PHP generated Swiper HTML) âââââââââââââââââââââââââââââââââ 8 9 function doInitLiveSlider( el ) { 10 el.setAttribute( 'data-ecs-swiper-init', '1' ); 11 12 var settings = JSON.parse( el.getAttribute( 'data-ecs-slider-settings' ) || '{}' ); 13 14 if ( settings.navigation ) { 15 settings.navigation = { 16 prevEl: el.querySelector( '.elementor-swiper-button-prev' ), 17 nextEl: el.querySelector( '.elementor-swiper-button-next' ), 18 }; 19 } 20 if ( settings.pagination ) { 21 settings.pagination.el = el.querySelector( '.swiper-pagination' ); 22 } 23 24 new Swiper( el, settings ); // eslint-disable-line no-undef 25 } 26 27 function initLiveSlider( el ) { 28 if ( el.hasAttribute( 'data-ecs-swiper-init' ) ) { 29 return; 30 } 31 32 // Defer init if hidden (e.g. inside display:none responsive-version on desktop). 33 // ResizeObserver fires when CSS shows the element â media query triggered by viewport change. 34 if ( el.offsetWidth === 0 && typeof ResizeObserver !== 'undefined' ) { 35 el.setAttribute( 'data-ecs-swiper-init', 'pending' ); 36 var ro = new ResizeObserver( function ( entries ) { 37 if ( entries[ 0 ].contentRect.width > 0 ) { 38 ro.disconnect(); 39 el.removeAttribute( 'data-ecs-swiper-init' ); 40 doInitLiveSlider( el ); 41 } 42 } ); 43 ro.observe( el ); 44 return; 45 } 46 47 doInitLiveSlider( el ); 48 } 49 50 function initAllLiveSliders() { 51 document.querySelectorAll( '.ecs-swiper:not([data-ecs-swiper-init])' ).forEach( initLiveSlider ); 52 } 53 54 // ââ Editor mode (JS builds Swiper from existing DOM) ââââââââââââââââââââââ 55 56 /** 57 * Read slider control values from the parent editor's Backbone model. 58 * This is the same pattern used in ecs-editor-preview.js. 59 */ 60 function getEditorSettings( containerId ) { 61 try { 62 return window.parent.elementor.getContainer( containerId ).settings.toJSON(); 63 } catch ( e ) { 64 return {}; 65 } 66 } 67 68 function buildSwiperConfig( s ) { 69 var navigation = s.ecs_navigation || 'arrows'; 70 var showArrows = navigation === 'arrows' || navigation === 'both'; 71 var showDots = navigation === 'dots' || navigation === 'both'; 72 73 var colsDesktop = parseInt( s.ecs_slider_columns, 10 ) || 1; 74 var colsTablet = parseInt( s.ecs_slider_columns_tablet, 10 ) || colsDesktop; 75 var colsMobile = parseInt( s.ecs_slider_columns_mobile, 10 ) || colsTablet; 76 var layoutTablet = s.ecs_container_type_tablet || ''; 77 var layoutMobile = s.ecs_container_type_mobile || ''; 78 79 var config = { 80 slidesPerView: colsDesktop, 81 loop: s.ecs_loop === 'yes', 82 speed: parseInt( s.ecs_speed, 10 ) || 500, 83 spaceBetween: parseInt( s.ecs_space_between, 10 ) || 0, 84 breakpoints: { 85 0: { 86 slidesPerView: colsMobile, 87 enabled: layoutMobile !== 'flex' && layoutMobile !== 'grid', 88 }, 89 768: { 90 slidesPerView: colsTablet, 91 enabled: layoutTablet !== 'flex' && layoutTablet !== 'grid', 92 }, 93 1025: { 94 slidesPerView: colsDesktop, 95 enabled: true, 96 }, 97 }, 98 }; 99 100 if ( s.ecs_autoplay === 'yes' ) { 101 config.autoplay = { 102 delay: parseInt( s.ecs_autoplay_speed, 10 ) || 3000, 103 pauseOnMouseEnter: s.ecs_pause_on_hover === 'yes', 104 disableOnInteraction: false, 105 }; 106 } 107 108 return { config: config, showArrows: showArrows, showDots: showDots }; 109 } 110 111 /** 112 * Move real children back to their scope (e-con-inner or container) then remove swiper. 113 * Using real elements (not clones) keeps Backbone model-binding intact and allows editing. 114 */ 115 function destroyEditorSlider( containerEl ) { 116 var existing = containerEl.querySelector( ':scope > .ecs-editor-swiper' ); 117 if ( existing ) { 118 var inner = containerEl.querySelector( ':scope > .e-con-inner' ); 119 var scope = inner || containerEl; 120 // Move real children back from slides to scope, preserving order 121 existing.querySelectorAll( ':scope > .swiper-wrapper > .swiper-slide:not(.swiper-slide-duplicate)' ).forEach( function ( slide ) { 122 var child = Array.from( slide.children ).find( function ( el ) { 123 return el.dataset && el.dataset.id; 124 } ); 125 if ( child ) { 126 scope.appendChild( child ); 127 } 128 } ); 129 if ( existing.swiper ) { 130 existing.swiper.destroy( true, true ); 131 } 132 existing.remove(); 133 } 134 containerEl.classList.remove( 'ecs-editor-slider-active' ); 135 containerEl.removeAttribute( 'data-ecs-slider-key' ); 136 containerEl.removeAttribute( 'data-ecs-rebuild-key' ); 137 } 138 139 /** Cache key based on child IDs only (structural changes). */ 140 function sliderKey( children ) { 141 return children.map( function ( el ) { return el.getAttribute( 'data-id' ); } ).join( ',' ); 142 } 143 144 /** 145 * Key for settings that require HTML rebuild (navigation type). 146 * Non-structural settings (columns, speedâ¦) are updated in-place by 147 * updateEditorSliderParams() to avoid disturbing the live DOM. 148 */ 149 function rebuildKey( built ) { 150 return ( built.showArrows ? 'a' : '' ) + ( built.showDots ? 'd' : '' ); 151 } 152 153 /**
154 * Build editor swiper by MOVING real children into slides (not cloning). 155 * Real elements stay connected to their Backbone views so they remain editable. 156 * Loop is disabled in editor to prevent Swiper from creating duplicate [data-id] nodes. 157 */ 158 function buildEditorSlider( containerEl ) { 159 var id = containerEl.getAttribute( 'data-id' ); 160 var settings = getEditorSettings( id ); 161 162 var inner = containerEl.querySelector( ':scope > .e-con-inner' ); 163 var scope = inner || containerEl; 164 var children = Array.from( scope.children ).filter( function ( el ) { 165 return el.dataset && el.dataset.id; 166 } ); 167 168 if ( ! children.length ) { 169 return; 170 } 171 172 var built = buildSwiperConfig( settings ); 173 built.config.loop = false; // disable loop in editor â avoids duplicate [data-id] nodes 174 175 var swiperEl = document.createElement( 'div' ); 176 swiperEl.className = 'swiper ecs-swiper ecs-editor-swiper'; 177 178 var wrapperEl = document.createElement( 'div' ); 179 wrapperEl.className = 'swiper-wrapper'; 180 181 // Move real children into slides â preserves Backbone binding and allows editing 182 children.forEach( function ( child ) { 183 var slide = document.createElement( 'div' ); 184 slide.className = 'swiper-slide'; 185 slide.appendChild( child ); // MOVE, not clone 186 wrapperEl.appendChild( slide ); 187 } ); 188 swiperEl.appendChild( wrapperEl ); 189 190 if ( built.showArrows ) { 191 swiperEl.insertAdjacentHTML( 'beforeend', 192 '<div class="elementor-swiper-button elementor-swiper-button-prev" role="button" tabindex="0">' + CHEVRON_PREV + '</div>' + 193 '<div class="elementor-swiper-button elementor-swiper-button-next" role="button" tabindex="0">' + CHEVRON_NEXT + '</div>' 194 ); 195 built.config.navigation = { 196 prevEl: swiperEl.querySelector( '.elementor-swiper-button-prev' ), 197 nextEl: swiperEl.querySelector( '.elementor-swiper-button-next' ), 198 }; 199 } 200 201 if ( built.showDots ) { 202 swiperEl.insertAdjacentHTML( 'beforeend', '<div class="swiper-pagination"></div>' ); 203 built.config.pagination = { 204 el: swiperEl.querySelector( '.swiper-pagination' ), 205 clickable: true, 206 }; 207 } 208 209 // CSS class hides empty .e-con-inner (children were moved out into swiper slides) 210 containerEl.classList.add( 'ecs-editor-slider-active' ); 211 containerEl.appendChild( swiperEl ); 212 containerEl.setAttribute( 'data-ecs-slider-key', sliderKey( children ) ); 213 containerEl.setAttribute( 'data-ecs-rebuild-key', rebuildKey( built ) ); 214 215 if ( typeof Swiper !== 'undefined' ) { // eslint-disable-line no-undef 216 new Swiper( swiperEl, built.config ); // eslint-disable-line no-undef 217 } 218 } 219 220 function syncEditorSlider( containerEl ) { 221 // Backbone re-render may have removed .ecs-editor-swiper while leaving 222 // ecs-editor-slider-active class â reset so we fall through to rebuild. 223 if ( 224 containerEl.classList.contains( 'ecs-editor-slider-active' ) && 225 ! containerEl.querySelector( ':scope > .ecs-editor-swiper' ) 226 ) { 227 destroyEditorSlider( containerEl ); 228 } 229 230 var swiperEl = containerEl.querySelector( ':scope > .ecs-editor-swiper' ); 231 var inner = containerEl.querySelector( ':scope > .e-con-inner' ); 232 var scope = inner || containerEl; 233 234 var children; 235 if ( swiperEl ) { 236 // Slider active: real children live inside swiper slides 237 var slideChildren = Array.from( 238 swiperEl.querySelectorAll( ':scope > .swiper-wrapper > .swiper-slide:not(.swiper-slide-duplicate)' ) 239 ).map( function ( slide ) { 240 return Array.from( slide.children ).find( function ( el ) { 241 return el.dataset && el.dataset.id; 242 } ); 243 } ).filter( Boolean ); 244 245 // Plus any new direct scope children Backbone may have appended (new widget added) 246 var newScopeChildren = Array.from( scope.children ).filter( function ( el ) { 247 return el.dataset && el.dataset.id; 248 } ); 249 children = slideChildren.concat( newScopeChildren ); 250 } else { 251 children = Array.from( scope.children ).filter( function ( el ) { 252 return el.dataset && el.dataset.id; 253 } ); 254 } 255 256 var cached = containerEl.getAttribute( 'data-ecs-slider-key' ); 257 if ( cached !== null && cached === sliderKey( children ) ) { 258 return; // structure unchanged 259 } 260 261 destroyEditorSlider( containerEl ); // moves children back to scope 262 if ( children.length ) { 263 buildEditorSlider( containerEl ); 264 } 265 } 266
267 /** 268 * Return the resolved ecs_container_type for the current device mode. 269 * Inherits from larger breakpoints when device-specific value is empty. 270 * Mirrors the same logic in ecs-editor-preview.js (parent frame). 271 */ 272 function getResolvedType( containerEl ) { 273 var id = containerEl.getAttribute( 'data-id' ); 274 if ( ! id ) { return 'flex'; } 275 try { 276 var device = window.parent.elementor.channels.deviceMode.request( 'currentMode' ) || 'desktop'; 277 var settings = window.parent.elementor.getContainer( id ).settings; 278 var suffixes = { desktop: '', tablet: '_tablet', mobile: '_mobile' }; 279 var order = device === 'mobile' ? [ 'mobile', 'tablet', 'desktop' ] 280 : device === 'tablet' ? [ 'tablet', 'desktop' ] 281 : [ 'desktop' ]; 282 for ( var i = 0; i < order.length; i++ ) { 283 var val = settings.get( 'ecs_container_type' + suffixes[ order[ i ] ] ); 284 if ( val ) { return val; } 285 } 286 return 'flex'; 287 } catch ( e ) { return 'flex'; } 288 } 289 290 function syncAllEditorSliders() { 291 // Cleanup or rebuild stale active markers. 292 document.querySelectorAll( '.ecs-editor-slider-active' ).forEach( function ( el ) { 293 if ( getResolvedType( el ) !== 'slider' ) { 294 destroyEditorSlider( el ); 295 } else if ( ! el.querySelector( ':scope > .ecs-editor-swiper' ) ) { 296 // Elementor re-rendered and removed our swiper DOM â rebuild it. 297 destroyEditorSlider( el ); // clears stale ecs-editor-slider-active class 298 syncEditorSlider( el ); 299 } 300 } ); 301 // Build: any container with an ECS slider class that resolves to 'slider' now. 302 document.querySelectorAll( '.e-con[class*="-slider"]' ).forEach( function ( el ) { 303 if ( ! el.classList.contains( 'ecs-editor-slider-active' ) && getResolvedType( el ) === 'slider' ) { 304 syncEditorSlider( el ); 305 } 306 } ); 307 } 308 309 /** 310 * Update Swiper config in-place without destroy/clone. 311 * Called on settings panel changes (columns, speed, color, etc.). 312 * Falls back to full syncEditorSlider only when HTML structure must change 313 * (navigation type or loop â these require different DOM elements). 314 */ 315 function updateEditorSliderParams( containerEl ) { 316 var swiperEl = containerEl.querySelector( ':scope > .ecs-editor-swiper' ); 317 if ( ! swiperEl || ! swiperEl.swiper ) { 318 syncEditorSlider( containerEl ); // swiper missing â full rebuild 319 return; 320 } 321 322 var id = containerEl.getAttribute( 'data-id' ); 323 var settings = getEditorSettings( id ); 324 var built = buildSwiperConfig( settings ); 325 var swiper = swiperEl.swiper; 326 327 // Navigation type or loop change requires HTML rebuild (different DOM structure) 328 var currRebuildKey = rebuildKey( built ); 329 var prevRebuildKey = containerEl.getAttribute( 'data-ecs-rebuild-key' ); 330 if ( prevRebuildKey !== null && prevRebuildKey !== currRebuildKey ) { 331 syncEditorSlider( containerEl ); 332 return; 333 } 334 335 // Safe in-place param update â no cloning, no race condition 336 swiper.params.slidesPerView = built.config.slidesPerView; 337 swiper.params.speed = built.config.speed; 338 swiper.params.spaceBetween = built.config.spaceBetween; 339 swiper.params.breakpoints = built.config.breakpoints; 340 341 if ( built.config.autoplay ) { 342 swiper.params.autoplay = built.config.autoplay; 343 if ( swiper.autoplay && ! swiper.autoplay.running ) { 344 swiper.autoplay.start(); 345 } 346 } else if ( swiper.autoplay && swiper.autoplay.running ) { 347 swiper.autoplay.stop(); 348 swiper.params.autoplay = false; 349 } 350 351 swiper.update(); 352 } 353 354 function updateAllEditorSliderParams() { 355 // Clear the structural key so syncEditorSlider always rebuilds with 356 // fresh settings (column count, speed, etc. may have changed). 357 document.querySelectorAll( '.ecs-editor-slider-active' ).forEach( function ( el ) { 358 el.removeAttribute( 'data-ecs-slider-key' ); 359 syncEditorSlider( el ); 360 } ); 361 } 362 363 // ââ Entry point âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 364 365 var inEditMode = !! ( 366 window.elementorFrontend && 367 typeof elementorFrontend.isEditMode === 'function' && 368 elementorFrontend.isEditMode() 369 ); 370 371 if ( inEditMode ) { 372 var suppressObserver = false;
373 374 var observer = new MutationObserver( function ( mutations ) { 375 if ( suppressObserver ) { return; } 376 377 var relevant = false; 378 for ( var i = 0; i < mutations.length; i++ ) { 379 var m = mutations[ i ]; 380 var t = m.target; 381 // Skip mutations caused by our own swiper structure 382 if ( t && t.classList && ( 383 t.classList.contains( 'ecs-editor-swiper' ) || 384 t.classList.contains( 'swiper-wrapper' ) || 385 t.classList.contains( 'swiper-slide' ) 386 ) ) { 387 continue; 388 } 389 relevant = true; 390 break; 391 } 392 if ( relevant ) { 393 suppressObserver = true; 394 syncAllEditorSliders(); 395 Promise.resolve().then( function () { suppressObserver = false; } ); 396 } 397 } ); 398 399 function setupEditor() { 400 syncAllEditorSliders(); 401 observer.observe( document.body, { 402 childList: true, 403 subtree: true, 404 attributes: true, 405 attributeFilter: [ 'class', 'data-id' ], 406 } ); 407 408 // Safety rebuild at 800ms: catches cases where MutationObserver fires before 409 // Backbone has finished populating widget content. syncEditorSlider will 410 // also detect empty widgets on each MO-triggered call going forward. 411 setTimeout( syncAllEditorSliders, 800 ); 412 413 // Settings panel changes (columns, colors, speedâ¦) don't mutate the DOM so 414 // MutationObserver never fires. Update Swiper params in-place â no cloning, 415 // no race condition with Backbone re-renders. 416 try { 417 var settingsTimer = null; 418 // editor:change fires for panel control edits in real usage. 419 window.parent.elementor.channels.editor.on( 'change', function () { 420 clearTimeout( settingsTimer ); 421 settingsTimer = setTimeout( updateAllEditorSliderParams, 150 ); 422 } ); 423 // command:after fires for every $e.run() including programmatic changes. 424 window.parent.elementor.channels.data.on( 'command:after', function () { 425 clearTimeout( settingsTimer ); 426 settingsTimer = setTimeout( updateAllEditorSliderParams, 150 ); 427 } ); 428 // Device mode switch changes which type is active â rebuild sliders. 429 window.parent.elementor.channels.deviceMode.on( 'change', function () { 430 clearTimeout( settingsTimer ); 431 settingsTimer = setTimeout( syncAllEditorSliders, 200 ); 432 } ); 433 } catch ( e ) { /* cross-origin or elementor unavailable */ } 434 } 435 436 // Expose a hook for ecs-editor-preview.js (parent frame) to call when 437 // settings change. Cross-frame Backbone channels are unreliable for this. 438 window.ecsSliderSettingsChanged = function () { 439 // Re-sync existing active sliders with updated params. 440 document.querySelectorAll( '.ecs-editor-slider-active' ).forEach( function ( el ) { 441 el.removeAttribute( 'data-ecs-slider-key' ); 442 syncEditorSlider( el ); 443 } ); 444 // Build any new sliders that just became active (e.g. type just set to 'slider'). 445 syncAllEditorSliders(); 446 }; 447 448 // components:init already fired by the time a footer script loads. 449 // Run directly â Backbone views are ready by DOMContentLoaded. 450 if ( document.readyState === 'loading' ) { 451 document.addEventListener( 'DOMContentLoaded', function () { 452 setTimeout( setupEditor, 300 ); 453 } ); 454 } else { 455 setTimeout( setupEditor, 300 ); 456 } 457 458 } else { 459 // Live frontend: PHP already generated Swiper HTML. 460 if ( document.readyState === 'loading' ) { 461 document.addEventListener( 'DOMContentLoaded', initAllLiveSliders ); 462 } else { 463 initAllLiveSliders(); 464 } 465 466 if ( typeof MutationObserver !== 'undefined' ) { 467 var liveObserver = new MutationObserver( function ( mutations ) { 468 var found = false; 469 for ( var i = 0; i < mutations.length; i++ ) { 470 var added = mutations[ i ].addedNodes; 471 for ( var j = 0; j < added.length; j++ ) { 472 var node = added[ j ]; 473 if ( node.nodeType !== 1 ) { continue; } 474 if ( 475 ( node.classList && node.classList.contains( 'ecs-swiper' ) ) || 476 ( node.querySelector && node.querySelector( '.ecs-swiper:not([data-ecs-swiper-init])' ) ) 477 ) { 478 found = true; 479 break; 480 } 481 } 482 if ( found ) { break; } 483 } 484 if ( found ) { initAllLiveSliders(); } 485 } ); 486 liveObserver.observe( document.body, { childList: true, subtree: true } ); 487 } 488 } 489 490} )();
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.