From 10445396dd50eb21e93625eaccd1658b2e2b186e Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Mon, 31 Mar 2025 17:22:10 +0300 Subject: [PATCH 1/3] fix(core): activate scroll spy link on connect --- .../controllers/scroll-spy-controller.ts | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/core/pfe-core/controllers/scroll-spy-controller.ts b/core/pfe-core/controllers/scroll-spy-controller.ts index 89af9d2481..efd0ad4356 100644 --- a/core/pfe-core/controllers/scroll-spy-controller.ts +++ b/core/pfe-core/controllers/scroll-spy-controller.ts @@ -43,6 +43,7 @@ export class ScrollSpyController implements ReactiveController { #root: ScrollSpyControllerOptions['root']; #rootMargin?: string; #threshold: number | number[]; + #intersectingElements: Element[] = []; #getRootNode: () => Node; #getHash: (el: Element) => string | null; @@ -100,7 +101,9 @@ export class ScrollSpyController implements ReactiveController { this.#initIo(); } - #initIo() { + #initializing = true; + + async #initIo() { const rootNode = this.#getRootNode(); if (rootNode instanceof Document || rootNode instanceof ShadowRoot) { const { rootMargin, threshold, root } = this; @@ -151,6 +154,24 @@ export class ScrollSpyController implements ReactiveController { this.#setActive(last ?? this.#linkChildren.at(0)); } this.#intersected = true; + this.#intersectingElements = + entries + .filter(x => x.isIntersecting) + .map(x => x.target); + if (this.#initializing) { + const ints = entries?.filter(x => x.isIntersecting) ?? []; + if (this.#intersectingElements) { + const [{ target = null } = {}] = ints; + const { id } = target ?? {}; + if (id) { + const link = this.#linkChildren.find(link => this.#getHash(link) === `#${id}`); + if (link) { + this.#setActive(link); + } + } + } + this.#initializing = false; + } } /** From 3f64067ebf8edb8a9110819d4f471bdbe6bfb5df Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Mon, 31 Mar 2025 17:50:24 +0300 Subject: [PATCH 2/3] feat(core): scroll-spy-controller onIntersection --- core/pfe-core/controllers/scroll-spy-controller.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/pfe-core/controllers/scroll-spy-controller.ts b/core/pfe-core/controllers/scroll-spy-controller.ts index efd0ad4356..a5473ae24b 100644 --- a/core/pfe-core/controllers/scroll-spy-controller.ts +++ b/core/pfe-core/controllers/scroll-spy-controller.ts @@ -23,6 +23,10 @@ export interface ScrollSpyControllerOptions extends IntersectionObserverInit { * @default el => el.getAttribute('href'); */ getHash?: (el: Element) => string | null; + /** + * Optional callback for when an intersection occurs + */ + onIntersection?(): void; } export class ScrollSpyController implements ReactiveController { @@ -47,6 +51,7 @@ export class ScrollSpyController implements ReactiveController { #getRootNode: () => Node; #getHash: (el: Element) => string | null; + #onIntersection?: () => void; get #linkChildren(): Element[] { return Array.from(this.host.querySelectorAll(this.#tagNames.join(','))) @@ -95,6 +100,7 @@ export class ScrollSpyController implements ReactiveController { this.#threshold = options.threshold ?? 0.85; this.#getRootNode = () => options.rootNode ?? host.getRootNode(); this.#getHash = options?.getHash ?? ((el: Element) => el.getAttribute('href')); + this.#onIntersection = options?.onIntersection; } hostConnected(): void { @@ -172,6 +178,7 @@ export class ScrollSpyController implements ReactiveController { } this.#initializing = false; } + this.#onIntersection?.(); } /** From bc2d5e9a3e17d5ec611d62cf87736f3ff73d0930 Mon Sep 17 00:00:00 2001 From: Benny Powers Date: Thu, 3 Apr 2025 16:02:42 +0300 Subject: [PATCH 3/3] fix(core): scroll-spy select last element on end scroll --- .../controllers/scroll-spy-controller.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/pfe-core/controllers/scroll-spy-controller.ts b/core/pfe-core/controllers/scroll-spy-controller.ts index a5473ae24b..24dd1e60cb 100644 --- a/core/pfe-core/controllers/scroll-spy-controller.ts +++ b/core/pfe-core/controllers/scroll-spy-controller.ts @@ -30,6 +30,18 @@ export interface ScrollSpyControllerOptions extends IntersectionObserverInit { } export class ScrollSpyController implements ReactiveController { + static #instances = new Set; + + static { + addEventListener('scroll', () => { + if (Math.round(window.innerHeight + window.scrollY) >= document.body.scrollHeight) { + this.#instances.forEach(ssc => { + ssc.#setActive(ssc.#linkChildren.at(-1)); + }); + } + }, { passive: true }); + } + #tagNames: string[]; #activeAttribute: string; @@ -104,9 +116,15 @@ export class ScrollSpyController implements ReactiveController { } hostConnected(): void { + ScrollSpyController.#instances.add(this); this.#initIo(); } + hostDisconnected(): void { + ScrollSpyController.#instances.delete(this); + this.#io?.disconnect(); + } + #initializing = true; async #initIo() {