@siali/sorolla
v2.4.2
Published
Annotation surface (bbox + polygon) for Siali, on React 19 + Konva
Readme
@siali/sorolla
Annotation surface for React 19: draw and correct bboxes and polygons over an image, with zoom, pan and keyboard shortcuts. The host owns the data, the toolbar and the chrome; the library owns the canvas.
pnpm add @siali/sorolla konva react-konva zustandreact, react-dom, konva and zustand are peer dependencies. React 18 users stay on the 1.x line:
pnpm add @siali/sorolla@react-18Minimal example
'use client';
import { SorollaAnnotator, type Annotation } from '@siali/sorolla';
import { useState } from 'react';
export function Review({ image }: { image: string }) {
const [annotations, setAnnotations] = useState<Annotation[]>([]);
return (
<div style={{ height: 600 }}>
<SorollaAnnotator
image={image}
annotations={annotations}
onAnnotationsChange={setAnnotations}
createId={() => crypto.randomUUID()}
/>
</div>
);
}The component fills its parent, so give the container a height. In Next.js, import it dynamically with ssr: false and mark canvas as a webpack external (Konva pulls the node bindings during SSR).
Model
Every coordinate is normalized to 0–1 against the image, never stage pixels.
type Annotation =
| { id: string; type: 'bbox'; box: { x: number; y: number; w: number; h: number }; label?: string; meta?: Record<string, unknown> }
| { id: string; type: 'polygon'; points: { x: number; y: number }[]; label?: string; meta?: Record<string, unknown> };Invariants:
box.x/box.yis the top-left corner;box.wandbox.hare>= 0.- A polygon has at least
MIN_POLYGON_VERTICES(3) vertices, in contour order. - Ids are unique inside the array.
onAnnotationsChangealways receives the full replacement array, never patches.metais opaque to the library: put domain data (detection ids, status, origin) there and map it in the host.
Style is render, not data: there are no color or stroke fields in the model. lookById[id].edges paints host strings on the four sides of a bbox (or the AABB of a polygon). Sorolla does not compute size or units.
Props
| Prop | Type | Notes |
|---|---|---|
| image | string | Image URL painted under the annotations |
| annotations | Annotation[] | Source of truth, owned by the host |
| onAnnotationsChange | (next: Annotation[]) => void | Full array on every create, edit and delete |
| createId | () => string | Identity for new annotations |
| tool / onToolChange | ToolId | select | bbox | polygon | pan |
| selectedId / onSelectedIdChange | string \| null | Single selection, syncable with a host list |
| hoveredId / onHoveredIdChange | string \| null | Single hover, syncable with a host list |
| enableShortcuts | boolean (default true) | Registers the shortcut map on the annotator container |
| enableHistory | boolean (default true) | Snapshot undo/redo, up to 50 steps |
| bodyDraggable | boolean (default true) | When false, bodies do not drag; handles still resize |
| lockedIds | readonly string[] | Geometry cannot be moved, resized or deleted |
| lookById | Record<string, AnnotationLook> | Per-id stroke, idle caption, optional dash, optional edges labels |
| onViewportChange | (viewport: Viewport) => void | Read-only viewport telemetry |
| className / style | — | Applied to the container |
Ref handle: fit(), zoomIn(), zoomOut(), cancelDraft().
tool, selectedId and hoveredId are optional: pass them to control the surface from a host toolbar or sidebar, omit them and the surface keeps its own state.
Tools
select— click to select, drag to move, corner handles to resize a bbox, vertex handles to edit a polygon; the midpoint handle inserts a vertex.bbox— press, drag, release. A drag smaller thanMIN_BOX_SIZEcreates nothing.polygon— click to add vertices, close by clicking the first vertex, double-clicking, or pressingEnterwith 3 or more vertices.pan— drag the image. Also available while holdingSpaceor dragging with the middle button.
Deleting is an action on the selection, not a tool. Selecting and editing (drag, resize, vertices) happen under select. Under bbox / polygon, existing shapes do not intercept the pointer so you can draw over them.
Touch
Pointer events are unified, so a stylus or a finger draws like a mouse. Two fingers pinch to zoom and drag to pan at the same time, and a two-finger gesture cancels any draft it interrupts. On touch-first devices the handles grow automatically.
Shortcuts
Registered on the annotator container, so the surface never steals keys from the rest of the page. Mod is Ctrl on Windows/Linux and Cmd on macOS.
| Key | Action |
|---|---|
| V / B / P / H | select / bbox / polygon / pan |
| Space (hold) | temporary pan |
| Escape | cancel the draft, or deselect |
| Delete / Backspace | delete the selection |
| = / - / 0 | zoom in / zoom out / fit |
| Mod+Z / Mod+Shift+Z / Mod+Y | undo / redo |
Zoom and viewport
Wheel and trackpad zoom anchored at the cursor, fit-contain on load and on container resize, fit() through the ref. The viewport is internal state; onViewportChange only reports it.
Not in 2.0
Line and point tools, freehand, measuring, multi-select and marquee, copy/paste, a product toolbar, a bidirectional controlled viewport, custom keymaps.
