Send content ref to get page changes for free

This commit is contained in:
Nicolas Mowen 2025-10-02 07:14:42 -06:00
parent 9096f113c1
commit 7614e6435d
6 changed files with 60 additions and 155 deletions

View File

@ -1,4 +1,4 @@
import { useCallback, useEffect } from "react"; import { MutableRefObject, useCallback, useEffect, useMemo } from "react";
export type KeyModifiers = { export type KeyModifiers = {
down: boolean; down: boolean;
@ -9,8 +9,17 @@ export type KeyModifiers = {
export default function useKeyboardListener( export default function useKeyboardListener(
keys: string[], keys: string[],
listener: (key: string | null, modifiers: KeyModifiers) => boolean, listener?: (key: string | null, modifiers: KeyModifiers) => boolean,
contentRef?: MutableRefObject<HTMLDivElement | null>,
) { ) {
const pageKeys = useMemo(
() =>
contentRef != undefined
? ["ArrowDown", "ArrowUp", "PageDown", "PageUp"]
: [],
[contentRef],
);
const keyDownListener = useCallback( const keyDownListener = useCallback(
(e: KeyboardEvent) => { (e: KeyboardEvent) => {
// @ts-expect-error we know this field exists // @ts-expect-error we know this field exists
@ -25,14 +34,44 @@ export default function useKeyboardListener(
shift: e.shiftKey, shift: e.shiftKey,
}; };
if (keys.includes(e.key)) { if (contentRef && pageKeys.includes(e.key)) {
switch (e.key) {
case "ArrowDown":
contentRef.current?.scrollBy({
top: 100,
behavior: "smooth",
});
break;
case "ArrowUp":
contentRef.current?.scrollBy({
top: -100,
behavior: "smooth",
});
break;
case "PageDown":
contentRef.current?.scrollBy({
top: contentRef.current.clientHeight / 2,
behavior: "smooth",
});
break;
case "PageUp":
contentRef.current?.scrollBy({
top: -contentRef.current.clientHeight / 2,
behavior: "smooth",
});
break;
}
} else if (keys.includes(e.key) && listener) {
const preventDefault = listener(e.key, modifiers); const preventDefault = listener(e.key, modifiers);
if (preventDefault) e.preventDefault(); if (preventDefault) e.preventDefault();
} else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") { } else if (
listener &&
(e.key === "Shift" || e.key === "Control" || e.key === "Meta")
) {
listener(null, modifiers); listener(null, modifiers);
} }
}, },
[keys, listener], [keys, pageKeys, listener, contentRef],
); );
const keyUpListener = useCallback( const keyUpListener = useCallback(
@ -48,10 +87,13 @@ export default function useKeyboardListener(
shift: false, shift: false,
}; };
if (keys.includes(e.key)) { if (listener && keys.includes(e.key)) {
e.preventDefault(); const preventDefault = listener(e.key, modifiers);
listener(e.key, modifiers); if (preventDefault) e.preventDefault();
} else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") { } else if (
listener &&
(e.key === "Shift" || e.key === "Control" || e.key === "Meta")
) {
listener(null, modifiers); listener(null, modifiers);
} }
}, },

View File

@ -113,43 +113,7 @@ function Exports() {
// Keyboard Listener // Keyboard Listener
const contentRef = useRef<HTMLDivElement | null>(null); const contentRef = useRef<HTMLDivElement | null>(null);
useKeyboardListener( useKeyboardListener([], undefined, contentRef);
["ArrowDown", "ArrowUp", "PageDown", "PageUp"],
(key, modifiers) => {
if (!modifiers.down) {
return true;
}
switch (key) {
case "ArrowDown":
contentRef.current?.scrollBy({
top: 100,
behavior: "smooth",
});
break;
case "ArrowUp":
contentRef.current?.scrollBy({
top: -100,
behavior: "smooth",
});
break;
case "PageDown":
contentRef.current?.scrollBy({
top: contentRef.current.clientHeight / 2,
behavior: "smooth",
});
break;
case "PageUp":
contentRef.current?.scrollBy({
top: -contentRef.current.clientHeight / 2,
behavior: "smooth",
});
break;
}
return true;
},
);
return ( return (
<div className="flex size-full flex-col gap-2 overflow-hidden px-1 pt-2 md:p-2"> <div className="flex size-full flex-col gap-2 overflow-hidden px-1 pt-2 md:p-2">

View File

@ -270,7 +270,7 @@ export default function FaceLibrary() {
const contentRef = useRef<HTMLDivElement | null>(null); const contentRef = useRef<HTMLDivElement | null>(null);
useKeyboardListener( useKeyboardListener(
["a", "Escape", "ArrowDown", "ArrowUp", "PageDown", "PageUp"], ["a", "Escape"],
(key, modifiers) => { (key, modifiers) => {
if (!modifiers.down) { if (!modifiers.down) {
return true; return true;
@ -293,34 +293,11 @@ export default function FaceLibrary() {
case "Escape": case "Escape":
setSelectedFaces([]); setSelectedFaces([]);
return true; return true;
case "ArrowDown":
contentRef.current?.scrollBy({
top: 100,
behavior: "smooth",
});
return true;
case "ArrowUp":
contentRef.current?.scrollBy({
top: -100,
behavior: "smooth",
});
return true;
case "PageDown":
contentRef.current?.scrollBy({
top: contentRef.current.clientHeight / 2,
behavior: "smooth",
});
break;
case "PageUp":
contentRef.current?.scrollBy({
top: -contentRef.current.clientHeight / 2,
behavior: "smooth",
});
return true;
} }
return false; return false;
}, },
contentRef,
); );
useEffect(() => { useEffect(() => {

View File

@ -235,7 +235,7 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
const contentRef = useRef<HTMLDivElement | null>(null); const contentRef = useRef<HTMLDivElement | null>(null);
useKeyboardListener( useKeyboardListener(
["a", "Escape", "ArrowDown", "ArrowUp", "PageDown", "PageUp"], ["a", "Escape"],
(key, modifiers) => { (key, modifiers) => {
if (!modifiers.down) { if (!modifiers.down) {
return true; return true;
@ -259,34 +259,11 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
case "Escape": case "Escape":
setSelectedImages([]); setSelectedImages([]);
return true; return true;
case "ArrowDown":
contentRef.current?.scrollBy({
top: 100,
behavior: "smooth",
});
return true;
case "ArrowUp":
contentRef.current?.scrollBy({
top: -100,
behavior: "smooth",
});
return true;
case "PageDown":
contentRef.current?.scrollBy({
top: contentRef.current.clientHeight / 2,
behavior: "smooth",
});
return true;
case "PageUp":
contentRef.current?.scrollBy({
top: -contentRef.current.clientHeight / 2,
behavior: "smooth",
});
return true;
} }
return false; return false;
}, },
contentRef,
); );
useEffect(() => { useEffect(() => {

View File

@ -651,7 +651,7 @@ function DetectionReview({
// keyboard // keyboard
useKeyboardListener( useKeyboardListener(
["a", "r", "Escape", "ArrowDown", "ArrowUp", "PageDown", "PageUp"], ["a", "r", "Escape"],
(key, modifiers) => { (key, modifiers) => {
if (!modifiers.down) { if (!modifiers.down) {
return true; return true;
@ -679,34 +679,11 @@ function DetectionReview({
case "Escape": case "Escape":
setSelectedReviews([]); setSelectedReviews([]);
return true; return true;
case "ArrowDown":
contentRef.current?.scrollBy({
top: 100,
behavior: "smooth",
});
return true;
case "ArrowUp":
contentRef.current?.scrollBy({
top: -100,
behavior: "smooth",
});
return true;
case "PageDown":
contentRef.current?.scrollBy({
top: contentRef.current.clientHeight / 2,
behavior: "smooth",
});
return true;
case "PageUp":
contentRef.current?.scrollBy({
top: -contentRef.current.clientHeight / 2,
behavior: "smooth",
});
return true;
} }
return false; return false;
}, },
contentRef,
); );
return ( return (

View File

@ -359,30 +359,6 @@ export default function SearchView({
setSearchDetail(uniqueResults[newIndex]); setSearchDetail(uniqueResults[newIndex]);
} }
return true; return true;
case "ArrowDown":
contentRef.current?.scrollBy({
top: 100,
behavior: "smooth",
});
return true;
case "ArrowUp":
contentRef.current?.scrollBy({
top: -100,
behavior: "smooth",
});
return true;
case "PageDown":
contentRef.current?.scrollBy({
top: contentRef.current.clientHeight / 2,
behavior: "smooth",
});
return true;
case "PageUp":
contentRef.current?.scrollBy({
top: -contentRef.current.clientHeight / 2,
behavior: "smooth",
});
return true;
} }
return false; return false;
@ -391,17 +367,9 @@ export default function SearchView({
); );
useKeyboardListener( useKeyboardListener(
[ ["a", "Escape", "ArrowLeft", "ArrowRight"],
"a",
"Escape",
"ArrowDown",
"ArrowLeft",
"ArrowRight",
"ArrowUp",
"PageDown",
"PageUp",
],
onKeyboardShortcut, onKeyboardShortcut,
contentRef,
); );
// scroll into view // scroll into view