Input Handling
The engine never touches the DOM directly. Hosts translate raw browser events into normalized, CSS-pixel domain events and feed them in. This keeps the core framework-agnostic and testable, and lets non-browser hosts drive the same engine.
Normalizing DOM eventsโ
@oh-just-another/state ships normalizers that convert raw pointer / keyboard / wheel events into the engine's domain shapes. Pointer and wheel normalizers take the host element so they can project client coordinates into host-local space.
import {
fromPointerEvent,
fromKeyboardEvent,
fromWheelEvent,
isEditableTarget,
} from "@oh-just-another/state";
host.addEventListener("pointerdown", (ev) => {
const data = fromPointerEvent(ev, host);
// forward `data` into your pointer handling
});
host.addEventListener("wheel", (ev) => {
const data = fromWheelEvent(ev, host);
// forward `data`
});
Skipping editable targetsโ
Global keyboard handlers must bail when the event is aimed at a text field, <select>, or any contenteditable host, so the user's typing is not hijacked. isEditableTarget answers that for the node you pass; inside a shadow root, pass event.composedPath()[0] (the real focused node) rather than the retargeted event.target.
import { fromKeyboardEvent, isEditableTarget } from "@oh-just-another/state";
window.addEventListener("keydown", (ev) => {
if (isEditableTarget(ev.target)) return;
const data = fromKeyboardEvent(ev);
// dispatch shortcut
});
isEditableTarget is also re-exported from @oh-just-another/editor. For keyboard shortcuts specifically, bindEditorHotkeys wires a keydown listener that already applies this editable-target check for you.
Arrow-key bindingsโ
The four arrow keys drive selection movement and flowchart building, disambiguated by modifier:
| Keys | Action |
|---|---|
Arrow | Nudge the selection (Shift = coarse step). |
Cmd/Ctrl + Arrow | Grow a flowchart CREATE session โ a connected node in that direction. |
Alt + Arrow | Navigate to the adjacent node (graph neighbour, else spatially nearest). |
Cmd/Ctrl + Shift + Arrow | Align the selection left / right / top / bottom. |
Flowchart create lifecycleโ
Cmd/Ctrl + Arrow opens a CREATE session on the single selected node. Each press in the same direction adds one more pending sibling (editor.growFlowchart(dir)); the pending nodes + links are a PREVIEW only โ the scene and history are untouched. bindEditorHotkeys is keydown-only, so the host wires the commit / cancel triggers:
window.addEventListener("keyup", (ev) => {
// Cmd/Ctrl released โ commit the preview as one undo step.
if (!ev.metaKey && !ev.ctrlKey && editor.flowchartPreview !== null) {
editor.commitFlowchart();
}
});
window.addEventListener("keydown", (ev) => {
if (ev.key === "Escape" && editor.flowchartPreview !== null) {
editor.cancelFlowchart(); // discard the preview, keep the selection
ev.preventDefault();
}
});
window.addEventListener("blur", () => {
editor.commitFlowchart(); // a missed keyup can't strand the preview
});
@oh-just-another/editor's Diagram component wires this for you. commitFlowchart selects the first new node; flowchartPreview exposes the pending nodes + links for custom overlays.