Skip to main content

Tools

Tools are expressed as the editor's active mode. The mode dictates how a pointer-down is interpreted โ€” select an element, pan, or draw a new shape of a given kind.

Modesโ€‹

Mode is a string union, and DEFAULT_MODE is "select". The built-in modes and their toolbar hotkeys:

ModeHotkeyBehaviour
selectVClick selects, drag on empty lassos, drag on selection moves.
handHDedicated pan mode.
draw-rectRRubber-band rectangle creation.
draw-ellipseORubber-band ellipse creation.
draw-textTClick places a text shape and opens its inline editor.
draw-edgeLEdge creation from press-down shape to release shape.
draw-frameFRubber-band frame creation.
brushBPressure-sensitive freehand stroke.
eraseEDrag to sweep shapes into a pending set (previewed dimmed); release deletes in one undo step. Brush strokes are erased partially โ€” the swept arc is removed and the survivors become fragments.
laserKEphemeral presentation pointer; the fading trail never touches the scene or history.
cropโ€”Image crop, entered by double-clicking an image. Enter commits, Escape cancels.

Colour sampling is not a mode: the colour picker arms a one-shot pipette via editor.beginEyedropperPick(onPick), and the next canvas click samples the colour under the cursor without leaving the current tool.

Pan and zoom remain available as gestures (middle-mouse, Space+drag, wheel) regardless of the active mode. In read-only mode only select, hand and laser mode switches are allowed.

The activeTool objectโ€‹

editor.activeTool is the single source of truth for the current tool โ€” a value object { type, locked, lastActiveTool } (ActiveTool). editor.setActiveTool(type) is the only way to switch; toolbar buttons and hotkeys reach it through the action registry.

import { Editor, DEFAULT_MODE, type ActiveTool, type Mode } from "@oh-just-another/state";

editor.setActiveTool("draw-rect");
const tool: ActiveTool = editor.activeTool;
tool.type; // "draw-rect"
tool.lastActiveTool; // "select" โ€” the tool active before the switch

The object reference is stable between changes, so it is safe in React dependency arrays.

With the React component, the tool is on the EditorAPI handle:

import { useRef } from "react";
import { Editor, type EditorAPI } from "@oh-just-another/editor";

function Host() {
const ref = useRef<EditorAPI>(null);
const drawRect = () => ref.current?.setActiveTool("draw-rect");
return <Editor ref={ref} initialTool="select" />;
}

Sticky tools (tool lock)โ€‹

By default a draw tool auto-reverts to select after one successful create. Toggle tool-lock (activeTool.locked) to keep the tool active so the user can draw many shapes in a row.

editor.setToolLocked(true);
const locked = editor.activeTool.locked;

Reacting to tool changesโ€‹

Listen to the typed tool event to keep a toolbar in sync. It fires on a type switch and on a lock flip.

editor.on("tool", (tool) => highlightToolButton(tool.type));