Skip to content

Commit 2144577

Browse files
committed
fix(core): scroll-spy for non-contiguous regions
See RedHat-UX/red-hat-design-system#2471
1 parent c658b80 commit 2144577

File tree

2 files changed

+37
-22
lines changed

2 files changed

+37
-22
lines changed

.changeset/every-maps-itch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@patternfly/pfe-core": patch
3+
---
4+
`ScrollSpyController`: improve responsiveness of scroll spy
5+

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

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,14 @@ export class ScrollSpyController implements ReactiveController {
3737
static {
3838
if (!isServer) {
3939
addEventListener('scroll', () => {
40-
if (Math.round(window.innerHeight + window.scrollY) >= document.body.scrollHeight) {
41-
this.#instances.forEach(ssc => {
42-
ssc.#setActive(ssc.#linkChildren.at(-1));
43-
});
44-
}
40+
this.#instances.forEach(ssc => {
41+
ssc.#reconcile();
42+
});
43+
}, { passive: true });
44+
addEventListener('scrollend', () => {
45+
this.#instances.forEach(ssc => {
46+
ssc.#reconcile();
47+
});
4548
}, { passive: true });
4649
addEventListener('hashchange', () => {
4750
this.#instances.forEach(ssc => {
@@ -72,9 +75,10 @@ export class ScrollSpyController implements ReactiveController {
7275

7376
#threshold: number | number[];
7477

75-
#intersectingTargets = new Set<Element>();
78+
#intersectionEntries = new Set<IntersectionObserverEntry>();
7679

7780
#linkTargetMap = new Map<Element, Element | null>();
81+
#targetLinkMap = new Map<Element, Element | null>();
7882

7983
#getRootNode: () => Node | null;
8084

@@ -148,6 +152,24 @@ export class ScrollSpyController implements ReactiveController {
148152

149153
#initializing = true;
150154

155+
#reconcile() {
156+
const { scrollY, innerHeight } = window;
157+
let link: Element | null | undefined = null;
158+
if (scrollY === 0) {
159+
link = this.#linkChildren.at(0);
160+
} else if (Math.round(innerHeight + scrollY) >= document.body.scrollHeight) {
161+
link = this.#linkChildren.at(-1);
162+
} else {
163+
const [entry] = [...this.#intersectionEntries].sort((a, b) => {
164+
return b.boundingClientRect.y - a.boundingClientRect.y;
165+
});
166+
link = this.#targetLinkMap.get(entry.target);
167+
}
168+
if (link) {
169+
this.#setActive(link);
170+
}
171+
}
172+
151173
async #initIo() {
152174
const rootNode = this.#getRootNode();
153175
if (rootNode instanceof Document || rootNode instanceof ShadowRoot) {
@@ -160,6 +182,7 @@ export class ScrollSpyController implements ReactiveController {
160182
if (target) {
161183
this.#io?.observe(target);
162184
this.#linkTargetMap.set(link, target);
185+
this.#targetLinkMap.set(target, link);
163186
}
164187
}
165188
}
@@ -209,32 +232,19 @@ export class ScrollSpyController implements ReactiveController {
209232
this.#markPassed(link, boundingClientRect.top < intersectionRect.top);
210233
}
211234
}
212-
const link = [...this.#passedLinks];
213-
const last = link.at(-1);
214-
this.#setActive(last ?? this.#linkChildren.at(0));
215235
}
216236
this.#intersected = true;
217-
this.#intersectingTargets.clear();
237+
this.#intersectionEntries.clear();
218238
for (const entry of entries) {
219239
if (entry.isIntersecting) {
220-
this.#intersectingTargets.add(entry.target);
240+
this.#intersectionEntries.add(entry);
221241
}
222242
}
223243
if (this.#initializing) {
224-
const ints = entries?.filter(x => x.isIntersecting) ?? [];
225-
if (this.#intersectingTargets.size > 0) {
226-
const [{ target = null } = {}] = ints;
227-
const { id } = target ?? {};
228-
if (id) {
229-
const link = this.#linkChildren.find(link => this.#getHash(link) === `#${id}`);
230-
if (link) {
231-
this.#setActive(link);
232-
}
233-
}
234-
}
235244
this.#initializing = false;
236245
}
237246
this.#onIntersection?.();
247+
this.#reconcile();
238248
}
239249

240250
/**

0 commit comments

Comments
 (0)