Functions
Import these from @grafloria/renderer.
Functions
activeRegistryScope
The active scope, or null. Hosts should not need this; the registries do.
tsfunction activeRegistryScope(): RegistryScope | null
assertEngineCompatible
Reject a plugin built against an incompatible host API.
tsfunction assertEngineCompatible(
manifest: ExtensionManifest<CapabilityName>,
apiVersion: string
): void
clearConnectionValidators
tsfunction clearConnectionValidators(): void
clearLinkPipeline
Drop every registration (tests, host teardown). Built-ins are re-seeded.
tsfunction clearLinkPipeline(): void
clearTools
tsfunction clearTools(): void
connectionValidatorCount
How many validators are live (tests assert this returns to 0 after dispose).
tsfunction connectionValidatorCount(): number
createCounterScaledPortal
A world-space portal that must also stay a FIXED SCREEN SIZE (a resize handle, a badge that should not grow when you zoom in). It lives in world space but counter-scales by 1/zoom on every camera change.
tsfunction createCounterScaledPortal(
htmlLayer: HTMLElement,
viewport: ViewportController,
options: { x?: number; y?: number; className?: string; style?: string } = {}
): ViewportPortal
createDiagramApi
tsfunction createDiagramApi(instance: DiagramInstance): DiagramApi
createExtensionHost
Convenience: a host bound to a live createDiagram() instance.
tsfunction createExtensionHost(options: ExtensionHostOptions): ExtensionHost
createPortal
Mount a SCREEN-SPACE portal — a floating panel pinned to the viewport.
tsconst panel = createPortal(diagram.container, { placement: 'top-right' });
panel.element.appendChild(myToolbar);
// later
panel.dispose();
tsfunction createPortal(root: HTMLElement, options: PortalOptions = {}): Portal
createViewportPortal
Mount a WORLD-SPACE portal — content that pans and zooms WITH the canvas.
x/y are WORLD coordinates. The element is placed inside the camera-
transformed HTML layer, so it needs no per-frame updates: the single transform
on the layer moves it. That is why this takes the layer, not the viewport, and
why there is no onChange subscription to leak.
tsconst note = createViewportPortal(htmlLayer, { x: 320, y: 180 });
note.element.textContent = 'sticky note';
tsfunction createViewportPortal(
htmlLayer: HTMLElement,
options: { x?: number; y?: number; className?: string; style?: string } = {}
): ViewportPortal
defineNodeComponent
tsfunction defineNodeComponent<D = Record<string, unknown>>(
component: NodeComponent<D>
): NodeComponent<D>
ensureScreenLayer
The screen-space layer, created lazily on the diagram root. Idempotent: many portals share one layer.
tsfunction ensureScreenLayer(root: HTMLElement): HTMLElement
getAnchor
tsfunction getAnchor(name: string): AnchorFn | undefined
getConnectionPoint
tsfunction getConnectionPoint(name: string): ConnectionPointFn | undefined
getConnector
tsfunction getConnector(name: string): ConnectorFn | undefined
getLinkPipelineVersion
tsfunction getLinkPipelineVersion(): number
getTool
tsfunction getTool(id: string): CanvasTool | undefined
hasAnchor
tsfunction hasAnchor(name: string): boolean
hasConnectionPoint
tsfunction hasConnectionPoint(name: string): boolean
hasConnector
tsfunction hasConnector(name: string): boolean
hasTool
tsfunction hasTool(id: string): boolean
isValidConnection
Evaluate every registered validator. This is the public isValidConnection
hook. With no validators registered it is { valid: true } — i.e. free.
tsfunction isValidConnection(candidate: ConnectionCandidate): ConnectionValidity
listAnchors
tsfunction listAnchors(): string[]
listConnectionPoints
tsfunction listConnectionPoints(): string[]
listConnectors
tsfunction listConnectors(): string[]
listTools
tsfunction listTools(): string[]
loadCanvasPlugins
tsfunction loadCanvasPlugins(): Promise<typeof import('./components/attach')>
mountNodeComponents
Wire a component registry into a live diagram.
Returns a disposer that unmounts every component and disconnects every observer — the leak rule; a stranded ResizeObserver keeps its target's whole subtree alive.
tsfunction mountNodeComponents(
instance: DiagramInstance,
registry: NodeComponentRegistry
): Disposer
nodeComponentOptions
The options you hand to createDiagram() to use a component registry from the
very first paint (preferred over mountNodeComponents on a running instance,
because the first render then already has the components).
tsconst diagram = createDiagram(el, {
nodes, edges,
...nodeComponentOptions(registry, () => diagram),
});
tsfunction nodeComponentOptions(
registry: NodeComponentRegistry,
getInstance: () => DiagramInstance
): {
renderCustomNode: (node: NodeModel, element: HTMLElement) => void;
removeCustomNode: (id: string, element: HTMLElement) => void;
}
notifyLinkPipelineChanged
Internal: let a PER-DIAGRAM registry participate in the version/notify protocol. A scoped registration must invalidate cached VNodes for exactly the reason a global one must — the definition is baked into the cache. Notifying every renderer (not just the contributing one) is deliberate: over-invalidation costs a repaint, under-invalidation shows a stale picture.
tsfunction notifyLinkPipelineChanged(): void
once
Wrap a function so it can only ever run once.
tsfunction once(fn: Disposer): Disposer
onLinkPipelineChange
tsfunction onLinkPipelineChange(listener: () => void): Disposer
registerAnchor
Register a named anchor. Address it per link via
link.metadata.sourceAnchor / link.metadata.targetAnchor.
tsfunction registerAnchor(name: string, fn: AnchorFn): Disposer
Returns a disposer that RESTORES whatever was registered under this name before (so overriding a built-in is reversible).
registerConnectionPoint
Register a named connection-point strategy. Address it per link via
link.metadata.connectionPoint, or set it as the diagram-wide default with
the renderer's connectionPoint config.
The built-in 'smart' strategy is the draw.io-style floating attachment that
used to be reachable ONLY through the boolean smartConnectionPoints config
flag. That flag still works (it now selects this strategy by name), so nothing
that relied on it changes behaviour.
tsfunction registerConnectionPoint(name: string, fn: ConnectionPointFn): Disposer
registerConnectionValidator
Register a connection validator. ALL registered validators must pass for a connection to be offered — veto power, not voting power, because a rule that can be outvoted is not a rule.
tsfunction registerConnectionValidator(validator: ConnectionValidator): Disposer
registerConnector
Register a named connector. Address it per link via link.connector
.
The four built-in names — straight / rounded / smooth / bezier — are
NOT in this map: they are the renderer's own internal branches and stay
exactly as they were. This registry is consulted only for names the renderer
does not recognise, which is precisely the case that used to be dropped.
tsfunction registerConnector(name: string, fn: ConnectorFn): Disposer
registerTool
Register (or replace) a canvas tool. Returns a disposer that RESTORES the
previous tool of the same id — so overriding 'node-drag' and then unloading
the extension gives the original back rather than leaving a hole.
On every pointerdown, {@link resolveTool} asks EVERY registered tool
hitTest(event, hit) and the gesture goes to the claiming tool with the
HIGHEST priority (default 1; the built-in ladder is effectively 0). Ties
resolve by registration order — first registered wins — and that is a
fallback, NOT a contract: tools composed from different waves and extensions
register in an order nobody designed. A tool that can ever be active at the
same time as another MUST state an explicit priority.
Choosing one: a POINT-SPECIFIC claim (hitTest inspects the point — "is there
ink under the pointer?") should outrank a POINT-AGNOSTIC mode claim (hitTest
is just "am I active?"), because the specific tool only fires where it means
to and would otherwise be starved by the broad one everywhere it matters. The whiteboard tools are the worked example: draw/rectangle/eraser sit at
WHITEBOARD_MODE_TOOL_PRIORITY (1), the ink-hit-only StrokeEditTool at
WHITEBOARD_INK_TOOL_PRIORITY (2) — see whiteboard-tools.ts.
tsfunction registerTool(tool: CanvasTool): Disposer
resolveTool
The tool (if any) that claims this gesture, highest priority first (ties: first registered — see the arbitration contract on {@link registerTool}).
The DomEventBinder calls this FIRST on pointerdown. undefined means "no
registered tool wants it" — and then the built-in ladder runs untouched, which
is why adding this seam changed no existing behaviour.
tsfunction resolveTool(
event: ToolPointerEvent,
hit: ToolHitContext
): CanvasTool | undefined
runInRegistryScope
Run fn with scope active, restoring whatever was active before.
An EMPTY scope is treated as no scope at all. That is not merely an optimisation: it keeps the hot read path for a diagram that contributed nothing — which is every existing embedder and all 104 demos — at exactly its previous cost, one null check with no Map lookup behind it.
tsfunction runInRegistryScope<T>(scope: RegistryScope | null | undefined, fn: () => T): T
satisfies
A deliberately small semver range matcher. Supports:
- / x any 1.2.3 exact ^1.2.3 >=1.2.3 <2.0.0 (and ^0.2.3 → >=0.2.3 <0.3.0) ~1.2.3 >=1.2.3 <1.3.0
=1.2.3, >1.2.3, <=1.2.3, <1.2.3 1.x / 1.2.x " || " union of any of the above
tsfunction satisfies(version: string, range: string): boolean
scopedTable
The active scope's table for name, or undefined when there is no scope or it
has no such table. THE READ HELPER every registry uses — deliberately one
function so all of them shadow identically.
tsfunction scopedTable<V>(name: string): Map<string, V> | undefined
sideTowards
The side of rect you would leave from to head towards towards — the same
dominant-axis rule the built-in smart strategy uses.
tsfunction sideTowards(rect: ExtRect, towards: ExtPoint): ExtSide
snapshotRestore
Build a disposer that puts a registry key back the way it was.
tsfunction snapshotRestore<T>(
previous: T | undefined,
restore: (value: T) => void,
remove: () => void
): Disposer
Parameters
previous: the value read out of the registry BEFORE the write (orundefinedwhen the key did not exist)restore: re-register the previous valueremove: delete the key (used when there was no previous value)
validateManifest
Throw with a precise reason. A malformed manifest must never half-load.
tsfunction validateManifest(manifest: ExtensionManifest<CapabilityName>): void
Was this page helpful?