Skip to main content

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:

KeysAction
ArrowNudge the selection (Shift = coarse step).
Cmd/Ctrl + ArrowGrow a flowchart CREATE session โ€” a connected node in that direction.
Alt + ArrowNavigate to the adjacent node (graph neighbour, else spatially nearest).
Cmd/Ctrl + Shift + ArrowAlign 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.