# CustomNodeHostSource

Import it from `@grafloria/renderer`.

Where a canvas's custom nodes are painted — the one thing the pipeline cannot know.

Only `getNodes` and `getHost` are required. The optional members exist for a canvas
that culls hosts or runs async painters (the JS canvas); a framework canvas whose hosts
are always in the document leaves them out.

```ts
interface CustomNodeHostSource
```

**Members**

- `getNodes(): readonly NodeModel[]` — Every node, in MODEL order — captures come out in this order, so exports are stable.
- `getHost(nodeId: string): HTMLElement | null | undefined` — The element this node's content is painted into, if it has one right now.
- `isExportable?(node: NodeModel): boolean` — Whether this node's widget can be in an export at all. Default: `metadata.useHTMLLayer` is set. (The JS canvas also refuses an explicitly frozen node, which the render pass of the same export omits.)
- `bounds?(node: NodeModel): Rectangle` — The world rect the capture is placed at. Default: `node.position` + `node.size`.
- `materialize?(nodes: readonly NodeModel[]): () => void` — Make sure the hosts of these (in-scope) nodes exist and are laid out, and return the undo. Called before every read. Default: nothing to mount.
- `pendingPaint?(nodeId: string): Promise<void> | undefined` — A promise for a host whose painter has not finished — the async path waits for it.
- `paintWarning?(nodeId: string, waited: boolean, timeoutMs: number): string | undefined` — A per-node caveat (a painter that threw, or is still painting), led into the capture's warning.
- `pin?(nodeIds: readonly string[]): () => void` — Hold these hosts in the document while an async capture waits; returns the release.
