A node is a Hakushi.Node: an id, a type, a place (x, y), a size or none, and data of yours. The canvas draws it with the :node slot of its type.

<.canvas id="schema" graph={@graph}>
  <:node type="table" :let={node}>
    <.table_node table={node.data} />
  </:node>
  <:node type="note" resizable :let={node}>
    <div class="note">{node.data.text}</div>
  </:node>
  <:node :let={node}>
    <div class="box">{node.id}</div>
  </:node>
</.canvas>

A :node slot without a type draws every other type. A canvas without a :node slot draws each node as a box with its label, a handle on each side.

Drawn on the server, once

The slot renders on the server, as any HEEx: your components, your icons, Tailwind classes, a form. The canvas sends the nodes as a LiveView stream, so a node is sent when it is new and when it changes, and not otherwise: a canvas of thousands of nodes sends the node that moved.

A node draws from the node, not from the LiveView's other assigns. A change of its data (an :update change, or a new graph) draws it again; a change of an assign that the slot reads does not.

Size

A node is as large as what the slot draws, unless it has a width and a height, which the canvas gives it. People resize the nodes of a resizable slot by their corners, when they are selected; the change is a :move with the new size.

Moving

People drag a node by any part of it but its fields, buttons, links and the elements with data-hakushi-nodrag. To drag a node by one part only (its title bar), mark that part:

<header data-hakushi-drag>{node.data.title}</header>

A locked node does not move, resize or go: in the browser, and on the server, which refuses a change that would.

Handles

A handle is where edges start (kind={:source}), end (kind={:target}), or both (the default). It sits on a side of its nearest positioned ancestor: the node, or an element of it with position: relative, such as a row.

<div :for={column <- node.data.columns} class="relative">
  <.handle id={column.name} side={:left} type={column.type} accepts={[column.type]} />
  {column.name}
  <.handle id={column.name} side={:right} type={column.type} accepts={[column.type]} />
</div>

The edges name the handles by id (source_handle, target_handle). A node may have several handles with one id, on different sides: an edge meets the one nearest its other end, so a line takes the short way whichever side the other node is on.

An edge with no handle id meets the node's first handle of its kind, or the middle of the side that faces the other node when the node has none.

type, accepts and max are the handle's rules: see Edges.

Groups

A :node slot with group draws nodes that hold others, as frames do: see Groups.

The label

Screen readers name each node by node_label, a function of the node: the default is its data's label or name, or its id.

<.canvas id="schema" graph={@graph} node_label={&"Table #{&1.data.name}"}>