Skip to content

Commit

Permalink
Fix scroll conflicts using isScrolling flag
Browse files Browse the repository at this point in the history
  • Loading branch information
jhildenbiddle committed Apr 11, 2024
1 parent e907db4 commit a3eb1c4
Showing 1 changed file with 78 additions and 25 deletions.
103 changes: 78 additions & 25 deletions src/core/event/index.js
Expand Up @@ -10,6 +10,7 @@ import * as dom from '../util/dom.js';
export function Events(Base) {
return class Events extends Base {
#intersectionObserver;
#isScrolling;
#title = dom.$.title;

// Initialization
Expand Down Expand Up @@ -75,6 +76,10 @@ export function Events(Base) {
this.#intersectionObserver?.disconnect();
this.#intersectionObserver = new IntersectionObserver(
entries => {
if (this.#isScrolling) {
return;
}

for (const entry of entries) {
const op = entry.isIntersecting ? 'add' : 'delete';

Expand All @@ -99,8 +104,13 @@ export function Events(Base) {
const href = this.router.toURL(this.router.getCurrentPath(), {
id,
});
const newSidebarActiveElm = this.#markSidebarActiveElm(href);

this.#markSidebarActiveElm(href);
newSidebarActiveElm?.scrollIntoView({
behavior: 'instant',
block: 'nearest',
inline: 'nearest',
});
}
},
{
Expand Down Expand Up @@ -319,10 +329,13 @@ export function Events(Base) {
`.markdown-section :where(h1, h2, h3, h4, h5)[id="${query.id}"]`
);

headingElm.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
if (headingElm) {
this.#watchNextScroll();
headingElm.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
}
}
// User click/tap
else if (source === 'navigate') {
Expand All @@ -341,6 +354,32 @@ export function Events(Base) {

// Functions
// =========================================================================
/**
* Set focus on the main content area: current route ID, first heading, or
* the main content container
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
* @param {Object} options HTMLElement focus() method options
* @void
*/
#focusContent(options = {}) {
const settings = {
preventScroll: true,
...options,
};
const { query } = this.route;
const focusEl = query.id
? // Heading ID
dom.find(`#${query.id}`)
: // First heading
dom.find('#main :where(h1, h2, h3, h4, h5, h6)') ||
// Content container
dom.find('#main');

// Move focus to content area
focusEl?.focus(settings);
}

/**
* Marks the active sidebar item
*
Expand Down Expand Up @@ -390,29 +429,43 @@ export function Events(Base) {
}

/**
* Set focus on the main content area: current route ID, first heading, or
* the main content container
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/focus
* @param {Object} options HTMLElement focus() method options
* Monitor next scroll start/end and set #isScrolling to true/false
* accordingly. Listeners are removed after the start/end events are fired.
* @void
*/
#focusContent(options = {}) {
const settings = {
preventScroll: true,
...options,
};
const { query } = this.route;
const focusEl = query.id
? // Heading ID
dom.find(`#${query.id}`)
: // First heading
dom.find('#main :where(h1, h2, h3, h4, h5, h6)') ||
// Content container
dom.find('#main');
#watchNextScroll() {
// Scroll start
document.addEventListener(
'scroll',
() => {
this.#isScrolling = true;

// Scroll end
if ('onscrollend' in window) {
document.addEventListener(
'scrollend',
() => (this.#isScrolling = false),
{ once: true }
);
}
// Browsers w/o native scrollend event support (Safari)
else {
const callback = () => {
clearTimeout(scrollTimer);

// Move focus to content area
focusEl?.focus(settings);
scrollTimer = setTimeout(() => {
document.removeEventListener('scroll', callback);
this.#isScrolling = false;
}, 100);
};

let scrollTimer;

document.addEventListener('scroll', callback, false);
}
},
{ once: true }
);
}
};
}

0 comments on commit a3eb1c4

Please sign in to comment.