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> </script>
{% endblock %} {% endblock %}
{% block scripts %} {% 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 %} {% for path in config.extra_javascript %}
<script src="{{ path | url }}"></script> <script src="{{ path | url }}"></script>
{% endfor %} {% endfor %}

View File

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

View File

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

View File

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