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 withTextStyle.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.