mirror of
https://github.com/squidfunk/mkdocs-material.git
synced 2026-07-31 09:48:53 -04:00
Refactored header autohiding and split overrides
This commit is contained in:
@@ -21,23 +21,34 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
NEVER,
|
||||
Observable,
|
||||
Subject,
|
||||
animationFrameScheduler,
|
||||
combineLatest,
|
||||
defer,
|
||||
of
|
||||
} from "rxjs"
|
||||
import {
|
||||
bufferCount,
|
||||
combineLatestWith,
|
||||
distinctUntilChanged,
|
||||
distinctUntilKeyChanged,
|
||||
filter,
|
||||
map,
|
||||
observeOn,
|
||||
shareReplay
|
||||
shareReplay,
|
||||
startWith,
|
||||
switchMap
|
||||
} from "rxjs/operators"
|
||||
|
||||
import { feature } from "~/_"
|
||||
import { resetHeaderState, setHeaderState } from "~/actions"
|
||||
import { Viewport, watchElementSize } from "~/browser"
|
||||
import {
|
||||
Viewport,
|
||||
watchElementSize,
|
||||
watchToggle
|
||||
} from "~/browser"
|
||||
|
||||
import { Component } from "../../_"
|
||||
import { Main } from "../../main"
|
||||
@@ -50,14 +61,22 @@ import { Main } from "../../main"
|
||||
* Header
|
||||
*/
|
||||
export interface Header {
|
||||
sticky: boolean /* Header stickyness */
|
||||
height: number /* Header visible height */
|
||||
sticky: boolean /* Header stickyness */
|
||||
hidden: boolean /* User scrolled past threshold */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Watch options
|
||||
*/
|
||||
interface WatchOptions {
|
||||
viewport$: Observable<Viewport> /* Viewport observable */
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount options
|
||||
*/
|
||||
@@ -67,6 +86,52 @@ interface MountOptions {
|
||||
main$: Observable<Main> /* Main area observable */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Helper functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Compute whether the header is hidden
|
||||
*
|
||||
* If the user scrolls past a certain threshold, the header can be hidden when
|
||||
* scrolling down, and shown when scrolling up.
|
||||
*
|
||||
* @param options - Options
|
||||
*
|
||||
* @returns Toggle observable
|
||||
*/
|
||||
function isHidden({ viewport$ }: WatchOptions): Observable<boolean> {
|
||||
if (!feature("header.autohide"))
|
||||
return of(false)
|
||||
|
||||
/* Compute direction and turning point */
|
||||
const direction$ = viewport$
|
||||
.pipe(
|
||||
map(({ offset: { y } }) => y),
|
||||
bufferCount(2, 1),
|
||||
map(([a, b]) => [a < b, b] as const),
|
||||
distinctUntilKeyChanged(0)
|
||||
)
|
||||
|
||||
/* Compute whether header should be hidden */
|
||||
const hidden$ = combineLatest([viewport$, direction$])
|
||||
.pipe(
|
||||
filter(([{ offset }, [, y]]) => Math.abs(y - offset.y) > 100),
|
||||
map(([, [direction]]) => direction),
|
||||
distinctUntilChanged(),
|
||||
)
|
||||
|
||||
/* Compute threshold for autohiding */
|
||||
const search$ = watchToggle("search")
|
||||
return combineLatest([viewport$, search$])
|
||||
.pipe(
|
||||
map(([{ offset }, search]) => offset.y > 400 && !search),
|
||||
distinctUntilChanged(),
|
||||
switchMap(active => active ? hidden$ : NEVER),
|
||||
startWith(false)
|
||||
)
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Functions
|
||||
* ------------------------------------------------------------------------- */
|
||||
@@ -75,11 +140,12 @@ interface MountOptions {
|
||||
* Watch header
|
||||
*
|
||||
* @param el - Header element
|
||||
* @param options - Options
|
||||
*
|
||||
* @returns Header observable
|
||||
*/
|
||||
export function watchHeader(
|
||||
el: HTMLElement
|
||||
el: HTMLElement, options: WatchOptions
|
||||
): Observable<Header> {
|
||||
return defer(() => {
|
||||
const styles = getComputedStyle(el)
|
||||
@@ -89,14 +155,16 @@ export function watchHeader(
|
||||
)
|
||||
})
|
||||
.pipe(
|
||||
combineLatestWith(watchElementSize(el)),
|
||||
map(([sticky, { height }]) => ({
|
||||
combineLatestWith(watchElementSize(el), isHidden(options)),
|
||||
map(([sticky, { height }, hidden]) => ({
|
||||
height: sticky ? height : 0,
|
||||
sticky,
|
||||
height: sticky ? height : 0
|
||||
hidden
|
||||
})),
|
||||
distinctUntilChanged((a, b) => (
|
||||
a.sticky === b.sticky &&
|
||||
a.height === b.height
|
||||
a.height === b.height &&
|
||||
a.hidden === b.hidden
|
||||
)),
|
||||
shareReplay(1)
|
||||
)
|
||||
@@ -122,11 +190,12 @@ export function mountHeader(
|
||||
internal$
|
||||
.pipe(
|
||||
distinctUntilKeyChanged("active"),
|
||||
combineLatestWith(header$),
|
||||
observeOn(animationFrameScheduler)
|
||||
)
|
||||
.subscribe(({ active }) => {
|
||||
.subscribe(([{ active }, { hidden }]) => {
|
||||
if (active)
|
||||
setHeaderState(el, "shadow")
|
||||
setHeaderState(el, hidden ? "hidden" : "shadow")
|
||||
else
|
||||
resetHeaderState(el)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user