| /** |
| * @license |
| * Copyright 2021 The Go Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style |
| * license that can be found in the LICENSE file. |
| */ |
| |
| const headerHeight = 3.5; |
| |
| /** |
| * MainLayoutController calculates dynamic height values for header elements |
| * to support variable size sticky positioned elements in the header so that |
| * banners and breadcumbs may overflow to multiple lines. |
| */ |
| export class MainLayoutController { |
| private headerObserver: IntersectionObserver; |
| private navObserver: IntersectionObserver; |
| private asideObserver: IntersectionObserver; |
| |
| constructor( |
| private mainHeader?: Element | null, |
| private mainNav?: Element | null, |
| private mainAside?: Element | null |
| ) { |
| this.headerObserver = new IntersectionObserver( |
| ([e]) => { |
| if (e.intersectionRatio < 1) { |
| for (const x of document.querySelectorAll('[class^="go-Main-header"')) { |
| x.setAttribute('data-fixed', 'true'); |
| } |
| } else { |
| for (const x of document.querySelectorAll('[class^="go-Main-header"')) { |
| x.removeAttribute('data-fixed'); |
| } |
| this.handleResize(); |
| } |
| }, |
| { threshold: 1, rootMargin: `${headerHeight * 16}px` } |
| ); |
| this.navObserver = new IntersectionObserver( |
| ([e]) => { |
| if (e.intersectionRatio < 1) { |
| this.mainNav?.classList.add('go-Main-nav--fixed'); |
| this.mainNav?.setAttribute('data-fixed', 'true'); |
| } else { |
| this.mainNav?.classList.remove('go-Main-nav--fixed'); |
| this.mainNav?.removeAttribute('data-fixed'); |
| } |
| }, |
| { threshold: 1, rootMargin: `-${headerHeight * 16 + 10}px` } |
| ); |
| this.asideObserver = new IntersectionObserver( |
| ([e]) => { |
| if (e.intersectionRatio < 1) { |
| this.mainHeader?.setAttribute('data-raised', 'true'); |
| } else { |
| this.mainHeader?.removeAttribute('data-raised'); |
| } |
| }, |
| { threshold: 1, rootMargin: `-${headerHeight * 16 + 20}px 0px 0px 0px` } |
| ); |
| this.init(); |
| } |
| |
| private init() { |
| this.handleResize(); |
| window.addEventListener('resize', this.handleResize); |
| this.mainHeader?.addEventListener('dblclick', this.handleDoubleClick); |
| const siteHeader = document.querySelector('.js-siteHeader'); |
| if (this.mainHeader?.hasChildNodes() && siteHeader) { |
| const headerSentinel = document.createElement('div'); |
| siteHeader.prepend(headerSentinel); |
| this.headerObserver.observe(headerSentinel); |
| } |
| if (this.mainNav?.hasChildNodes()) { |
| const navSentinel = document.createElement('div'); |
| this.mainNav.prepend(navSentinel); |
| this.navObserver.observe(navSentinel); |
| } |
| if (this.mainAside) { |
| const asideSentinel = document.createElement('div'); |
| this.mainAside.prepend(asideSentinel); |
| this.asideObserver.observe(asideSentinel); |
| } |
| } |
| |
| private handleDoubleClick: EventListener = e => { |
| const target = e.target; |
| if (target === this.mainHeader?.lastElementChild) { |
| window.getSelection()?.removeAllRanges(); |
| window.scrollTo({ top: 0, behavior: 'smooth' }); |
| } |
| }; |
| |
| private handleResize = () => { |
| const setProp = (name: string, value: string) => |
| document.documentElement.style.setProperty(name, value); |
| setProp('--js-unit-header-height', '0'); |
| setTimeout(() => { |
| const mainHeaderHeight = (this.mainHeader?.getBoundingClientRect().height ?? 0) / 16; |
| setProp('--js-unit-header-height', `${mainHeaderHeight}rem`); |
| setProp('--js-sticky-header-height', `${headerHeight}rem`); |
| setProp('--js-unit-header-top', `${(mainHeaderHeight - headerHeight) * -1}rem`); |
| }); |
| }; |
| } |
| |
| const el = <T extends HTMLElement>(selector: string) => document.querySelector<T>(selector); |
| new MainLayoutController(el('.js-mainHeader'), el('.js-mainNav'), el('.js-mainAside')); |