Core ConceptsWorkspace & Content Nodes

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
  • Handles wheel/keyboard shortcuts
  • Maintains active workspace states (panning, resizing, dragging, reparenting, spacing adjustment, marquee selection)
  • 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
  },
})

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')  // parentId

Manipulating 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 position

Preview 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’s fully scoped and never pollutes the host application:

// Inject inline CSS — :root, html, body selectors are rewritten to :host
ws.injectCSS(`
  .card { padding: 24px; border-radius: 12px; }
  .btn { background: #6366f1; color: white; }
`)
 
// Load an external stylesheet
await ws.injectCSSLink('https://cdn.example.com/tailwind.css')

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 — the SDK never auto-detects scripts.

// Flag a node as having interactive JS behavior
ws.markNodeHasJS('interactive-widget')
 
// Check and remove
if (ws.hasJSMark('interactive-widget')) {
  ws.unmarkNodeHasJS('interactive-widget')
}

See the full Workspace API Reference for all available methods including State Forcing and Synthetic Interaction.