[]previous and next page

Solid

Use CanvasFlow in Solid with components and primitives that return signals.

canvasflow/solid gives you <CanvasFlow>, <CanvasFlowCanvas> and primitives that follow the editor as signals. It needs solid-js 1.8 or newer.

A board in one component#

Board.tsxtsx
import { CanvasFlow } from 'canvasflow/solid'

export function Board() {
  return <CanvasFlow persistenceKey="board" style={{ height: '100vh' }} />
}

The component mounts the canvas and the default UI into a div that fills its parent. Give it a height, because an editor needs room.

Props#

Everything createCanvasFlow accepts is a prop (persistenceKey, dir, readonly, themes, plugins, shapes, tools, storage…), plus:

Prop What it does
ui false for no default UI, or an object that configures it (see Customizing the UI).
onMount Called once with (editor, app, ui). Return a function and it runs as a cleanup.
class, style Passed to the element.
children Rendered inside the canvas once it is ready.

Options are read once, when the canvas mounts. dir and readonly follow later changes.

Board.tsxtsx
import { CanvasFlow } from 'canvasflow/solid'

export function Board() {
  return <CanvasFlow persistenceKey="board" onMount={(editor) => editor.zoomToFit()} />
}

Your own panels#

Children render inside the canvas, so the primitives work in them. Each returns a signal.

SelectionCount.tsxtsx
import { useSelectedIds, useValue } from 'canvasflow/solid'

export function SelectionCount() {
  const selected = useSelectedIds()
  const shapes = useValue((editor) => editor.getCurrentPageShapes().length)
  return (
    <div class="badge">
      {selected().length} selected of {shapes()}
    </div>
  )
}
Board.tsxtsx
import { CanvasFlow } from 'canvasflow/solid'
import { SelectionCount } from './SelectionCount'

export function Board() {
  return (
    <CanvasFlow style={{ height: '100vh' }}>
      <SelectionCount />
    </CanvasFlow>
  )
}

useValue(selector, isEqual?) re-reads on every change and updates the signal only when the result differs. It stops with its owner. useEditorEvent('tool', handler) listens to an event until the owner is disposed. useEditor() returns the editor. useTool, useCanUndo, useCanRedo, useCamera, useTheme and useDirection are ready-made.

Headless#

<CanvasFlowCanvas> is the canvas without the default UI. It never references it, so your bundle does not carry the toolbar.

Toolbar.tsxtsx
import { For } from 'solid-js'
import { useEditor, useTool } from 'canvasflow/solid'

export function Toolbar() {
  const editor = useEditor()
  const tool = useTool()
  return (
    <div role="toolbar">
      <For each={['select', 'draw', 'rectangle', 'arrow', 'text']}>
        {(id) => (
          <button aria-pressed={tool() === id} onClick={() => editor.setTool(id)}>
            {id}
          </button>
        )}
      </For>
    </div>
  )
}

See Building your own UI for the full tour.

Solid Start and server rendering#

On the server the component renders nothing, and the canvas appears when the page runs. It is safe to import anywhere.