Learn / FAQ & troubleshooting

FAQ & troubleshooting

Every entry here is a wall someone actually hit — most of them us, while verifying the tutorials. Symptoms first, causes second.

The canvas is blank

The container has no resolved height. The canvas fills 100% of its parent — and 100% of zero is an empty page. Give the wrapper a real height (height: 100vh, a flex row with flex: 1, a sized grid cell). This is the #1 first-run issue in every framework.

My custom node renders as a plain rectangle

In plain JS and React the spec must carry custom: true — that is what routes the node through the HTML layer where your renderer/component mounts. Vue slots (#node-<type>) and Angular templates (ng-template grafloriaNode="type") set it for you; nodeTypes and registerNodeType do not.

instance.undo is not a function

Correct — history lives on the engine: await instance.getEngine().undo(). Angular's canvas component mirrors undo()/redo() directly; nothing else does. Keyboard ⌘Z/Ctrl+Z works everywhere with zero wiring.

I can't see any ports

Ports show on hover by default. Show them permanently with interaction: { portVisibility: 'always' } at creation (a prop in React/Vue) or engine.setInteractionConfig({ portVisibility: 'always' }) at runtime.

My connection validator fires on a completely different page

The validator registry is process-global, not per-canvas. Keep the disposer returned by registerConnectionValidator and call it on unmount (plus clearConnectionValidators() if you want a clean slate). React StrictMode double-invokes effects — the cleanup protects you there too.

The layout prop doesn't re-run when my data changes

Deliberate. layout re-runs when the prop value changes, never when node data changes — otherwise every user drag would be fought by a relayout. Re-run on demand: instance.getEngine().layout('elk') (then renderNow()), or applyLayout() in Angular/Vue.

Re-applying edited data keeps stale nodes

setNodes/loadText reconcile: ids that still exist keep their live objects. When you re-apply externally-edited data for the same ids, clear first — api.setEdges([]); api.setNodes([]); then apply.

Export throws "the canvas has not rendered yet"

Export needs a painted canvas — call it after the first paint (in practice: after onInit/ngAfterViewInit plus a frame, or behind a user action). SVG returns a raw string; PNG/PDF return data: URLs.

ESM, require(), Jest

  • Since engine 0.3.x / renderer 0.4.x / element 0.4.x the packages are pure ESM. Bundlers need no config; Node ≥ 20.19 can even require() them.
  • Jest with CJS transforms: allow the packages through (transformIgnorePatterns: ['/node_modules/(?!@grafloria)']) or run Jest/Vitest in ESM mode. Test failures like "Unexpected token export" are runner config, not library bugs.
  • Importing @grafloria/element registers the <grafloria-flow> element as a side effect — safe in Node and workers (it no-ops without customElements).

Isn't 9 MB unpacked huge?

npm's unpacked-size stat counts uncompressed ESM source plus full TypeScript declarations — none of it ships. What your users download, measured worst-case: engine 228 KB gz, react/vue 334, angular 395, element 451 — with ELK layout in a lazy chunk (432 KB gz) that only downloads if invoked. Every package README carries a two-minute reproduction script.

The element ignores my nodes attribute

Malformed JSON — the element warns ([grafloria-flow] ignoring malformed JSON attribute) and keeps running. Single-quote the attribute, double-quote the JSON inside, and remember geometry is "position":{"x":0,"y":0} — never top-level x/y. After mount, prefer properties: el.nodes = [...].

Something not covered here? The 111 demos are executable answers — find the closest one and read its source. Or open an issue on GitHub.