Workspace & Content Nodes
The Workspace is the top-level orchestrating engine in Canvus. It binds interaction handlers, manages node hierarchies, and renders overlays. A Content Node is a single user-provided HTML element represented in the workspace.
The Workspace
The Workspace class is the central orchestrator and the public entry point for consumer integrations. It:
- Hooks up mouse/pointer event bindings
- Delegates gesture and shortcut interactions to specialized, claim-based handlers (panning, resizing, dragging, reparenting, spacing adjustment, marquee selection, and tool drawing)
- Manages the Synchronous Reflow Loop
- Coordinates between the Shadow DOM layer and the Canvas overlay
import { Workspace } from '@canvus/core'
const ws = new Workspace(containerElement, {
onHTMLCommit(id, html) {
// Receive clean HTML when a node is modified
},
onOperationsGenerated(ops) {
// Receive operation payloads for undo/redo
},
})Claim-Based Event Handling
To keep the codebase modular, interaction logic is decomposed into dedicated handlers (found in src/handlers/). The Workspace acts as a thin event dispatcher:
- Pointer Events: During
pointerdown, the workspace loops through registeredInteractionHandlers in priority order and callsclaim(). The first handler to claim the event becomes theactiveHandler, and subsequent move/up/cancel events are routed to it until released. - Keyboard Shortcuts: Key down and up events are forwarded to
KeyboardHandlers (such as clipboard operations or layout nudging) which consume the key strokes as needed.
WorkspaceContext
Handlers interact with the workspace using the WorkspaceContext interface. This context provides read-only access to sub-systems (like NodeTree, ShadowMount, OverlayRenderer), mutable state accessors (like the viewport and active selections), and common helper operations (like triggering a canvas redraw or emitting interaction changes), preventing handlers from directly mutating internal workspace state.
Content Nodes
A Content Node represents a single piece of user-provided HTML in the workspace. Nodes are defined using the WebHTMLNode interface:
interface WebHTMLNode {
id: string // Unique identifier
rawMarkup: string // The user's raw HTML string
currentRect: Rect | null // Initial position and size (canvas-space)
parentId?: string | null // Parent node ID (null for root-level)
childIds?: readonly string[] // Child node IDs
layoutMode?: LayoutMode | null // Display mode hint (flex, grid, block)
depth?: number // Tree depth (0 for root)
}Adding Nodes
// Add a root-level node
ws.addNode({
id: 'card-1',
rawMarkup: '<div class="card">Hello World</div>',
currentRect: { x: 100, y: 100, width: 300, height: 200 },
})
// Add a child node inside a parent container
ws.addNode({
id: 'child-1',
rawMarkup: '<p>Child content</p>',
currentRect: null,
}, 'card-1') // parentIdManipulating Nodes
// Update a node's HTML content
ws.updateMarkup('card-1', '<div class="card">Updated!</div>')
// Change a CSS property
ws.setNodeStyle('card-1', 'background-color', '#f0f0f0')
// Batch style changes
ws.setNodeStyles('card-1', {
'padding': '24px',
'border-radius': '12px',
})
// Manipulate CSS classes (Tailwind/Bootstrap)
ws.addClass('card-1', 'shadow-lg')
ws.removeClass('card-1', 'border')
ws.toggleClass('card-1', 'hidden')
// Remove a node
ws.removeNode('card-1')The Node Tree
Under the hood, Canvus maintains an in-memory NodeTree that tracks parent-child relationships, depth indices, and sibling ordering. This tree is always kept in sync with the Shadow DOM.
Never mutate parentId or childIds directly. Always use the Workspace mutation APIs (addNode, removeNode, reparentNode, reorderChild) to maintain structural invariants and prevent circular references.
// Access the tree
const tree = ws.getNodeTree()
// Get all nodes in topological order
const nodes = ws.getNodes()
// Reparent a node
ws.reparentNode('child-1', 'new-parent-id')
// Reorder a child within its parent
ws.reorderChild('child-1', 0) // Move to first positionPreview Mode
The Workspace supports a Preview Mode toggle that disables all editing overlays and lets pointer events pass through to the Shadow DOM content. This allows native interaction testing — links, buttons, and forms behave as they would in a live browser.
// Enter preview mode
ws.setPreviewMode(true)
// Check if preview is active
if (ws.isPreviewMode()) {
console.log('Editing is disabled')
}
// Return to edit mode
ws.setPreviewMode(false)Entering Preview Mode automatically clears selection, hover state, and any active interaction gesture.
Stylesheet Injection
Custom CSS (Tailwind, brand fonts, imported template styles) is injected directly into the Shadow DOM so it is fully scoped and never pollutes the host application. For detail and examples, see Scoped Stylesheets.
JS Badge Marking
The SDK draws a visual ⚡️ JS badge on the canvas when a node is flagged as containing JavaScript. The host application is responsible for detection. For detail and examples, see Script Flagging Example.
See the full Workspace API Reference for all available methods including State Forcing and Synthetic Interaction.