Skip to content

Commit

Permalink
Fix ToC scroll abort (#37)
Browse files Browse the repository at this point in the history
* fix scroll to active ToC li aborting scroll from within-page links

* readme document new prop scrollBehavior: 'auto' | 'smooth' = `smooth`

* fix tests from vitest broken svelte/internal module resolution

vitest-dev/vitest#2834

* clean up
  • Loading branch information
janosh committed Mar 12, 2023
1 parent 5df7767 commit e1e4066
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 27 deletions.
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,20 @@
"@typescript-eslint/eslint-plugin": "^5.54.1",
"@typescript-eslint/parser": "^5.54.1",
"@vitest/coverage-c8": "^0.29.2",
"eslint": "^8.35.0",
"eslint": "^8.36.0",
"eslint-plugin-svelte3": "^4.0.0",
"hastscript": "^7.2.0",
"jsdom": "^21.1.0",
"jsdom": "^21.1.1",
"mdsvex": "^0.10.6",
"mdsvexamples": "^0.3.3",
"prettier": "^2.8.4",
"prettier-plugin-svelte": "^2.9.0",
"rehype-autolink-headings": "^6.1.1",
"rehype-slug": "^5.1.0",
"svelte-check": "^3.1.0",
"svelte-check": "^3.1.2",
"svelte-preprocess": "^5.0.1",
"svelte-zoo": "^0.3.4",
"svelte2tsx": "^0.6.3",
"svelte2tsx": "^0.6.7",
"typescript": "^4.9.5",
"vite": "^4.1.4",
"vitest": "^0.29.2"
Expand Down
8 changes: 7 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,12 @@ Full list of props and bindable variables for this component (all of them option

Which DOM node to use as the `MutationObserver` root node. This is usually the page's `<main>` tag or `<body>` element. All headings to list in the ToC should be children of this root node. Use the closest parent node containing all headings for efficiency, especially if you have a lot of elements on the page that are on a separate branch of the DOM tree from the headings you want to list.

1. ```ts
scrollBehavior: 'auto' | 'smooth' = `smooth`
```

Whether to scroll the page smoothly or instantly when clicking on a ToC item. Set to `'auto'` to use the browser's default behavior.

1. ```ts
title: string = `On this page`
```
Expand All @@ -169,7 +175,7 @@ Full list of props and bindable variables for this component (all of them option
titleTag: string = `h2`
```

Change the HTML tag to be used for the ToC title. For example, to get `<strong>{title}</strong>`, set `titleTag='strong'`
Change the HTML tag to be used for the ToC title. For example, to get `<strong>{title}</strong>`, set `titleTag='strong'`.

1. ```ts
tocItems: HTMLLIElement[] = []
Expand Down
33 changes: 16 additions & 17 deletions src/lib/Toc.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { onMount } from 'svelte'
// /internal needed for module resolution pending fix for https://github.com/vitest-dev/vitest/issues/2834
import { onMount } from 'svelte/internal'
import { blur } from 'svelte/transition'
import { MenuIcon } from '.'
Expand All @@ -22,6 +23,7 @@
export let open: boolean = false
export let openButtonLabel: string = `Open table of contents`
export let pageBody: string | HTMLElement = `body`
export let scrollBehavior: 'auto' | 'smooth' = `smooth`
export let title: string = `On this page`
export let titleTag: string = `h2`
export let tocItems: HTMLLIElement[] = []
Expand Down Expand Up @@ -71,6 +73,7 @@
return () => mutation_observer.disconnect()
})
let scroll_id: number
function set_active_heading() {
let idx = headings.length
while (idx--) {
Expand All @@ -81,30 +84,26 @@
if (top < activeHeadingScrollOffset || idx === 0) {
activeHeading = headings[idx]
activeTocLi = tocItems[idx]
if (keepActiveTocItemInView && activeTocLi) {
// get the currently active ToC list item
// scroll the active ToC item into the middle of the ToC container
nav.scrollTo?.({ top: activeTocLi?.offsetTop - nav.offsetHeight / 2 })
}
// this annoying hackery to wait for scroll end is necessary because scrollend event only has 2%
// browser support https://stackoverflow.com/a/57867348 and Chrome doesn't support multiple
// simultaneous scrolls, smooth or otherwise (https://stackoverflow.com/a/63563437)
clearTimeout(scroll_id)
scroll_id = window.setTimeout(() => {
if (keepActiveTocItemInView && activeTocLi) {
// get the currently active ToC list item
// scroll the active ToC item into the middle of the ToC container
activeTocLi.scrollIntoView?.({ behavior: scrollBehavior, block: `center` })
}
}, 50)
return // exit while loop if updated active heading
}
}
}
function get_offset_top(element: HTMLElement | null): number {
// added in https://github.com/janosh/svelte-toc/pull/16
if (!element) return 0
return element.offsetTop + get_offset_top(element.offsetParent as HTMLElement)
}
const handler = (node: HTMLHeadingElement) => (event: MouseEvent | KeyboardEvent) => {
if (event instanceof KeyboardEvent && ![`Enter`, ` `].includes(event.key)) return
open = false
// Chrome doesn't (yet?) support multiple simultaneous smooth scrolls (https://stackoverflow.com/q/49318497)
// with node.scrollIntoView(). Use window.scrollTo() instead.
const scrollMargin = Number(getComputedStyle(node).scrollMarginTop.replace(`px`, ``))
window.scrollTo({ top: get_offset_top(node) - scrollMargin, behavior: `smooth` })
node.scrollIntoView({ behavior: scrollBehavior, block: `start` })
const id = getHeadingIds && getHeadingIds(node)
if (id) history.replaceState({}, ``, `#${id}`)
Expand Down
6 changes: 3 additions & 3 deletions src/routes/(demos)/left-border-active-li/+page.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Maybe your style is a left border on the active li rather than a background color? Here's an example how to do that:
Your prefer a left border on the active `<li>` to a full background color? Here's how to do that:

```svelte example
<script>
Expand All @@ -25,8 +25,8 @@ Maybe your style is a left border on the active li rather than a background colo
</main>
<Toc
--toc-active-border="solid teal"
--toc-active-border-width="0 0 0 3pt"
--toc-active-border="solid cornflowerblue"
--toc-active-border-width="0 0 0 2pt"
--toc-active-bg="none"
--toc-active-border-radius="0"
/>
Expand Down
3 changes: 1 addition & 2 deletions src/routes/+layout.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { redirect } from '@sveltejs/kit'
import type { LayoutLoad } from './$types'

export const prerender = true

export const load: LayoutLoad = ({ url }) => {
export const load = ({ url }) => {
if (url.pathname.endsWith(`.md`)) {
throw redirect(307, url.pathname.replace(/\.md$/, ``))
}
Expand Down

0 comments on commit e1e4066

Please sign in to comment.