Skip to content

Commit 197c2b5

Browse files
committed
#3484 homepage: improve scrolling
Signed-off-by: Patrizio Bekerle <patrizio@bekerle.com>
1 parent 9cc629b commit 197c2b5

File tree

1 file changed

+173
-1
lines changed

1 file changed

+173
-1
lines changed

docs/homepage/custom.js

Lines changed: 173 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,150 @@
217217
}
218218
}
219219

220+
function getSuggestionRows() {
221+
return document.querySelectorAll("li > button");
222+
}
223+
224+
function getSuggestionLists() {
225+
const rows = getSuggestionRows();
226+
const lists = new Set();
227+
228+
for (const row of rows) {
229+
const list = row.closest("ul");
230+
if (list) lists.add(list);
231+
}
232+
233+
return [...lists];
234+
}
235+
236+
function ensureSuggestionListScrollable() {
237+
const lists = getSuggestionLists();
238+
239+
for (const list of lists) {
240+
list.style.maxHeight = "min(60vh, 28rem)";
241+
list.style.overflowY = "auto";
242+
list.style.overflowX = "hidden";
243+
list.style.overscrollBehavior = "contain";
244+
list.style.WebkitOverflowScrolling = "touch";
245+
246+
let parent = list.parentElement;
247+
let depth = 0;
248+
while (parent && depth < 3) {
249+
if (parent.scrollHeight > parent.clientHeight) {
250+
parent.style.maxHeight = "min(60vh, 28rem)";
251+
parent.style.overflowY = "auto";
252+
parent.style.overflowX = "hidden";
253+
parent.style.overscrollBehavior = "contain";
254+
parent.style.WebkitOverflowScrolling = "touch";
255+
}
256+
parent = parent.parentElement;
257+
depth += 1;
258+
}
259+
}
260+
}
261+
262+
function getScrollableContainer(list) {
263+
if (!list) return null;
264+
if (list.scrollHeight > list.clientHeight) return list;
265+
266+
let parent = list.parentElement;
267+
let depth = 0;
268+
while (parent && depth < 5) {
269+
if (parent.scrollHeight > parent.clientHeight) return parent;
270+
parent = parent.parentElement;
271+
depth += 1;
272+
}
273+
274+
return list;
275+
}
276+
277+
function keepActiveSuggestionVisible() {
278+
const lists = getSuggestionLists();
279+
280+
for (const list of lists) {
281+
const scroller = getScrollableContainer(list);
282+
if (!scroller) continue;
283+
284+
const active = list.querySelector(
285+
[
286+
'button[aria-selected="true"]',
287+
'li[aria-selected="true"] > button',
288+
'button[aria-current="true"]',
289+
'button[data-headlessui-state~="active"]',
290+
'li[data-headlessui-state~="active"] > button',
291+
'button[tabindex="0"]',
292+
].join(", "),
293+
);
294+
if (!active) continue;
295+
296+
const activeRect = active.getBoundingClientRect();
297+
const scrollerRect = scroller.getBoundingClientRect();
298+
299+
if (activeRect.top < scrollerRect.top) {
300+
scroller.scrollTop -= scrollerRect.top - activeRect.top;
301+
} else if (activeRect.bottom > scrollerRect.bottom) {
302+
scroller.scrollTop += activeRect.bottom - scrollerRect.bottom;
303+
}
304+
}
305+
}
306+
307+
function getPrimarySuggestionList() {
308+
const lists = getSuggestionLists();
309+
let best = null;
310+
let bestCount = 0;
311+
312+
for (const list of lists) {
313+
const count = list.querySelectorAll("li > button").length;
314+
if (count > bestCount) {
315+
best = list;
316+
bestCount = count;
317+
}
318+
}
319+
320+
return best;
321+
}
322+
323+
function scrollSuggestionListByKeyboard(event) {
324+
const list = getPrimarySuggestionList();
325+
if (!list) return;
326+
const scroller = getScrollableContainer(list);
327+
if (!scroller) return;
328+
329+
const firstItem = list.querySelector("li > button");
330+
const itemHeight = firstItem
331+
? firstItem.getBoundingClientRect().height
332+
: 36;
333+
334+
if (event.key === "ArrowDown") {
335+
scroller.scrollTop += itemHeight;
336+
return;
337+
}
338+
339+
if (event.key === "ArrowUp") {
340+
scroller.scrollTop -= itemHeight;
341+
return;
342+
}
343+
344+
if (event.key === "PageDown") {
345+
scroller.scrollTop += scroller.clientHeight * 0.9;
346+
return;
347+
}
348+
349+
if (event.key === "PageUp") {
350+
scroller.scrollTop -= scroller.clientHeight * 0.9;
351+
return;
352+
}
353+
354+
if (event.key === "Home") {
355+
scroller.scrollTop = 0;
356+
return;
357+
}
358+
359+
if (event.key === "End") {
360+
scroller.scrollTop = scroller.scrollHeight;
361+
}
362+
}
363+
220364
window.open = function (url, target, features) {
221365
const raw = typeof url === "string" ? url : String(url ?? "");
222366
const direct = decodeIfSearchEngineRedirect(raw);
@@ -288,10 +432,38 @@
288432
}
289433
};
290434

291-
const observer = new MutationObserver(markQonSuggestionRows);
435+
const observer = new MutationObserver(() => {
436+
markQonSuggestionRows();
437+
ensureSuggestionListScrollable();
438+
keepActiveSuggestionVisible();
439+
});
292440
observer.observe(document.documentElement, {
293441
childList: true,
294442
subtree: true,
295443
});
296444
markQonSuggestionRows();
445+
ensureSuggestionListScrollable();
446+
447+
document.addEventListener(
448+
"keydown",
449+
(event) => {
450+
const key = event.key;
451+
if (
452+
key === "ArrowDown" ||
453+
key === "ArrowUp" ||
454+
key === "PageDown" ||
455+
key === "PageUp" ||
456+
key === "Home" ||
457+
key === "End"
458+
) {
459+
requestAnimationFrame(() => {
460+
keepActiveSuggestionVisible();
461+
scrollSuggestionListByKeyboard(event);
462+
});
463+
}
464+
},
465+
true,
466+
);
467+
468+
keepActiveSuggestionVisible();
297469
})();

0 commit comments

Comments
 (0)