Skip to main content

Shapes

A diagram is a Scene made of elements. @oh-just-another/scene defines the element model and the pure operations that mutate it. Elements are plain data; the editor engine is just the thing that applies operations to them over time.

The built-in element typesโ€‹

Element is a discriminated union of BuiltinElement variants, each with a type guard:

  • RectangleElement, EllipseElement, PolygonElement, PathElement โ€” geometry.
  • TextElement โ€” text with TextStyle.
  • ImageElement โ€” raster / SVG media (see Assets).
  • BlockArrowElement, BrushElement โ€” arrows and freehand strokes.
  • GroupElement, FrameElement, TemplateElement โ€” containers and reusable templates.

Type guards (isRectangle, isText, isImage, isGroup, isFrame, โ€ฆ) narrow an Element safely.

Mutating a sceneโ€‹

Operations are pure functions that take a scene and return { scene, patch } โ€” they never mutate in place. The patch is what history and collaboration replay.

updateElement takes an updater function, not a partial:

import {
emptyScene,
addElement,
updateElement,
removeElement,
orderForTop,
DEFAULT_LAYER_ID,
} from "@oh-just-another/scene";
import { elementId } from "@oh-just-another/types";

const id = elementId("r1");
let { scene } = addElement(emptyScene(), {
id,
layerId: DEFAULT_LAYER_ID,
type: "rectangle",
position: { x: 0, y: 0 },
rotation: 0,
scale: { x: 1, y: 1 },
order: orderForTop([]),
style: { fill: "#88f" },
width: 100,
height: 50,
});

({ scene } = updateElement(scene, id, (el) => ({ ...el, style: { fill: "#f80" } })));
({ scene } = removeElement(scene, id));

Links (edges) have their own operations โ€” addLink, updateLink, removeLink โ€” and a Link model with endpoints, routing, arrowheads and labels.

Layers, viewport, queriesโ€‹

A Scene also carries Layers (addLayer / updateLayer), a Viewport (pan / zoom / grid), and Annotations. A rich query layer reads it without scanning: getElement, getElementAt, getElementsInBounds, getElementWorldBounds, plus a spatial index (buildSpatialIndex, SpatialGrid) for large scenes.

Anchors and snappingโ€‹

Elements expose anchors (STANDARD_ANCHORS, getAnchorWorld, findNearestAnchor) that links attach to, and a snap engine (SnapEngine with gridSnapper, anchorSnapper, outlineSnapper) drives alignment during drags.

Extending the modelโ€‹

Custom element types plug in through registries: registerBounder provides an AABB so the element participates in hit-testing and selection, and a matching registerElementRenderer (from the renderer layer) draws it. Both are re-exported from @oh-just-another/editor.