1/** 2 * Highlight Slider Block - JavaScript 3 * 4 * @package StudioSwift 5 */ 6 7(function () { 8 'use strict'; 9 10 const DESKTOP_BREAKPOINT = '(min-width: 1199px)'; 11 12 /** 13 * Set active state for a brand scroller item by index. 14 * 15 * @param {NodeList} items 16 * @param {NodeList} images 17 * @param {NodeList} overlayItems 18 * @param {number|null} index 19 */ 20 function setActiveItem(items, images, overlayItems, index) { 21 items.forEach((item) => item.classList.remove('is-active')); 22 images.forEach((img) => img.classList.remove('is-active')); 23 overlayItems.forEach((item) => item.classList.remove('is-active')); 24 25 if (index === null || index < 0) { 26 return; 27 } 28 29 if (items[index]) { 30 items[index].classList.add('is-active'); 31 } 32 33 if (images[index]) { 34 images[index].classList.add('is-active'); 35 } 36 37 if (overlayItems[index]) { 38 overlayItems[index].classList.add('is-active'); 39 } 40 } 41 42 /** 43 * Tear down an initialized brand scroller instance. 44 * 45 * @param {HTMLElement} brandScroller 46 */ 47 function destroyHighlightSlider(brandScroller) { 48 if (brandScroller._highlightSliderCleanup) { 49 brandScroller._highlightSliderCleanup(); 50 delete brandScroller._highlightSliderCleanup; 51 } 52 53 delete brandScroller.dataset.highlightSliderInit; 54 } 55 56 /** 57 * Initialize Highlight Slider functionality 58 */ 59 function initHighlightSlider() { 60 const brandScroller = document.querySelector('.js-brand-scroller'); 61 62 // Element not present (e.g. editor preview with no content yet). 63 if (!brandScroller) { 64 return; 65 } 66 67 if (brandScroller.dataset.highlightSliderInit === 'true') { 68 destroyHighlightSlider(brandScroller); 69 } 70 71 brandScroller.dataset.highlightSliderInit = 'true'; 72 73 const brandScrollerItems = brandScroller.querySelectorAll('.js-brand-scroller-item'); 74 const brandScrollerImages = brandScroller.querySelectorAll('.js-brand-scroller-bg'); 75 const brandScrollerOverlayItems = brandScroller.querySelectorAll('.js-brand-scroller-overlay-item'); 76 const brandScrollerListItems = brandScroller.querySelectorAll('.js-brand-scroller-list > li'); 77 const brandScrollerCount = brandScrollerItems.length; 78 const desktopMediaQuery = window.matchMedia(DESKTOP_BREAKPOINT); 79 80 let scrollTween = null; 81 let previousTargetItem = null; 82 let lastHoveredIndex = null; 83 let hoverHandlers = null; 84 85 function destroyScrollMode() { 86 if (scrollTween) { 87 if (scrollTween.scrollTrigger) { 88 scrollTween.scrollTrigger.kill(); 89 } 90 scrollTween.kill(); 91 scrollTween = null; 92 } 93 94 previousTargetItem = null; 95 } 96 97 function destroyHoverMode() { 98 if (!hoverHandlers) { 99 return; 100 } 101 102 hoverHandlers.listItems.forEach((listItem, index) => { 103 listItem.removeEventListener('mouseenter', hoverHandlers.onItemEnter[index]); 104 }); 105 hoverHandlers = null; 106 lastHoveredIndex = null; 107 } 108 109 function resetActiveState() { 110 brandScroller.classList.remove('brand-scroller--active'); 111 setActiveItem(brandScrollerItems, brandScrollerImages, brandScrollerOverlayItems, null); 112 previousTargetItem = null; 113 } 114 115 function initScrollMode() { 116 gsap.registerPlugin(ScrollTrigger); 117 resetActiveState(); 118 119 scrollTween = gsap.to(brandScroller, { 120 scrollTrigger: { 121 trigger: brandScroller, 122 start: 'top top', 123 end: '+=100%', 124 pin: true, 125 scrub: 2, 126 onUpdate: (self) => { 127 const progress = self.progress; 128 const targetItem = Math.min( 129 Math.floor(progress * brandScrollerCount) + 1, 130 brandScrollerCount 131 ); 132 133 if (targetItem === previousTargetItem) { 134 return; 135 } 136 137 setActiveItem( 138 brandScrollerItems, 139 brandScrollerImages, 140 brandScrollerOverlayItems, 141 targetItem - 1 142 ); 143 previousTargetItem = targetItem; 144 }, 145 onEnter: () => { 146 brandScroller.classList.add('brand-scroller--active'); 147 }, 148 onLeaveBack: () => { 149 resetActiveState(); 150 }, 151 }, 152 }); 153 } 154 155 function setHoveredItem(index) { 156 lastHoveredIndex = index; 157 brandScroller.classList.add('brand-scroller--active'); 158 setActiveItem( 159 brandScrollerItems, 160 brandScrollerImages, 161 brandScrollerOverlayItems, 162 index 163 ); 164 } 165 166 function initHoverMode() { 167 resetActiveState(); 168 lastHoveredIndex = null; 169 170 const onItemEnter = []; 171
172 brandScrollerListItems.forEach((listItem, index) => { 173 const handler = () => { 174 if (index === lastHoveredIndex) { 175 return; 176 } 177 178 setHoveredItem(index); 179 }; 180 181 onItemEnter[index] = handler; 182 listItem.addEventListener('mouseenter', handler); 183 }); 184 185 hoverHandlers = { 186 listItems: brandScrollerListItems, 187 onItemEnter, 188 }; 189 } 190 191 function setupMode() { 192 if (desktopMediaQuery.matches) { 193 destroyScrollMode(); 194 initHoverMode(); 195 } else { 196 destroyHoverMode(); 197 initScrollMode(); 198 } 199 200 if (typeof ScrollTrigger !== 'undefined') { 201 ScrollTrigger.refresh(); 202 } 203 } 204 205 const onBreakpointChange = () => { 206 setupMode(); 207 }; 208 209 setupMode(); 210 desktopMediaQuery.addEventListener('change', onBreakpointChange); 211 212 brandScroller._highlightSliderCleanup = () => { 213 desktopMediaQuery.removeEventListener('change', onBreakpointChange); 214 destroyScrollMode(); 215 destroyHoverMode(); 216 resetActiveState(); 217 }; 218 } 219 220 // Refresh ScrollTrigger pin dimensions when viewport size changes (single listener) 221 let resizeTimeout; 222 window.addEventListener('resize', () => { 223 clearTimeout(resizeTimeout); 224 resizeTimeout = setTimeout(() => { 225 if (typeof ScrollTrigger !== 'undefined') { 226 ScrollTrigger.refresh(); 227 } 228 }, 100); 229 }); 230 231 // Initialize on DOM ready 232 if (document.readyState === 'loading') { 233 document.addEventListener('DOMContentLoaded', initHighlightSlider); 234 } else { 235 initHighlightSlider(); 236 } 237 238 // Reinitialize for dynamically loaded content (e.g., Gutenberg preview) 239 if (window.acf) { 240 window.acf.addAction('render_block_preview/type=highlight-slider', initHighlightSlider); 241 } 242})();
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.