Skip to content
D
Documentation

Ext — functions

reference
5 min readUpdated

Functions

Import these from @grafloria/renderer.

Functions

activeRegistryScope

The active scope, or null. Hosts should not need this; the registries do.

ts
function activeRegistryScope(): RegistryScope | null

assertEngineCompatible

Reject a plugin built against an incompatible host API.

ts
function assertEngineCompatible(
  manifest: ExtensionManifest<CapabilityName>,
  apiVersion: string
): void

clearConnectionValidators

ts
function clearConnectionValidators(): void

clearLinkPipeline

Drop every registration (tests, host teardown). Built-ins are re-seeded.

ts
function clearLinkPipeline(): void

clearTools

ts
function clearTools(): void

connectionValidatorCount

How many validators are live (tests assert this returns to 0 after dispose).

ts
function 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.

ts
function createCounterScaledPortal(
  htmlLayer: HTMLElement,
  viewport: ViewportController,
  options: { x?: number; y?: number; className?: string; style?: string } = {}
): ViewportPortal

createDiagramApi

ts
function createDiagramApi(instance: DiagramInstance): DiagramApi

createExtensionHost

Convenience: a host bound to a live createDiagram() instance.

ts
function createExtensionHost(options: ExtensionHostOptions): ExtensionHost

createPortal

Mount a SCREEN-SPACE portal — a floating panel pinned to the viewport.

ts
const panel = createPortal(diagram.container, { placement: 'top-right' });
panel.element.appendChild(myToolbar);
// later
panel.dispose();
ts
function 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.

ts
const note = createViewportPortal(htmlLayer, { x: 320, y: 180 });
note.element.textContent = 'sticky note';
ts
function createViewportPortal(
  htmlLayer: HTMLElement,
  options: { x?: number; y?: number; className?: string; style?: string } = {}
): ViewportPortal

defineNodeComponent

ts
function 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.

ts
function ensureScreenLayer(root: HTMLElement): HTMLElement

getAnchor

ts
function getAnchor(name: string): AnchorFn | undefined

getConnectionPoint

ts
function getConnectionPoint(name: string): ConnectionPointFn | undefined

getConnector

ts
function getConnector(name: string): ConnectorFn | undefined

getLinkPipelineVersion

ts
function getLinkPipelineVersion(): number

getTool

ts
function getTool(id: string): CanvasTool | undefined

hasAnchor

ts
function hasAnchor(name: string): boolean

hasConnectionPoint

ts
function hasConnectionPoint(name: string): boolean

hasConnector

ts
function hasConnector(name: string): boolean

hasTool

ts
function 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.

ts
function isValidConnection(candidate: ConnectionCandidate): ConnectionValidity

listAnchors

ts
function listAnchors(): string[]

listConnectionPoints

ts
function listConnectionPoints(): string[]

listConnectors

ts
function listConnectors(): string[]

listTools

ts
function listTools(): string[]

loadCanvasPlugins

ts
function 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.

ts
function 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).

ts
const diagram = createDiagram(el, {
  nodes, edges,
  ...nodeComponentOptions(registry, () => diagram),
});
ts
function 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.

ts
function notifyLinkPipelineChanged(): void

once

Wrap a function so it can only ever run once.

ts
function once(fn: Disposer): Disposer

onLinkPipelineChange

ts
function onLinkPipelineChange(listener: () => void): Disposer

registerAnchor

Register a named anchor. Address it per link via link.metadata.sourceAnchor / link.metadata.targetAnchor.

ts
function 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.

ts
function 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.

ts
function 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.

ts
function 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.

ts
function 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.

ts
function 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.

ts
function 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

ts
function 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.

ts
function 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.

ts
function sideTowards(rect: ExtRect, towards: ExtPoint): ExtSide

snapshotRestore

Build a disposer that puts a registry key back the way it was.

ts
function snapshotRestore<T>(
  previous: T | undefined,
  restore: (value: T) => void,
  remove: () => void
): Disposer

Parameters

  • previous: the value read out of the registry BEFORE the write (or undefined when the key did not exist)
  • restore: re-register the previous value
  • remove: delete the key (used when there was no previous value)

validateManifest

Throw with a precise reason. A malformed manifest must never half-load.

ts
function validateManifest(manifest: ExtensionManifest<CapabilityName>): void

Was this page helpful?