From 756e85e8e814657c42c4a6f9c299b5bef32aee22 Mon Sep 17 00:00:00 2001 From: Chris Swithinbank Date: Sat, 7 Sep 2024 00:18:54 +0200 Subject: Refactor sidebar persistence logic for better slow device performance (#2242) Co-authored-by: HiDeoo <494699+HiDeoo@users.noreply.github.com> --- .changeset/heavy-forks-help.md | 5 ++ packages/starlight/components/Sidebar.astro | 38 ++---------- .../starlight/components/SidebarPersistState.ts | 6 +- .../starlight/components/SidebarPersister.astro | 72 ++++++++++++++++++++++ .../starlight/components/SidebarRestorePoint.astro | 12 ++++ packages/starlight/components/SidebarSublist.astro | 2 + 6 files changed, 98 insertions(+), 37 deletions(-) create mode 100644 .changeset/heavy-forks-help.md create mode 100644 packages/starlight/components/SidebarPersister.astro create mode 100644 packages/starlight/components/SidebarRestorePoint.astro diff --git a/.changeset/heavy-forks-help.md b/.changeset/heavy-forks-help.md new file mode 100644 index 00000000..17f017ff --- /dev/null +++ b/.changeset/heavy-forks-help.md @@ -0,0 +1,5 @@ +--- +"@astrojs/starlight": patch +--- + +Refactors the logic for persisting and restoring sidebar state across navigations for better performance on slow or busy devices diff --git a/packages/starlight/components/Sidebar.astro b/packages/starlight/components/Sidebar.astro index c47d0315..1dd4c047 100644 --- a/packages/starlight/components/Sidebar.astro +++ b/packages/starlight/components/Sidebar.astro @@ -2,46 +2,16 @@ import type { Props } from '../props'; import MobileMenuFooter from 'virtual:starlight/components/MobileMenuFooter'; -import { getSidebarHash } from '../utils/navigation'; +import SidebarPersister from './SidebarPersister.astro'; import SidebarSublist from './SidebarSublist.astro'; const { sidebar } = Astro.props; -const hash = getSidebarHash(sidebar); --- - + - + +
- -{ - /* - Inline script to restore sidebar state as soon as possible. - - On smaller viewports, restoring state is skipped as the sidebar is collapsed inside a menu. - - The state is parsed from session storage and restored. - - This is a progressive enhancement, so any errors are swallowed silently. - */ -} - - diff --git a/packages/starlight/components/SidebarPersistState.ts b/packages/starlight/components/SidebarPersistState.ts index 029dd95c..4c4513ef 100644 --- a/packages/starlight/components/SidebarPersistState.ts +++ b/packages/starlight/components/SidebarPersistState.ts @@ -1,7 +1,6 @@ // Collect required elements from the DOM. const scroller = document.getElementById('starlight__sidebar'); const target = scroller?.querySelector('sl-sidebar-state-persist'); -const details = [...(target?.querySelectorAll('details') || [])]; /** Starlight uses this key to store sidebar state in `sessionStorage`. */ const storageKey = 'sl-sidebar-state'; @@ -58,8 +57,9 @@ target?.addEventListener('click', (event) => { // This excludes clicks outside of the ``, which don’t trigger toggles. const toggledDetails = event.target.closest('summary')?.closest('details'); if (!toggledDetails) return; - const index = details.indexOf(toggledDetails); - if (index === -1) return; + const restoreElement = toggledDetails.querySelector('sl-sidebar-restore'); + const index = parseInt(restoreElement?.dataset.index || ''); + if (isNaN(index)) return; setToggleState(!toggledDetails.open, index); }); diff --git a/packages/starlight/components/SidebarPersister.astro b/packages/starlight/components/SidebarPersister.astro new file mode 100644 index 00000000..ae485c97 --- /dev/null +++ b/packages/starlight/components/SidebarPersister.astro @@ -0,0 +1,72 @@ +--- +/* + This component is designed to wrap the tree of `` components in the sidebar. + + It does the following: + - Wraps the tree in an `` custom element + - Before the tree renders, adds an inline script which loads state and defines + the behaviour for the `` custom element. + - After the tree renders, adds an inline script which restores the sidebar scroll state. + + Notes: + - On smaller viewports, restoring state is skipped as the sidebar is collapsed inside a menu. + - The state is parsed from session storage and restored. + - This is a progressive enhancement, so any errors are swallowed silently. +*/ + +import type { Props } from '../props'; +import { getSidebarHash } from '../utils/navigation'; + +const hash = getSidebarHash(Astro.props.sidebar); + +declare global { + interface Window { + /** Restored scroll position. Briefly stored on the `window` global to pass between inline scripts. */ + _starlightScrollRestore?: number; + } +} +--- + + + + + + + + + + diff --git a/packages/starlight/components/SidebarRestorePoint.astro b/packages/starlight/components/SidebarRestorePoint.astro new file mode 100644 index 00000000..d3d96939 --- /dev/null +++ b/packages/starlight/components/SidebarRestorePoint.astro @@ -0,0 +1,12 @@ +--- +/** Unique symbol for storing a running index in `locals`. */ +const currentGroupIndexSymbol = Symbol.for('starlight-sidebar-group-index'); +const locals = Astro.locals as Record; + +/** The current sidebar group’s index retrieved from `locals` if set, starting at `0`. */ +const index = locals[currentGroupIndexSymbol] || 0; +// Increment the index for the next instance. +locals[currentGroupIndexSymbol] = index + 1; +--- + + diff --git a/packages/starlight/components/SidebarSublist.astro b/packages/starlight/components/SidebarSublist.astro index 23252501..b521ba13 100644 --- a/packages/starlight/components/SidebarSublist.astro +++ b/packages/starlight/components/SidebarSublist.astro @@ -2,6 +2,7 @@ import { flattenSidebar, type SidebarEntry } from '../utils/navigation'; import Icon from '../user-components/Icon.astro'; import Badge from '../user-components/Badge.astro'; +import SidebarRestorePoint from './SidebarRestorePoint.astro'; interface Props { sublist: SidebarEntry[]; @@ -35,6 +36,7 @@ const { sublist, nested } = Astro.props;
i.isCurrent) || !entry.collapsed} > +
{entry.label} -- cgit