1let downScrollAttempts = 0; 2let footerShown = false; 3let lastScrollTop = 0; 4let lastScrollTime = Date.now(); 5let footerAnimationTimer = null; 6const SCROLL_COOLDOWN = 300; // Minimum time between scroll checks in ms 7const SCROLL_THRESHOLD = 50; // Pixels from bottom to consider as "bottom" 8const FOOTER_TRANSITION_MS = 600; 9const PRODUCT_FOOTER_REQUEST_EVENT = 'labindia:footer-request'; 10const FOOTER_STATE_EVENT = 'labindia:footer-state'; 11 12function isHorizontalProductFooterControlled() { 13 const controller = window.productHorizontalScroll; 14 15 return Boolean( 16 document.querySelector('[data-product-scroll-container]') && 17 document.documentElement.classList.contains('product-view-page') && 18 !document.body.classList.contains('product-scroll-fallback') && 19 controller && 20 controller.controlsFooter === true 21 ); 22} 23 24function emitFooterState(open, options) { 25 const settings = options || {}; 26 27 window.dispatchEvent(new CustomEvent(FOOTER_STATE_EVENT, { 28 detail: { 29 open: open, 30 source: settings.source || 'legacy', 31 input: settings.input || 'unknown' 32 } 33 })); 34} 35 36// Utility function to check if an element is scrollable 37function isScrollable(element) { 38 if (!element || !(element instanceof Element)) { 39 return false; // Return false if the element is not a valid DOM element 40 } 41 42 const style = window.getComputedStyle(element); 43 return ( 44 (style.overflow === 'auto' || style.overflow === 'scroll' || style.overflowY === 'auto' || style.overflowY === 'scroll') && 45 element.scrollHeight > element.clientHeight 46 ); 47} 48 49 50// Function to determine if scrolling is happening within a scrollable container 51function isScrollEventFromScrollableElement(event) { 52 let element = event.target; 53 while (element && element !== document.body) { 54 if (isScrollable(element)) { 55 const { scrollTop, scrollHeight, clientHeight } = element; 56 // Check if the element is at its scrollable bottom 57 if (scrollTop + clientHeight < scrollHeight - SCROLL_THRESHOLD) { 58 return true; // Still scrollable, don't trigger footer logic 59 } 60 } 61 element = element.parentElement; // Traverse up to check ancestors 62 } 63 return false; 64} 65 66// Function to check if the window is scrolled to the bottom 67function isScrolledToBottom() { 68 const windowHeight = window.innerHeight; 69 const documentHeight = document.documentElement.scrollHeight; 70 const scrollTop = window.pageYOffset || document.documentElement.scrollTop; 71 return documentHeight - (scrollTop + windowHeight) <= SCROLL_THRESHOLD; 72} 73 74// Debounced scroll check 75function canProcessScroll() { 76 const now = Date.now(); 77 if (now - lastScrollTime >= SCROLL_COOLDOWN) { 78 lastScrollTime = now; 79 return true; 80 } 81 return false; 82} 83 84// Hide the footer with animation 85function hideFooter(options) { 86 const settings = options || {}; 87 const footer = document.querySelector('.footer-sidebar'); 88 89 if (!footer) return false; 90 if (!footerShown && !footer.classList.contains('active')) return true; 91 92 clearTimeout(footerAnimationTimer); 93 footerShown = false; 94 downScrollAttempts = 0; 95 footer.style.transition = 'right 0.6s ease, opacity 0.6s ease'; 96 footer.style.right = '-50%'; 97 footer.style.opacity = '0'; 98 footer.classList.remove('active'); 99 emitFooterState(false, settings); 100 101 footerAnimationTimer = setTimeout(() => { 102 footer.style.visibility = 'hidden'; 103 footerAnimationTimer = null; 104 }, FOOTER_TRANSITION_MS); 105 106 return true; 107} 108 109// Show the footer with animation 110function showFooter(options) { 111 const settings = options || {}; 112 const requireBottom = settings.requireBottom !== false; 113 const footer = document.querySelector('.footer-sidebar'); 114 115 if (!footer || (requireBottom && !isScrolledToBottom())) return false; 116 if (footerShown && footer.classList.contains('active')) return true; 117 118 clearTimeout(footerAnimationTimer); 119 footer.style.visibility = 'visible'; 120 footer.style.transition = 'right 0.6s ease, opacity 0.6s ease'; 121 footer.style.right = '0'; 122 footer.style.opacity = '1'; 123 footer.classList.add('active'); 124 footerShown = true; 125 downScrollAttempts = 0; 126 emitFooterState(true, settings); 127 128 if (settings.input === 'keyboard') { 129 const closeButton = document.getElementById('footerToggle'); 130 131 if (closeButton && typeof closeButton.focus === 'function') { 132 closeButton.focus({ 133 preventScroll: true 134 }); 135 } 136 } 137 138 footerAnimationTimer = setTimeout(() => { 139 footerAnimationTimer = null; 140 }, FOOTER_TRANSITION_MS); 141 142 return true; 143} 144 145window.addEventListener(PRODUCT_FOOTER_REQUEST_EVENT, (event) => { 146 const detail = event.detail || {}; 147
148 if (detail.source !== 'product-horizontal') return; 149 150 // Always accept the product controller's close request. During responsive 151 // teardown, the mode/body class may change before cleanup finishes. 152 if (detail.action === 'close') { 153 hideFooter({ 154 source: detail.source, 155 input: detail.input 156 }); 157 return; 158 } 159 160 if ( 161 detail.action !== 'open' || 162 !isHorizontalProductFooterControlled() 163 ) return; 164 165 const controller = window.productHorizontalScroll; 166 const state = typeof controller.getState === 'function' ? 167 controller.getState() : 168 null; 169 170 const isAtFinalPanel = state && 171 state.currentIndex === state.panelCount - 1 && 172 Math.abs(window.scrollY - state.lastPanelStop) <= 6; 173 174 if (!isAtFinalPanel) return; 175 176 showFooter({ 177 requireBottom: false, 178 source: detail.source, 179 input: detail.input 180 }); 181}); 182 183// Wheel event handler 184window.addEventListener('wheel', (event) => { 185 if (isHorizontalProductFooterControlled()) return; 186 if (!canProcessScroll() || isScrollEventFromScrollableElement(event)) return; 187 188 if (footerShown && event.deltaY < 0) { 189 hideFooter(); 190 } else if (!footerShown && isScrolledToBottom()) { 191 if (event.deltaY > 0) { 192 downScrollAttempts++; 193 if (downScrollAttempts >= 2) { 194 showFooter(); 195 } 196 } 197 } else { 198 downScrollAttempts = 0; 199 } 200}); 201 202// Touch handling 203let touchStartY = 0; 204let lastTouchTime = Date.now(); 205const TOUCH_COOLDOWN = 150; 206 207function canProcessTouch() { 208 const now = Date.now(); 209 if (now - lastTouchTime >= TOUCH_COOLDOWN) { 210 lastTouchTime = now; 211 return true; 212 } 213 return false; 214} 215 216window.addEventListener('touchstart', (event) => { 217 touchStartY = event.touches[0].clientY; 218}); 219 220window.addEventListener('touchmove', (event) => { 221 if (isHorizontalProductFooterControlled()) return; 222 if (!canProcessTouch() || isScrollEventFromScrollableElement(event)) return; 223 224 const touchY = event.touches[0].clientY; 225 const deltaY = touchStartY - touchY; 226 227 if (footerShown && deltaY < 0) { 228 hideFooter(); 229 } else if (!footerShown && isScrolledToBottom()) { 230 if (deltaY > 0) { 231 downScrollAttempts++; 232 if (downScrollAttempts >= 3) { 233 showFooter(); 234 } 235 } 236 } else { 237 downScrollAttempts = 0; 238 } 239 240 touchStartY = touchY; 241}); 242 243// Scroll event handler 244let scrollTimeout; 245 246window.addEventListener('scroll', (event) => { 247 if (isHorizontalProductFooterControlled()) return; 248 if (!canProcessScroll() || isScrollEventFromScrollableElement(event)) return; 249 250 clearTimeout(scrollTimeout); 251 scrollTimeout = setTimeout(() => { 252 const st = window.pageYOffset || document.documentElement.scrollTop; 253 254 if (footerShown && st < lastScrollTop) { 255 hideFooter(); 256 } 257 258 if (!isScrolledToBottom()) { 259 downScrollAttempts = 0; 260 } 261 262 lastScrollTop = st <= 0 ? 0 : st; 263 }, 50); 264}); 265 266 267// Footer toggle button 268const footerToggle = document.getElementById('footerToggle'); 269 270if (footerToggle) { 271 footerToggle.addEventListener('click', () => { 272 if (footerShown) { 273 hideFooter({ 274 source: 'footer-toggle', 275 input: 'button' 276 }); 277 } else { 278 showFooter({ 279 source: 'footer-toggle', 280 input: 'button' 281 }); 282 } 283 }); 284} 285 286// Add event listener to the new button with id "show-footer-btn" that triggers the footer to appear 287document.addEventListener('DOMContentLoaded', () => { 288 const footerButton = document.getElementById('show-footer-btn'); 289 if (footerButton) { 290 footerButton.addEventListener('click', (event) => { 291 event.preventDefault(); // Prevent default link behavior 292 293 // Show the footer 294 if (!footerShown) { 295 showFooter(); 296 } 297 }); 298 } else { 299 // console.error('Element with ID "show-footer-btn" not found.'); 300 } 301}); 302 303 304 305
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.