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 = {
down: boolean;
@ -9,8 +9,17 @@ export type KeyModifiers = {
export default function useKeyboardListener(
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(
(e: KeyboardEvent) => {
// @ts-expect-error we know this field exists
@ -25,14 +34,44 @@ export default function useKeyboardListener(
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);
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);
}
},
[keys, listener],
[keys, pageKeys, listener, contentRef],
);
const keyUpListener = useCallback(
@ -48,10 +87,13 @@ export default function useKeyboardListener(
shift: false,
};
if (keys.includes(e.key)) {
e.preventDefault();
listener(e.key, modifiers);
} else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") {
if (listener && keys.includes(e.key)) {
const preventDefault = listener(e.key, modifiers);
if (preventDefault) e.preventDefault();
} else if (
listener &&
(e.key === "Shift" || e.key === "Control" || e.key === "Meta")
) {
listener(null, modifiers);
}
},

View File

@ -113,43 +113,7 @@ function Exports() {
// Keyboard Listener
const contentRef = useRef<HTMLDivElement | null>(null);
useKeyboardListener(
["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;
},
);
useKeyboardListener([], undefined, contentRef);
return (
<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);
useKeyboardListener(
["a", "Escape", "ArrowDown", "ArrowUp", "PageDown", "PageUp"],
["a", "Escape"],
(key, modifiers) => {
if (!modifiers.down) {
return true;
@ -293,34 +293,11 @@ export default function FaceLibrary() {
case "Escape":
setSelectedFaces([]);
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;
},
contentRef,
);
useEffect(() => {

View File

@ -235,7 +235,7 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
const contentRef = useRef<HTMLDivElement | null>(null);
useKeyboardListener(
["a", "Escape", "ArrowDown", "ArrowUp", "PageDown", "PageUp"],
["a", "Escape"],
(key, modifiers) => {
if (!modifiers.down) {
return true;
@ -259,34 +259,11 @@ export default function ModelTrainingView({ model }: ModelTrainingViewProps) {
case "Escape":
setSelectedImages([]);
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;
},
contentRef,
);
useEffect(() => {

View File

@ -651,7 +651,7 @@ function DetectionReview({
// keyboard
useKeyboardListener(
["a", "r", "Escape", "ArrowDown", "ArrowUp", "PageDown", "PageUp"],
["a", "r", "Escape"],
(key, modifiers) => {
if (!modifiers.down) {
return true;
@ -679,34 +679,11 @@ function DetectionReview({
case "Escape":
setSelectedReviews([]);
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;
},
contentRef,
);
return (

View File

@ -359,30 +359,6 @@ export default function SearchView({
setSearchDetail(uniqueResults[newIndex]);
}
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;
@ -391,17 +367,9 @@ export default function SearchView({
);
useKeyboardListener(
[
"a",
"Escape",
"ArrowDown",
"ArrowLeft",
"ArrowRight",
"ArrowUp",
"PageDown",
"PageUp",
],
["a", "Escape", "ArrowLeft", "ArrowRight"],
onKeyboardShortcut,
contentRef,
);
// scroll into view