1import {createRafScheduler} from '../core/rafScheduler.js'; 2import {initBrandingAdOffset} from './brandingAdOffset.js'; 3 4const DIRECTION_THRESHOLD = 8; 5const OPEN_MENU_SELECTOR = '.mobile-nav.is-open, [data-header-submenu].is-open, [data-mega-menu-dropdown].is-open'; 6 7const getScrollPosition = () => { 8 const maxScroll = Math.max(0, document.documentElement.scrollHeight - window.innerHeight); 9 return Math.min(maxScroll, Math.max(0, window.scrollY)); 10}; 11 12export const initHeaderScrollReveal = () => { 13 const header = document.querySelector('[data-js-module~="header-scroll-reveal"]'); 14 const container = header?.closest('.site-header-container'); 15 if (!(header instanceof HTMLElement) || !(container instanceof HTMLElement) 16 || container.classList.contains('site-header-container--scroll-reveal')) { 17 return; 18 } 19 20 let previousY = getScrollPosition(); 21 let headerHeight = header.offsetHeight; 22 let direction = 0; 23 let travel = 0; 24 let isHidden = false; 25 const syncHeaderOffset = initBrandingAdOffset(); 26 27 const isInteractionActive = () => header.contains(document.activeElement) 28 || header.querySelector(OPEN_MENU_SELECTOR) !== null 29 || document.body.classList.contains('search-open'); 30 31 const setHidden = (hidden) => { 32 if (hidden === isHidden) { 33 return; 34 } 35 36 isHidden = hidden; 37 container.classList.toggle('is-scroll-hidden', hidden); 38 syncHeaderOffset?.(); 39 }; 40 41 const resetDirection = () => { 42 previousY = getScrollPosition(); 43 direction = 0; 44 travel = 0; 45 }; 46 47 const updateFromScroll = () => { 48 const currentY = getScrollPosition(); 49 const delta = currentY - previousY; 50 previousY = currentY; 51 52 if (currentY <= headerHeight || isInteractionActive()) { 53 resetDirection(); 54 setHidden(false); 55 return; 56 } 57 58 if (delta === 0) { 59 return; 60 } 61 62 const nextDirection = Math.sign(delta); 63 travel = nextDirection === direction ? travel + Math.abs(delta) : Math.abs(delta); 64 direction = nextDirection; 65 if (travel >= DIRECTION_THRESHOLD) { 66 setHidden(direction > 0); 67 } 68 }; 69 70 const scrollScheduler = createRafScheduler(updateFromScroll); 71 72 const refreshSize = () => { 73 headerHeight = header.offsetHeight; 74 resetDirection(); 75 if (previousY <= headerHeight || isInteractionActive()) { 76 setHidden(false); 77 } 78 79 syncHeaderOffset?.(); 80 }; 81 82 const restorePosition = () => { 83 container.classList.remove('is-scroll-ready'); 84 refreshSize(); 85 setHidden(previousY > headerHeight && !isInteractionActive()); 86 // Measure the initial/restored state before enabling transitions. 87 container.getBoundingClientRect(); 88 container.classList.add('is-scroll-ready'); 89 }; 90 91 const updateInteraction = () => { 92 resetDirection(); 93 if (isInteractionActive()) { 94 setHidden(false); 95 } 96 97 syncHeaderOffset?.(); 98 }; 99 100 const interactionScheduler = createRafScheduler(updateInteraction); 101 header.addEventListener('focusin', updateInteraction); 102 header.addEventListener('focusout', interactionScheduler.schedule); 103 104 const menuObserver = new MutationObserver(interactionScheduler.schedule); 105 menuObserver.observe(header, {subtree: true, attributes: true, attributeFilter: ['class', 'aria-expanded']}); 106 const searchObserver = new MutationObserver(interactionScheduler.schedule); 107 searchObserver.observe(document.body, {attributes: true, attributeFilter: ['class']}); 108 109 if (typeof ResizeObserver !== 'undefined') { 110 const resizeObserver = new ResizeObserver(refreshSize); 111 resizeObserver.observe(header); 112 } 113 114 window.addEventListener('scroll', scrollScheduler.schedule, {passive: true}); 115 window.addEventListener('resize', refreshSize); 116 window.addEventListener('orientationchange', refreshSize); 117 window.addEventListener('pageshow', restorePosition); 118 window.addEventListener('pagehide', scrollScheduler.cancel); 119 container.classList.add('site-header-container--scroll-reveal'); 120 restorePosition(); 121};
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.