Skip to content

Commit 6cf967b

Browse files
authored
Merge branch 'main' into 2887-pf-text-input-autocomplete
2 parents 3338f8b + 57a5c57 commit 6cf967b

File tree

1 file changed

+47
-1
lines changed

1 file changed

+47
-1
lines changed

core/pfe-core/controllers/scroll-spy-controller.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,25 @@ export interface ScrollSpyControllerOptions extends IntersectionObserverInit {
2323
* @default el => el.getAttribute('href');
2424
*/
2525
getHash?: (el: Element) => string | null;
26+
/**
27+
* Optional callback for when an intersection occurs
28+
*/
29+
onIntersection?(): void;
2630
}
2731

2832
export class ScrollSpyController implements ReactiveController {
33+
static #instances = new Set<ScrollSpyController>;
34+
35+
static {
36+
addEventListener('scroll', () => {
37+
if (Math.round(window.innerHeight + window.scrollY) >= document.body.scrollHeight) {
38+
this.#instances.forEach(ssc => {
39+
ssc.#setActive(ssc.#linkChildren.at(-1));
40+
});
41+
}
42+
}, { passive: true });
43+
}
44+
2945
#tagNames: string[];
3046
#activeAttribute: string;
3147

@@ -43,9 +59,11 @@ export class ScrollSpyController implements ReactiveController {
4359
#root: ScrollSpyControllerOptions['root'];
4460
#rootMargin?: string;
4561
#threshold: number | number[];
62+
#intersectingElements: Element[] = [];
4663

4764
#getRootNode: () => Node;
4865
#getHash: (el: Element) => string | null;
66+
#onIntersection?: () => void;
4967

5068
get #linkChildren(): Element[] {
5169
return Array.from(this.host.querySelectorAll(this.#tagNames.join(',')))
@@ -94,13 +112,22 @@ export class ScrollSpyController implements ReactiveController {
94112
this.#threshold = options.threshold ?? 0.85;
95113
this.#getRootNode = () => options.rootNode ?? host.getRootNode();
96114
this.#getHash = options?.getHash ?? ((el: Element) => el.getAttribute('href'));
115+
this.#onIntersection = options?.onIntersection;
97116
}
98117

99118
hostConnected(): void {
119+
ScrollSpyController.#instances.add(this);
100120
this.#initIo();
101121
}
102122

103-
#initIo() {
123+
hostDisconnected(): void {
124+
ScrollSpyController.#instances.delete(this);
125+
this.#io?.disconnect();
126+
}
127+
128+
#initializing = true;
129+
130+
async #initIo() {
104131
const rootNode = this.#getRootNode();
105132
if (rootNode instanceof Document || rootNode instanceof ShadowRoot) {
106133
const { rootMargin, threshold, root } = this;
@@ -151,6 +178,25 @@ export class ScrollSpyController implements ReactiveController {
151178
this.#setActive(last ?? this.#linkChildren.at(0));
152179
}
153180
this.#intersected = true;
181+
this.#intersectingElements =
182+
entries
183+
.filter(x => x.isIntersecting)
184+
.map(x => x.target);
185+
if (this.#initializing) {
186+
const ints = entries?.filter(x => x.isIntersecting) ?? [];
187+
if (this.#intersectingElements) {
188+
const [{ target = null } = {}] = ints;
189+
const { id } = target ?? {};
190+
if (id) {
191+
const link = this.#linkChildren.find(link => this.#getHash(link) === `#${id}`);
192+
if (link) {
193+
this.#setActive(link);
194+
}
195+
}
196+
}
197+
this.#initializing = false;
198+
}
199+
this.#onIntersection?.();
154200
}
155201

156202
/**

0 commit comments

Comments
 (0)