Most LivePanels application code uses a focused set of public modules.
Start with LivePanels.Canvas, render with the toolkit helpers, then reach for
graph, drawing, persistence, shared, or manual integration APIs only when that canvas
needs them.
flowchart TD
App["Application LiveView"]
Canvas["LivePanels.Canvas\nmounts the runtime"]
Toolkit["LivePanels.Toolkit.*\ncomponents and DOM attrs"]
Items["LivePanels.Canvas.Item\nitem lifecycle and placement"]
Data["LivePanels.Canvas.Item\nitem props"]
Selection["LivePanels.Canvas.Selection\nselection and bulk actions"]
Layout["LivePanels.Canvas.Layout\nplan item layouts"]
History["LivePanels.Canvas.History\nundo and redo"]
Clipboard["LivePanels.Canvas.Clipboard\ncopy and paste"]
Read["LivePanels.Canvas.Read\nread current projection"]
Graph["LivePanels.Graph\nports, connectors, graph layout"]
Drawing["LivePanels.Drawing\nvector items and elements"]
Shared["LivePanels.Canvas.Shared.*\nshared canvas coordination"]
Manual["LivePanels.Toolkit.Integration.*\nescape hatches"]
App --> Canvas
App --> Toolkit
App --> Items
App --> Data
App --> Selection
App --> Layout
App --> History
App --> Clipboard
App --> Read
App --> Graph
App --> Drawing
Canvas --> Shared
App --> ManualStart Here
Use LivePanels.Canvas in the LiveView that owns the canvas route:
defmodule MyAppWeb.BoardLive do
use MyAppWeb, :live_view
use LivePanels.Canvas,
pubsub: MyApp.PubSub
use LivePanels.Toolkit.Canvas
def render(assigns) do
~H"""
<.canvas_runtime {canvas_runtime_attrs(assigns)}>
<:item id="note-1" type="note" at={{120, 80}} size={{520, 320}}>
<article class="note-card">Write ordinary HEEx here.</article>
</:item>
</.canvas_runtime>
"""
end
enduse LivePanels.Canvas attaches the runtime. Add use LivePanels.Toolkit.Canvas,
Graph, or Drawing when the LiveView renders toolkit helper components or attrs.
Toolkit Imports
Toolkit modules are use presets. They do not change the state model.
| Toolkit | Use when |
|---|---|
LivePanels.Toolkit.Canvas | You need runtime helpers, item frame helpers, item controls, selection, layout, history, clipboard, and viewport JS. |
LivePanels.Toolkit.Graph | You need canvas controls plus graph ports, connectors, selection, and fit helpers. |
LivePanels.Toolkit.Drawing | You need canvas controls plus drawing modes, vector item controls, and point editing helpers. |
Most apps should use a toolkit preset instead of hand-writing protocol attrs.
Command Owners
Commands stay on the module that owns the vocabulary:
| Work | Module |
|---|---|
| Add, remove, present, place, move, resize, focus, attach, detach | LivePanels.Canvas.Item |
| Replace, merge, delete, or clear item props | LivePanels.Canvas.Item |
| Select, clear, align, distribute, z-order selected items | LivePanels.Canvas.Selection |
| Layout commands and reserved layout space | LivePanels.Canvas.Arrange |
| Layout planning contracts and adapters | LivePanels.Canvas.Layout |
| Undo and redo | LivePanels.Canvas.History |
| Copy and paste | LivePanels.Canvas.Clipboard |
| Store and navigate viewport anchors | LivePanels.Canvas.Viewport |
| Connect, disconnect, reconnect, fit, and lay out graph connectors | LivePanels.Graph |
| Create and edit vector items or drawing-owned elements | LivePanels.Drawing |
Every command module accepts either a canvas LiveView socket or a
LivePanels.Canvas.Transaction context:
socket =
LivePanels.Canvas.transaction!(socket, fn tx ->
tx
|> LivePanels.Canvas.Item.move_by!("panel-a", {24, 0})
|> LivePanels.Canvas.Item.merge_props!("panel-a", %{accent: "red"})
|> LivePanels.Graph.connect!(from: {"panel-a", "out"}, to: {"panel-b", "in"})
end)Use transactions when several changes should become one command history step.
Browser Controls
Application-rendered controls should use toolkit attrs:
<button {add_item_attrs(:note)}>New note</button>
<button {remove_item_attrs(context)}>Close</button>
<button {layout_attrs(:grid)}>Layout</button>
<button {undo_attrs()}>Undo</button>Those attrs emit the LivePanels protocol payloads expected by the runtime. They also keep your visible markup free to change.
Read Side
Use LivePanels.Canvas.Read when application code needs current runtime information:
items = LivePanels.Canvas.Read.items(socket)
selection = LivePanels.Canvas.Read.selection(socket)
last = LivePanels.Canvas.Read.last_command(socket)
trace = LivePanels.Canvas.Read.command_trace(socket)Use root helpers for runtime identity and subscriptions:
canvas_id = LivePanels.canvas_id(socket)
client_id = LivePanels.client_id(socket)
mode = LivePanels.mode(socket)
:ok = LivePanels.subscribe(socket, :events)Escape Hatches
Reach for these only when the normal path is not enough:
| Need | API |
|---|---|
Mount the canvas runtime without the use macro | LivePanels.Canvas.mount_runtime/4 |
| Add runtime items after mount | LivePanels.Canvas.Item.add/3 |
| Render runtime-created item bodies | id-less <:item :let={context}> slot |
| Authorize app-specific commands | LivePanels.Canvas.Policy |
| Persist layouts outside the default store | LivePanels.Canvas.LayoutStore |
| Refresh rendered item streams without mutating canvas state | LivePanels.Canvas.Render |
| Run shared canvases in a cluster | LivePanels.Canvas.Shared.Backend and LivePanels.Canvas.Shared.Adapter |
| Build manual protocol attrs or render loops | LivePanels.Toolkit.Integration.ProtocolAttrs, LivePanels.Toolkit.Integration.Render |
If an app can use a toolkit helper or owner module, prefer that over an escape hatch.