v0.3.2 (2026-10-24)
Bug Fixes
- A client node of unknown type no longer crashes the flow.
lf:node_changeadd and replace events ranString.to_existing_atom/1on the type the browser sent, so a type that was not already an atom raised and took the LiveView down. Such nodes now draw as:default, as unknown edge types already fall back to:bezier.
v0.3.1 (2026-10-24)
Bug Fixes
- Edge label editing survives server updates. A re-render of the flow while a label was being
edited (for example the selection update that follows the double click) made morphdom remove
the focused input. Chrome fires
blurduring that removal and the editor's blur handler detached the input first, so the patch aborted withNotFoundError: Failed to execute 'removeChild' on 'Node'and the editor closed. The label wrapper is nowphx-update="ignore"while editing: server changes to that label are applied by the next update after Enter, Escape or a click elsewhere ends the edit.
Showcase
flow_example/uses esbuild 0.25.12 and runs the dev watcher with--watch-delay=200, so it no longer fails withCould not resolve "phoenix-colocated/flow_example"while LiveView rewrites the colocated hook manifests after a recompile.
v0.3.0 (2026-10-23)
First release since v0.2.3: sync with the latest xyflow releases (React Flow 12.10 → 12.12,
@xyflow/system 0.0.74 → 0.0.83), node resizing, server-side viewport culling, sub-flows,
server-side layouts, a client refactor, new guides and a showcase app (flow_example/) with every
React Flow example, including the Pro ones.
Upgrading from 0.2.x
- ELK is no longer bundled: if you use
lf:layout_data/lf:auto-layout, runmix live_flow.gen.elk(see "Changes" below). - The hook reads its configuration from
data-config. If you styled or queried the olddata-*attributes of.lf-container, update those selectors. - Background classes are now
.lf-background-pattern.{dots,lines,cross}. connection_modedefaults to:strict.- Library CSS lives in
@layer components, so app rules override it without!important; drop!importantoverrides you added for LiveFlow.
New Features (React Flow examples parity)
Added while porting every React Flow example (including the Pro ones) to the showcase app:
- Sub-flows.
parent_idis now fully supported: child positions are relative to the parent, children move with it,extent: :parent(or a relative{{x1, y1}, {x2, y2}}) keeps them inside, parents render before their children, hidden parents hide their subtree and deleting a parent deletes its children.type: :groupnodes get a see-through default style. NewState.absolute_position/2,absolute_nodes/1,children/2,descendant_ids/2,depth/2andset_parent/4(re-parents keeping the absolute position). - Graph helpers (xyflow utils):
State.incomers/2,outgoers/2,connected_edges/2,node_rect/2andintersecting_nodes/3. - JS API:
element.liveflowon the flow container (React Flow'suseReactFlow()):screenToFlowPosition,flowToScreenPosition,getViewport,setViewport,fitView,zoomIn/zoomOut/zoomTo,getNodes,getNode,getIntersectingNodes,getSelection,setSelection. The container dispatcheslf:readywhen mounted. - Drag & drop from outside: dropping an element with
application/liveflowdrag data pusheslf:dropwith the data and the flow position (snapped to the grid when enabled). - Opt-in events:
context_menu(lf:node_context_menu,lf:edge_context_menu,lf:pane_context_menu),pane_click_events(lf:pane_click, not fired after a connection ends on the pane) andconnection_events(lf:connect_start). lf:connect_cancelnow carries the source node/handle, the release position in flow coordinates and areason("pane","invalid","abort") — e.g. to add a node where a connection is dropped.connect_on_click(defaulttrue): click a handle, then another one, to connect (touch).multi_connection: dragging from a selected node connects every selected node.connection_line_style/connection_line_classto customize the connection line.LiveFlow.Components.NodeToolbar(React Flow's<NodeToolbar />).- Server-side layouts in
LiveFlow.Layout:tree/2(layered, top-bottom or left-right),force/2(force-directed),resolve_collisions/2andapply_positions/2. LiveFlow.animate_positions/5animates nodes (and their edges/children) to new positions on the client, which then pushes the final positions back.- Client-side ELK layout uses node handles as ports (fewer edge crossings).
LiveFlow.Export.to_svg/2: SVG of a flow rendered in pure Elixir (no browser).LiveFlow.Paths.Geometry: public edge geometry (handle points, floating points, edge paths).- Custom edge renderers also receive
source_position,target_position,path_typeandpath_options_json. - Handle
offset:Handle.source(:right, offset: 0.25)places a handle along its side and edges attach exactly there (server, client and export) — several handles per side. LiveFlow.animate_positions/5acceptsfit_view: true(fits the view to the final positions).- Every viewport, layout and export event pushed to the client (
lf:fit_view,lf:zoom_to,lf:zoom_in,lf:zoom_out,lf:pan_to,lf:set_viewport,lf:layout_data,lf:tree_layout_data,lf:animate_positions,lf:export_svg,lf:export_png) accepts aflow_id, so only that flow reacts when several share a LiveView;lf:layout_dataaccepts any ELK option ("options") andprepare_tree_layout_data/2a"flow_id". - Export events take options:
JS.dispatch("lf:export-png", to: "#flow", detail: %{filename:, scale:, padding:}). Validation.max_connections/3acceptsside: :source | :target | :both.- The Flow component accepts a
classattribute. - Reconnect handles of
reconnectableedges are always grabbable (shown on hover/selection), andlf:delete_reconnect_edgecarries theendand the dropposition. - Library CSS rules live in
@layer componentsso app styles and Tailwind utilities override them without!important(theme variables stay in@layer base). LiveFlow.Export.to_svg/2draws markers in each edge's color and honorsstroke-width.minimap_node_color(a color orfn node -> color end, React Flow'snodeColor); minimap nodes carrydata-node-id/data-node-type.- A drag cancelled with Escape ends with position changes flagged
"aborted" => true, so apps can tell it apart from a drop. registerEdgePath(type, fn)(JS) lets the client redraw custom edge shapes while nodes are dragged; while dragging, only the built-in overlays (label, toolbar, buttons, or elements withdata-lf-label-anchor) are moved to the label point.:panelslot on the Flow component (React Flow's<Panel />):<:panel position={:top_right}>withtop_left,top_center,top_right,center_left,center_right,bottom_left,bottom_center,bottom_right; panels don't pan/zoom and are left out of exports.- Paste at the cursor:
lf:pastecarries the pointer position (flow coordinates) when the pointer is over the flow, andClipboard.paste/3acceptsposition:. Collaboration.apply_remote_change/2handles{:node_add, node},{:node_update, id, attrs},{:node_remove, id},{:edge_update, id, attrs}and{:edge_reconnect, params}; node changes go throughNodeChange(resize, add, replace) and remote selection changes are ignored.lf:connect_cancelincludestarget_node_id/target_handle_idwhen an invalid connection is dropped on a handle.helper_linesandcursorscan be toggled at runtime.element.liveflow.getNodes()also returnstype,selectable,draggableanddeletable.
New Features (xyflow parity)
- Auto-pan while dragging nodes, connecting and drawing a selection box close to the edge of the
viewport (
auto_pan_on_node_drag,auto_pan_on_connect,auto_pan_on_selection,auto_pan_speed). The selection box is anchored in flow coordinates so it follows the viewport (xyflow 12.11.0). - Panning: middle mouse button panning from anywhere, including over nodes and selection boxes;
pan_activation_key("Space"by default,"Control"supported);pan_on_dragaccepts a list of mouse buttons;pan_on_scroll(+pan_on_scroll_mode,pan_on_scroll_speed);zoom_on_pinch(trackpad) andzoom_on_double_click. - Selection:
selection_on_drag,selection_mode(:partial/:full), configurableselection_keyandmulti_selection_key. Touch prefers panning over drag selection. - Keyboard:
delete_key_codeis now honored and, like every key option, accepts combinations of any length ("Meta+Shift+Backspace"). Selected nodes move with the arrow keys (Shift = 4x,disable_keyboard_a11yto opt out). Escape cancels drags, resizes, connections and selections. - Node extent:
node_extentconstrains both dragging and resizing. - Resizing: a
resizechange withresizing: trueis sent when a resize starts and it is always followed by a finalresizing: falsechange, also when the resize is cancelled (xyflow 12.12.0).NodeResizergainsmax_width,max_heightandkeep_aspect_ratio; sizes snap to the grid. - Smoothstep/step edges are a port of xyflow's current
getSmoothStepPath, including the newstep_positionoption,center_x/center_yand the rounded-bend fix (xyflow 12.10.2). The Elixir and JavaScript implementations produce identical paths. - Connections:
connection_line_type,connection_radius(snap to the closest handle) and a workingconnection_mode(:strictby default,:looseallows source-to-source). - Custom edge IDs:
Validation.Connection.validate_and_create/3accepts:edge_id, and the newState.reconnect_edge/3handleslf:reconnect_edge(with:replace_id/:edge_id). - Bounds:
State.bounds/2acceptsnodes:(unknown IDs are ignored instead of stretching the bounds to the origin) andinclude_hidden:. Fit view is computed on the server, so it also covers nodes culled by virtualization;send_update(Flow, fit_view: %{nodes: [...], padding: ...}). - Background: SVG pattern with xyflow's (fixed) offset math, kept in sync during client-side
pan/zoom. New
background_gap,background_size,background_offset,background_line_width;:crossis now an actual cross pattern. - Minimap: real container size for the viewport rectangle, hidden nodes ignored (shows the viewport
when every node is hidden),
minimap_pannable,minimap_zoomable. Viewport.screen_to_flow/4acceptssnap_to_grid:/snap_grid:; newViewport.snap/2.LiveFlow.Node.new/4accepts:width,:height,:hiddenand:drag_handle(CSS selector the node can be dragged from). The serializer round-tripshidden,resizable,drag_handleand resizable sizes.node_drag_threshold,z_index_mode,elevate_nodes_on_select,elevate_edges_on_select.- New JS exports:
getEdgePath,getSmoothStepPath,getStepPath,getBezierPath,getStraightPath,EdgeUpdater,calcAutoPan,parseKeys,matchesKeys,exportSVG,exportPNG,buildExportSvg.
Node Resizing
- Node Resizing. New
LiveFlow.Components.NodeResizercomponent adds interactive resize handles to custom nodes. Users can drag corner and edge handles to resize nodes, with real-time edge path updates during resize. Supports configurable minimum dimensions and 8 resize handle positions (N, S, E, W, NE, NW, SE, SW).- New
NodeResizer.node_resizer/1function component — drop it inside any custom node template - New
resizable: trueoption onLiveFlow.Node.new/4to mark nodes as resizable - New
resizechange type inNodeChangefor server-side dimension updates - Client-side resize with zero-latency feedback (same pattern as node drag)
- Touch/mobile support with larger resize handles on coarse pointer devices
- CSS variables:
--lf-resizer-color,--lf-resizer-sizefor customization
- New
Server-Side Viewport Culling (Virtualization)
- Server-Side Viewport Culling (Virtualization). Pure Elixir server-side viewport culling dramatically reduces DOM size for large flows. Only nodes and edges visible in the current viewport are rendered, enabling smooth interaction with 10,000+ node flows.
- New
LiveFlow.Viewport.Cullingmodule — pure Elixir culling logic with zero JS dependencies - Opt-in via
virtualization: truein flow opts - 200px flow-space margin pre-renders nodes just outside the viewport for smooth panning
- Unmeasured nodes use estimated size (200x100) for visibility — render near viewport, get measured by JS
- Edge culling: edges with at least one visible endpoint are kept
max_visiblecap (default 500) prevents browser crash on extreme zoom-out (e.g., fit-view with 10K nodes)- Nodes closest to viewport center are prioritized when cap is exceeded
- Minimap always renders all nodes regardless of culling
- Container dimensions sent from JS via
lf:container_resizeevent (only change: ~15 lines of JS)
- New
Performance:
| Node Count | DOM Without Culling | DOM With Culling | Reduction |
|---|---|---|---|
| 500 | 500 | ~108 | 78% |
| 1,000 | 1,000 | ~108 | 89% |
| 10,000 | 10,000 (crash) | ~180 | 98% |
Documentation
- New guides: Edges, Interaction, Events & JavaScript API, Sub-flows & Grouping, Layout, Built-in Components and Saving, Import & Export. Getting Started, Custom Nodes, Collaboration and Themes were updated (the Themes guide listed CSS variables that don't exist; the README Quick Start called functions that don't exist).
Bug Fixes
- Duplicate edges were not detected when the existing edge had no handle ids (
nil) and the client sent its default ids ("source"/"target"):State.edge_exists?/5,Validation.no_duplicate_edges/2andmax_connections/3now compare effective handle ids. - A node removed and added back with the same size never reported its dimensions again.
- The vendored ELK bundle triggered esbuild's
equals-negative-zerowarning on every build of apps using LiveFlow (b==-0.?0:breplaced by the equivalentb===0?0:b). - SVG/PNG export now matches the screen. Nodes and edge labels are cloned with their computed
styles inlined, so Tailwind classes, theme variables, icons (CSS masks) and markers render as in the
browser. The PNG rasterizes the same SVG instead of a text-only approximation (the previous pure-SVG
rendering remains as a fallback if the browser taints the canvas). Animated edges, which disappeared
because their theme variable wasn't exported, are drawn. Editor chrome (delete buttons, resizers, edge
toolbars) is left out; add
data-lf-export-ignoreto exclude other elements. The export includes the background pattern and the full edge extent. Web fonts and external images are not embedded. - Default edge markers used the page text color (
currentColorinside<defs>); they now follow the edge stroke like xyflow. - Options and per-node flags set to
false(pan_on_drag,nodes_draggable, nodedraggable, handleconnectable, ...) had no effect: HEEx dropsfalseattributes. The configuration is now sent as JSON (data-config). - Custom nodes rendered by function components, custom edge labels and edge toolbars never re-rendered
after the first render (
__changed__: %{}). - Step, smoothstep, straight and floating edges were redrawn as bezier curves while dragging, resizing or receiving remote drags; icons inside edge buttons were overwritten with the edge path.
- The selection box was never drawn (its events went to the parent LiveView).
- Edges pointing to a missing node crashed the Flow component; edges to hidden nodes are skipped.
fitViewcounted handles and buttons as nodes at the origin.- Typing Backspace/Ctrl+A/Ctrl+Z in an input inside a node triggered flow shortcuts; with several flows on a page, all of them reacted to the same keys.
- Touch: connections never completed, inputs inside nodes couldn't be focused, no
touchcancel. - Every click on a node sent position changes;
lf:node_clickfired after drags and connections. - Releasing outside the canvas or pressing Escape while reconnecting deleted the edge.
- The local selection went stale after deletes, undo or paste.
- Zoom buttons used the (possibly stale) server zoom; viewport animations could overlap.
- Resize could leave node measuring disabled after a click on a resize handle.
destroyed()leaked touch listeners, timers and the global export handle.- Node stacking ignored the render order: a selected or recently dragged node could stay below
the others. The CSS
z-indexnow follows the sort order (parents below children,z_index,elevate_nodes_on_select). State.delete_selected/1(Backspace/Delete) removed nodes and edges markeddeletable: false.
Changes
ELK is no longer bundled. elkjs added about 1.6 MB (minified) to every app's
app.js, even without auto layout. Apps that uself:layout_data/lf:auto-layoutrunmix live_flow.gen.elk, which vendors elkjs inassets/vendor/and registers it with the newregisterElk(ELK)export (an elkjs instance, e.g. a web-worker one, works too). Without it the hook logs how to install it. The Elixir layouts (Layout.tree/2,force/2) don't need ELK.The hook reads its configuration from
data-config; the olddata-*attributes on.lf-containerare gone. The container is focusable (tabindex="0").Client-only UI (selection box, connection preview, shortcuts panel) lives in a
phx-update="ignore"interaction layer. The unreachablelf:connect_*/lf:selection_box_*handlers were removed from the Flow component.connection_modedefaults to:strict(what the client already enforced).lf:container_resizeis always sent to the Flow component (used by the minimap).Hot paths (wheel, drag, pinch) use a cached container rect instead of forcing layout.
Background CSS classes changed from
.lf-background-{dots,lines,cross}to.lf-background-pattern.{dots,lines,cross}.
v0.2.3 (2026-02-20)
Improvements
- Clean JavaScript imports.
package.jsonnow points directly to source files instead of requiring a build step. Phoenix 1.8+ apps withNODE_PATHconfigured can import with clean paths:import { LiveFlowHook, FileImportHook, setupDownloadHandler } from "live_flow" - Export
FileImportHookandsetupDownloadHandlerfrom main entry. No longer need a separate import fromhooks/utility_hooks.js.
v0.2.2 (2026-02-20)
Performance — Realtime Collaboration
broadcast_frominstead ofbroadcast. PubSub messages now skip the sender process entirely, eliminating self-filtering overhead and reducing message processing by ~N per broadcast (where N = number of connected users).- Live drag broadcasting. Intermediate node positions are broadcast to remote users every 100ms during drag (via
lf:drag_move), so other users see live movement instead of position jumps at drag end. Uses a lightweight DOM-only update path — no flow state change or LiveView re-render on receivers. - Smooth cursor interpolation. Remote cursors now use lerp (linear interpolation) animation at 60fps instead of jumping between positions, providing fluid cursor movement.
- Reduced cursor throttle. Local cursor broadcast interval reduced from 100ms to 50ms for more responsive cursor tracking.
- Server-side cursor throttle. Added Process dictionary-based throttle in the example LiveView to prevent PubSub flooding with many concurrent users.
- Client-side remote edge updates. When receiving remote drag positions, edge SVG paths are recalculated client-side to stay visually connected to moving nodes.
v0.2.1 (2026-02-20)
Bug Fixes
- Fix auto-layout node overlaps. ELK and tree layout now measure actual DOM node dimensions (
offsetWidth/offsetHeight) before computing positions, instead of relying on server-side dimensions that may still be at default fallback values (150x50). This prevents nodes from overlapping when their rendered size exceeds the defaults.
v0.2.0 (2026-02-20)
Performance
- Fully client-side drag with instant edge updates. Node positions AND edge SVG paths are now recalculated client-side during drag, eliminating all latency. Only the final position is sent to the server when the drag ends. After each LiveView DOM patch, client-side positions and viewport transforms are re-applied so nothing "jumps back" to stale server state. No changes required in your LiveView event handlers.
- Viewport stability. The client-side viewport transform is re-applied after every server DOM patch, preventing zoom/pan jumps when clicking nodes or during selection changes.
Internal
- Edge
<g>elements now includedata-source,data-target,data-source-handle, anddata-target-handleattributes for client-side path recalculation during drag.
v0.1.0 (2025-xx-xx)
Initial Release
- Flow diagram LiveComponent with pan, zoom, and viewport controls
- Node system: drag, select, resize, custom node types (function components + LiveComponents)
- Edge system: bezier, straight, step, smoothstep paths with animated edges and labels
- Handle system: source/target handles with connection validation
- Selection: click, shift-click, selection box (lasso)
- Undo/Redo history with configurable max entries
- Copy/Paste/Cut/Duplicate clipboard
- JSON serialization/deserialization
- Real-time collaboration via PubSub with cursor sharing
- Connection validation: composable validators (no_duplicate_edges, nodes_exist, no_cycles, types_compatible, max_connections)
- ELK auto-layout and tree layout algorithms
- Helper lines (alignment guides) during drag
- 36 built-in themes (12 hand-crafted + 24 auto-generated from daisyUI palettes)
- Tailwind v4 theme plugin
- SVG/PNG export
- Touch/mobile optimization (pinch-to-zoom, long-press selection)
- Keyboard shortcuts panel
- Edge label inline editing