Indicators
Selection indicators are the on-canvas chrome that surrounds the current
selection โ the selection frame, handles, and the floating panel that follows
it. The drawing lives in @oh-just-another/state and the React surface in
@oh-just-another/react-ui.
Overlay passโ
renderOverlay from @oh-just-another/state paints all interaction chrome โ
selection frames (oriented per shape rotation), resize/rotate handles, lasso,
link highlights, peer cursors โ onto a render target in one pass, styled by an
OverlayStyle (start from DEFAULT_OVERLAY_STYLE). Handle geometry helpers
(handlePosition, hitHandle, HANDLE_SIZE) are exported separately โ see
Handles.
import { renderOverlay, DEFAULT_OVERLAY_STYLE, type OverlayStyle } from "@oh-just-another/state";
Floating selection panelโ
@oh-just-another/react-ui provides SelectionFloatingPanel, the panel that
tracks the active selection bounds (via floating-ui, flipping sides when space
runs out) and hosts the property quick actions. It reads the live selection
from the diagram context; offset, placement, and edge insets are optional
props.
import { SelectionFloatingPanel } from "@oh-just-another/react-ui";
function Overlay() {
return <SelectionFloatingPanel />;
}
What the panel shows depends on the selection:
- One element โ the full control set of its type, a comment button and the element link.
- Several elements โ the intersection of the members' multi-selection
sets: two shapes keep the shape controls (minus the link), a shape + a text
share the text controls (font, style, colour โ written through
Editor.updateTextStyle/updateTextProps, which route to a shape's label or a text element's own style), a shape + a frame share nothing. Mixed types add a Filter that narrows the selection to one type. The tail gains Arrange (align, distribute from three) and Group. - A group โ its children's shared controls; Ungroup replaces Group and
align / distribute stay disabled until something else joins the selection.
Double-click a child to edit inside the group: the toolbar then targets the
children and hides Group / Ungroup;
Escor a click outside leaves.
Cursorsโ
The canvas cursor is computed from the interaction state (computeCursor) and
uses system keywords only:
| State | Cursor |
|---|---|
| Idle, hover over empty canvas or an element (selected or not) | default |
| Dragging an element, link, waypoint, caption or comment | default |
| Resize handle (hover / drag) | ns / ew / nwse / nesw-resize, following the shape's rotation |
| Rotate handle | grab / grabbing |
| Link start dot, drawing a link from an anchor | crosshair |
| Link endpoint / waypoint handle | grab |
| Caption of the selected link | text |
| Rubber-band selection | crosshair |
Hand tool, Space, panning, read-only board | grab / grabbing |
| Rect / ellipse / frame / edge / brush / laser / crop tools, colour picker | crosshair |
| Text tool, in-canvas text editing | text |
| Eraser | none (the overlay paints the eraser ring) |
| Comment pin | pointer |
Every outcome has a CursorRole; a host can replace the image per role with
editor.setCursorOverride(role, { url, url2x, hotspot, fallback }) (DPR-aware
via image-set) or reset it with null.
The full <Editor> from @oh-just-another/editor wires these indicators up by
default; reach for the lower-level pieces above only when building a custom
overlay or a headless rendering of the selection state.