Fixed scroll offset when activating linked content tabs

This commit is contained in:
squidfunk 2022-09-13 16:13:41 +02:00
parent e7a8ae0113
commit ec9e2a3258
7 changed files with 72 additions and 36 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -245,7 +245,7 @@
</script>
{% endblock %}
{% block scripts %}
<script src="{{ 'assets/javascripts/bundle.48f2be22.min.js' | url }}"></script>
<script src="{{ 'assets/javascripts/bundle.d691e9de.min.js' | url }}"></script>
{% for path in config.extra_javascript %}
<script src="{{ path | url }}"></script>
{% endfor %}

View File

@ -213,7 +213,7 @@ const content$ = defer(() => merge(
/* Content */
...getComponentElements("content")
.map(el => mountContent(el, { target$, print$ })),
.map(el => mountContent(el, { viewport$, target$, print$ })),
/* Search highlighting */
...getComponentElements("content")

View File

@ -22,7 +22,7 @@
import { Observable, merge } from "rxjs"
import { getElements } from "~/browser"
import { Viewport, getElements } from "~/browser"
import { Component } from "../../_"
import { Annotation } from "../annotation"
@ -68,6 +68,7 @@ export type Content =
* Mount options
*/
interface MountOptions {
viewport$: Observable<Viewport> /* Viewport observable */
target$: Observable<HTMLElement> /* Location target observable */
print$: Observable<boolean> /* Media print observable */
}
@ -88,7 +89,7 @@ interface MountOptions {
* @returns Content component observable
*/
export function mountContent(
el: HTMLElement, { target$, print$ }: MountOptions
el: HTMLElement, { viewport$, target$, print$ }: MountOptions
): Observable<Component<Content>> {
return merge(
@ -110,6 +111,6 @@ export function mountContent(
/* Content tabs */
...getElements("[data-tabs]", el)
.map(child => mountContentTabs(child))
.map(child => mountContentTabs(child, { viewport$ }))
)
}

View File

@ -37,11 +37,13 @@ import {
subscribeOn,
takeLast,
takeUntil,
tap
tap,
withLatestFrom
} from "rxjs"
import { feature } from "~/_"
import {
Viewport,
getElement,
getElementContentOffset,
getElementContentSize,
@ -66,6 +68,17 @@ export interface ContentTabs {
active: HTMLLabelElement /* Active tab label */
}
/* ----------------------------------------------------------------------------
* Helper types
* ------------------------------------------------------------------------- */
/**
* Mount options
*/
interface MountOptions {
viewport$: Observable<Viewport> /* Viewport observable */
}
/* ----------------------------------------------------------------------------
* Functions
* ------------------------------------------------------------------------- */
@ -102,11 +115,12 @@ export function watchContentTabs(
* this functionality ourselves.
*
* @param el - Content tabs element
* @param options - Options
*
* @returns Content tabs component observable
*/
export function mountContentTabs(
el: HTMLElement
el: HTMLElement, { viewport$ }: MountOptions
): Observable<Component<ContentTabs>> {
/* Render content tab previous button for pagination */
@ -189,23 +203,44 @@ export function mountContentTabs(
/* Set up linking of content tabs, if enabled */
if (feature("content.tabs.link"))
push$.pipe(skip(1))
.subscribe(({ active }) => {
push$.pipe(
skip(1),
withLatestFrom(viewport$)
)
.subscribe(([{ active }, { offset }]) => {
const tab = active.innerText.trim()
for (const set of getElements("[data-tabs]"))
for (const input of getElements<HTMLInputElement>(
":scope > input", set
)) {
const label = getElement(`label[for="${input.id}"]`)
if (label.innerText.trim() === tab) {
input.click()
break
}
}
if (active.hasAttribute("data-md-switching")) {
active.removeAttribute("data-md-switching")
/* Persist active tabs in local storage */
const tabs = __md_get<string[]>("__tabs") || []
__md_set("__tabs", [...new Set([tab, ...tabs])])
/* Determine viewport offset of active tab */
} else {
const y = el.offsetTop - offset.y
/* Passively activate other tabs */
for (const set of getElements("[data-tabs]"))
for (const input of getElements<HTMLInputElement>(
":scope > input", set
)) {
const label = getElement(`label[for="${input.id}"]`)
if (
label !== active &&
label.innerText.trim() === tab
) {
label.setAttribute("data-md-switching", "")
input.click()
break
}
}
/* Bring active tab into view */
window.scrollTo({
top: el.offsetTop - y
})
/* Persist active tabs in local storage */
const tabs = __md_get<string[]>("__tabs") || []
__md_set("__tabs", [...new Set([tab, ...tabs])])
}
})
/* Create and return component */