Make search page keyboard-navigable

Still experimental
This commit is contained in:
Lim Chee Aun 2024-11-01 17:31:06 +08:00
parent f0773dc6f3
commit 7d922550d0

View file

@ -24,6 +24,12 @@ const SHORT_LIMIT = 5;
const LIMIT = 40; const LIMIT = 40;
const emptySearchParams = new URLSearchParams(); const emptySearchParams = new URLSearchParams();
const scrollIntoViewOptions = {
block: 'nearest',
inline: 'center',
behavior: 'smooth',
};
function Search({ columnMode, ...props }) { function Search({ columnMode, ...props }) {
const params = columnMode ? {} : useParams(); const params = columnMode ? {} : useParams();
const { masto, instance, authenticated } = api({ const { masto, instance, authenticated } = api({
@ -186,10 +192,79 @@ function Search({ columnMode, ...props }) {
}, },
); );
const itemsSelector = '.timeline > li > a, .hashtag-list > li > a';
const jRef = useHotkeys('j', () => {
const activeItem = document.activeElement.closest(itemsSelector);
const activeItemRect = activeItem?.getBoundingClientRect();
const allItems = Array.from(
scrollableRef.current.querySelectorAll(itemsSelector),
);
if (
activeItem &&
activeItemRect.top < scrollableRef.current.clientHeight &&
activeItemRect.bottom > 0
) {
const activeItemIndex = allItems.indexOf(activeItem);
let nextItem = allItems[activeItemIndex + 1];
if (nextItem) {
nextItem.focus();
nextItem.scrollIntoView(scrollIntoViewOptions);
}
} else {
const topmostItem = allItems.find((item) => {
const itemRect = item.getBoundingClientRect();
return itemRect.top >= 44 && itemRect.left >= 0;
});
if (topmostItem) {
topmostItem.focus();
topmostItem.scrollIntoView(scrollIntoViewOptions);
}
}
});
const kRef = useHotkeys('k', () => {
// focus on previous status after active item
const activeItem = document.activeElement.closest(itemsSelector);
const activeItemRect = activeItem?.getBoundingClientRect();
const allItems = Array.from(
scrollableRef.current.querySelectorAll(itemsSelector),
);
if (
activeItem &&
activeItemRect.top < scrollableRef.current.clientHeight &&
activeItemRect.bottom > 0
) {
const activeItemIndex = allItems.indexOf(activeItem);
let prevItem = allItems[activeItemIndex - 1];
if (prevItem) {
prevItem.focus();
prevItem.scrollIntoView(scrollIntoViewOptions);
}
} else {
const topmostItem = allItems.find((item) => {
const itemRect = item.getBoundingClientRect();
return itemRect.top >= 44 && itemRect.left >= 0;
});
if (topmostItem) {
topmostItem.focus();
topmostItem.scrollIntoView(scrollIntoViewOptions);
}
}
});
const [filterBarParent] = useAutoAnimate(); const [filterBarParent] = useAutoAnimate();
return ( return (
<div id="search-page" class="deck-container" ref={scrollableRef}> <div
id="search-page"
class="deck-container"
tabIndex="-1"
ref={(node) => {
scrollableRef.current = node;
jRef(node);
kRef(node);
}}
>
<div class="timeline-deck deck"> <div class="timeline-deck deck">
<header class={uiState === 'loading' ? 'loading' : ''}> <header class={uiState === 'loading' ? 'loading' : ''}>
<div class="header-grid"> <div class="header-grid">