PageScene: useEditorCanvas hook + tabIndex on viewport canvas

The Phase 6 hooks (useFreeCam, useViewportPointer, useViewportKeyboard)
need a React-shaped accessor for the canvas DOM node so their
useEffects re-run when the canvas mounts/unmounts. Promote the
existing editorCanvasSignal / camViewCanvasSignal into useEditorCanvas
/ useCamViewCanvas hooks that bridge signal -> React state via a
subscribe.

Add tabIndex={0} and outline:none to the editor canvas so it can
receive keyboard focus without showing a focus ring.
This commit is contained in:
Tanish Manku committed 2026-04-29 09:48:59 -07:00
1 parent c23e652499
commit 61c4936c29
2 files changed
+28

No files matched your search

@@ -17,6 +17,8 @@ export const EditorCanvas = () => {
id="video-scene"
width="1280px"
height="720px"
tabIndex={0}
style={{ outline: "none" }}
/>
);
};
@@ -0,0 +1,26 @@
import { useEffect, useState } from "react";
import {
camViewCanvasSignal,
editorCanvasSignal,
} from "~/pages/PageScene/contexts/EngineContext";
// React-shaped accessors for the canvas DOM nodes that the engine
// renders into. The underlying storage is a signal (set by
// EngineCanvases on mount); these hooks convert that to React state so
// dependent useEffects re-run when the canvas mounts/unmounts.
export function useEditorCanvas(): HTMLCanvasElement | null {
const [canvas, setCanvas] = useState<HTMLCanvasElement | null>(
editorCanvasSignal.value,
);
useEffect(() => editorCanvasSignal.subscribe(setCanvas), []);
return canvas;
}
export function useCamViewCanvas(): HTMLCanvasElement | null {
const [canvas, setCanvas] = useState<HTMLCanvasElement | null>(
camViewCanvasSignal.value,
);
useEffect(() => camViewCanvasSignal.subscribe(setCanvas), []);
return canvas;
}