Skip to content

Editor API Reference

The main editor component — all-in-one canvas, toolbar, inspector, and timeline.

interface ElucimEditorProps {
initialDocument?: ElucimDocument;
initialFrame?: number | 'last';
theme?: ElucimTheme; // unified content theme
editorTheme?: Record<string, string>; // explicit chrome overrides
onDocumentChange?: (document: ElucimDocument) => void;
onBrowseImage?: BrowseImageFn; // custom image picker callback
imageResolver?: ImageResolverFn; // resolve image refs to URLs
className?: string;
style?: React.CSSProperties;
}
PropTypeDefaultDescription
initialDocumentElucimDocumentEmpty 800×600 sceneStarting scene document
initialFramenumber | 'last'0Starting frame. Use 'last' to start at final frame.
themeElucimThemeUnified content theme — same type used by DslRenderer. Editor chrome is auto-derived from content tokens.
editorThemeRecord<string, string>Explicit chrome overrides — accepts hex, named colors, or var() refs. Overrides auto-derived values.
onDocumentChange(doc: ElucimDocument) => voidCalled on every document change (skips initial mount)
onBrowseImageBrowseImageFnCustom image picker callback. When set, shows a ”…” browse button next to image fields. See Image Assets.
imageResolverImageResolverFnResolves opaque image ref values to renderable URLs. See Image Assets.
classNamestringCSS class on root element
styleCSSPropertiesInline styles on root element
function MyApp() {
const handleChange = (doc: ElucimDocument) => {
// Called on every edit — debounce if doing expensive work
console.log('Document changed:', doc);
};
return (
<ElucimEditor
initialDocument={myDoc}
onDocumentChange={handleChange}
/>
);
}

The internal layout component used by ElucimEditor. Must be rendered inside an EditorProvider. Use this when you need custom composition (e.g. adding your own panels) while keeping the standard floating-panel layout, scrollbar styles, and theme injection.

import { EditorProvider, ElucimEditorLayout, useEditorDocument } from '@elucim/editor';
function SaveButton() {
const doc = useEditorDocument();
return <button onClick={() => save(doc)}>Save</button>;
}
function MyEditor({ doc }: { doc: ElucimDocument }) {
return (
<EditorProvider initialDocument={doc}>
<ElucimEditorLayout theme={myTheme} />
<SaveButton />
</EditorProvider>
);
}
ExportDescription
ElucimEditorMain editor — canvas + toolbar + inspector + timeline
ElucimEditorLayoutInternal layout for custom composition inside EditorProvider
ElucimCanvasCanvas with zoom/pan and selection overlays
ToolbarElement template palette
InspectorProperty editing panel
TimelineAnimation playback controls
FloatingPanelDraggable floating container
SelectionOverlaySelection boxes, resize/rotate handles
DotGridZoom-adaptive dot grid background
MinimapCanvas overview minimap
ZoomControlsZoom buttons and zoom-to-fit

Returns the full editor state and dispatch function.

function useEditorState(): [EditorState, React.Dispatch<EditorAction>];
const [state, dispatch] = useEditorState();
// Read state
console.log(state.selectedIds);
console.log(state.viewport.zoom);
// Dispatch actions
dispatch({ type: 'SELECT', ids: ['element-1'] });
dispatch({ type: 'UNDO' });

Returns the current ElucimDocument.

function useEditorDocument(): ElucimDocument;

Returns the array of selected element IDs.

function useEditorSelection(): string[];
HookDescription
useViewport()Zoom/pan state and handlers
useDrag()Element drag, resize, and rotate interactions
useMarquee()Marquee (lasso) selection interactions
useMeasuredBounds()DOM-based element bounds measurement
interface EditorState {
document: ElucimDocument;
selectedIds: string[];
viewport: { x: number; y: number; zoom: number };
past: ElucimDocument[];
future: ElucimDocument[];
currentFrame: number;
isPlaying: boolean;
activeTool: EditorTool;
isPanning: boolean;
toolbarPosition: { x: number; y: number };
inspectorPosition: { x: number; y: number } | null;
inspectorPinned: boolean;
toolbarCollapsed: boolean;
}
type EditorTool = 'select' | 'rect' | 'circle' | 'line' | 'arrow' | 'text' | 'latex';

All available dispatch actions:

ActionPayloadDescription
SELECTids: string[]Replace selection
SELECT_ADDid: stringAdd to selection
SELECT_TOGGLEid: stringToggle element in/out of selection
DESELECT_ALLClear selection
SET_DOCUMENTdocument: ElucimDocumentReplace entire document
UPDATE_ELEMENTid: string, changes: objectUpdate element properties
UPDATE_CANVASchanges: objectUpdate scene root properties
ADD_ELEMENTelement: objectAdd element to scene
DELETE_ELEMENTSids: string[]Remove elements
DUPLICATE_ELEMENTSids: string[], offset?: {dx, dy}Clone elements with offset (default +20,+20)
MOVE_ELEMENTid: string, dx: number, dy: numberTranslate element (moves all selected if multi-selected)
RESIZE_ELEMENTid: string, handle: string, dx, dy, constrain?: booleanResize element. constrain forces uniform scaling.
ROTATE_ELEMENTid: string, angleDeg: numberRotate element
GROUP_ELEMENTSids: string[]Group 2+ elements
UNGROUPid: stringUngroup a group element
RENAME_ELEMENTid: string, newId: stringRename element ID
REORDER_ELEMENTid: string, newIndex: numberChange element z-order by index
BRING_FORWARDids: string[]Move elements one step forward
SEND_BACKWARDids: string[]Move elements one step backward
BRING_TO_FRONTids: string[]Move elements to front
SEND_TO_BACKids: string[]Move elements to back
ALIGN_ELEMENTSids: string[], direction: AlignDirectionAlign elements (left, right, top, bottom, center-h, center-v)
DISTRIBUTE_ELEMENTSids: string[], direction: DistributeDirectionDistribute 3+ elements evenly (horizontal, vertical)
SET_VIEWPORTviewport: Partial<Viewport>Update zoom/pan
SET_FRAMEframe: numberSeek to frame
SET_PLAYINGplaying: booleanPlay or pause
SET_TOOLtool: EditorToolSwitch active tool
ZOOM_TO_FITAuto-fit canvas to viewport
UNDOUndo last action
REDORedo last undone action

Sentinel value used when the canvas itself is selected:

import { CANVAS_ID } from '@elucim/editor';
// CANVAS_ID === '__canvas__'
const isCanvasSelected = selectedIds.includes(CANVAS_ID);

All shortcuts work when the editor canvas is focused. They are disabled when typing in input fields.

ShortcutAction
Delete / BackspaceDelete selected elements
Ctrl+ZUndo
Ctrl+Y / Ctrl+Shift+ZRedo
Ctrl+ASelect all elements
Ctrl+DDuplicate selected elements
Ctrl+CCopy selected elements
Ctrl+VPaste copied elements
Ctrl+GGroup selected elements (2+)
Ctrl+Shift+GUngroup selected group
EscapeDeselect all, reset to select tool
ShortcutAction
Ctrl+]Bring forward one step
Ctrl+[Send backward one step
Ctrl+Shift+]Bring to front
Ctrl+Shift+[Send to back
ShortcutAction
Arrow KeysNudge selected elements by 1px
Shift+Arrow KeysNudge by 10px
Ctrl+= / Ctrl+-Zoom in / out
Ctrl+0Zoom to fit
Ctrl+1Zoom to 100%
Space (hold)Pan mode
Ctrl/Cmd+ScrollZoom at cursor
InteractionBehavior
Click elementSelect (replaces selection)
Shift/Ctrl/Cmd+Click elementToggle in/out of selection
Drag elementMove selected elements
Alt+Drag elementDuplicate in place, then drag copy
Shift+Drag resize handleConstrained (uniform) resize
Drag on empty canvasMarquee selection
Shift+Drag on empty canvasAdd to existing selection
Middle-click dragPan
Right-clickContext menu

Right-click on elements or the canvas to access:

Context menu with z-order, clipboard, and selection options Context menu with z-order, clipboard, and selection options (light mode)
  • Group / Ungroup — combine or split element groups
  • Duplicate / Copy / Paste / Delete — clipboard and element operations
  • Bring Forward / Send Backward / Bring to Front / Send to Back — z-order controls
  • Align (when 2+ selected) — Left, Right, Top, Bottom, Center ↔, Center ↕
  • Distribute (when 3+ selected) — Horizontal, Vertical (even spacing)
  • Select All / Deselect All
// Serialize document to JSON string
function exportToJson(document: ElucimDocument): string;
// Parse JSON string to document
function importFromJson(json: string): ElucimDocument;
// Trigger browser file download
function downloadAsJson(document: ElucimDocument, filename?: string): void;
// Compute bounding box for a DSL element
function getElementBounds(element: object):
{ x: number; y: number; width: number; height: number } | null;
// Merge multiple bounding boxes
function mergeBounds(boxes: Array<{ x: number; y: number; width: number; height: number }>):
{ x: number; y: number; width: number; height: number };
// Hit-test a point against a bounding box
function isPointInBounds(
point: { x: number; y: number },
bounds: { x: number; y: number; width: number; height: number }
): boolean;
// Snap a value to the nearest grid increment
function computeSnap(value: number, gridSize: number): number;

Pass the same ElucimTheme to both DslRenderer and ElucimEditor — the editor automatically derives its chrome tokens from the content theme:

import type { ElucimTheme } from '@elucim/core';
const myTheme: ElucimTheme = {
foreground: '#e0e0e0',
background: '#1a1a2e',
primary: '#4fc3f7',
muted: '#64748b',
surface: '#1e293b',
border: '#334155',
};
// Same theme for both — no duplicate token maps needed
<DslRenderer dsl={doc} theme={myTheme} colorScheme="dark" />
<ElucimEditor initialDocument={doc} theme={myTheme} />

Use editorTheme to override specific chrome tokens while keeping the auto-derivation:

<ElucimEditor
initialDocument={doc}
theme={myTheme}
editorTheme={{ 'color-scheme': 'light', accent: '#ff6b35' }}
/>
// Get CSS var() reference with fallback
function v(token: string): string;
// Merge theme overrides with defaults
function buildThemeVars(overrides?: Record<string, string>): Record<string, string>;
// Derive editor chrome from an ElucimTheme content theme.
// Uses CSS color-mix() for panel/chrome/input-bg, so var() surface values work correctly.
function deriveEditorTheme(contentTheme: ElucimTheme, colorScheme: 'light' | 'dark'): Record<string, string>;
// Generate SVG rotation cursor with custom color
function makeRotateCursor(color?: string): string;
// Pre-built rotation cursor with default accent
const ROTATE_CURSOR: string;
// Full token registry with defaults
const EDITOR_TOKENS: Record<string, string>;
// Light-mode token defaults (used when color-scheme: 'light')
const EDITOR_TOKENS_LIGHT: Record<string, string>;

Automatically maps content tokens → editor chrome tokens:

import { deriveEditorTheme } from '@elucim/editor';
import type { ElucimTheme } from '@elucim/core';
const myTheme: ElucimTheme = {
foreground: '#e0e0e0',
background: '#1a1a2e',
surface: 'var(--my-surface)', // CSS var() values work
primary: '#4fc3f7',
};
const chrome = deriveEditorTheme(myTheme, 'dark');
// chrome.panel → 'color-mix(in srgb, var(--my-surface) 95%, transparent)'
// chrome.chrome → 'color-mix(in srgb, var(--my-surface) 85%, transparent)'
// chrome.fg → '#e0e0e0'
// chrome.accent → '#4fc3f7'

Panel, chrome, and input-bg use CSS color-mix() to derive semi-transparent variants from the surface color. This works with both hex values and var() references — no JavaScript color parsing needed.

// All available element templates
const ELEMENT_TEMPLATES: Array<{
type: string;
category: string;
label: string;
defaults: object;
}>;
// Templates grouped by category
function getTemplatesByCategory(): Record<string, typeof ELEMENT_TEMPLATES>;
// Display labels for categories
const CATEGORY_LABELS: Record<string, string>;