Merge pull request #35 from ibs0d/codex/fix-passive-listener-bug-in-live-grid-zoom

Fix passive wheel listener for live grid card zoom
This commit is contained in:
ibs0d 2026-03-09 12:47:52 +11:00 committed by GitHub
commit 6e8fd4fa32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -441,6 +441,7 @@ export default function DraggableGridLayout({
const cameraZoomViewportRefs = useRef<Record<string, HTMLDivElement | null>>( const cameraZoomViewportRefs = useRef<Record<string, HTMLDivElement | null>>(
{}, {},
); );
const cameraZoomWheelCleanupRefs = useRef<Record<string, () => void>>({});
const getCardZoomDimensions = useCallback((cameraName: string) => { const getCardZoomDimensions = useCallback((cameraName: string) => {
const viewport = cameraZoomViewportRefs.current[cameraName]; const viewport = cameraZoomViewportRefs.current[cameraName];
@ -492,14 +493,18 @@ export default function DraggableGridLayout({
); );
const handleCardWheelZoom = useCallback( const handleCardWheelZoom = useCallback(
(cameraName: string, event: React.WheelEvent<HTMLDivElement>) => { (
cameraName: string,
event: WheelEvent,
viewportElement: HTMLDivElement,
) => {
if (!event.shiftKey) { if (!event.shiftKey) {
return; return;
} }
event.preventDefault(); event.preventDefault();
const bounds = event.currentTarget.getBoundingClientRect(); const bounds = viewportElement.getBoundingClientRect();
const cursorX = event.clientX - bounds.left; const cursorX = event.clientX - bounds.left;
const cursorY = event.clientY - bounds.top; const cursorY = event.clientY - bounds.top;
@ -514,8 +519,7 @@ export default function DraggableGridLayout({
cursorX, cursorX,
cursorY, cursorY,
); );
const content = event.currentTarget const content = viewportElement.firstElementChild as HTMLElement | null;
.firstElementChild as HTMLElement | null;
const persisted = toPersistedCameraZoomState(next, { const persisted = toPersistedCameraZoomState(next, {
viewportWidth: bounds.width, viewportWidth: bounds.width,
viewportHeight: bounds.height, viewportHeight: bounds.height,
@ -534,6 +538,37 @@ export default function DraggableGridLayout({
[getDefaultZoomTransform], [getDefaultZoomTransform],
); );
const detachCardZoomWheelListener = useCallback((cameraName: string) => {
cameraZoomWheelCleanupRefs.current[cameraName]?.();
delete cameraZoomWheelCleanupRefs.current[cameraName];
}, []);
const attachCardZoomWheelListener = useCallback(
(cameraName: string, viewportElement: HTMLDivElement) => {
detachCardZoomWheelListener(cameraName);
const listener = (event: WheelEvent) => {
handleCardWheelZoom(cameraName, event, viewportElement);
};
viewportElement.addEventListener("wheel", listener, { passive: false });
cameraZoomWheelCleanupRefs.current[cameraName] = () => {
viewportElement.removeEventListener("wheel", listener);
};
},
[detachCardZoomWheelListener, handleCardWheelZoom],
);
useEffect(() => {
return () => {
Object.values(cameraZoomWheelCleanupRefs.current).forEach((cleanup) =>
cleanup(),
);
cameraZoomWheelCleanupRefs.current = {};
};
}, []);
useEffect(() => { useEffect(() => {
cameras.forEach((camera) => { cameras.forEach((camera) => {
hydrateCameraZoomFromStorage(camera.name); hydrateCameraZoomFromStorage(camera.name);
@ -782,11 +817,15 @@ export default function DraggableGridLayout({
ref={(node) => { ref={(node) => {
cameraZoomViewportRefs.current[camera.name] = node; cameraZoomViewportRefs.current[camera.name] = node;
if (node) { if (!node) {
hydrateCameraZoomFromStorage(camera.name); detachCardZoomWheelListener(camera.name);
return;
} }
attachCardZoomWheelListener(camera.name, node);
hydrateCameraZoomFromStorage(camera.name);
}} }}
onWheel={(event) => handleCardWheelZoom(camera.name, event)}
> >
<LivePlayer <LivePlayer
key={camera.name} key={camera.name}