@dschz/solid-flow 0.3.0-next.3 → 0.3.0-next.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,100 +1,69 @@
1
1
  <p align="center">
2
- <img src="https://assets.solidjs.com/banner?project=solid-flow&type=Ecosystem&background=tiles" alt="@dschz/solid-flow banner" />
2
+ <!-- the %20%20 suffix works around a banner-generator bug that clips a trailing W -->
3
+ <img src="https://assets.solidjs.com/banner?project=solid-flow%20%20&type=Ecosystem&background=tiles" alt="@dschz/solid-flow banner" />
3
4
  </p>
4
5
 
5
6
  # Solid Flow
6
7
 
7
8
  [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
8
9
  [![npm](https://img.shields.io/npm/v/@dschz/solid-flow?color=blue)](https://www.npmjs.com/package/@dschz/solid-flow)
10
+ [![JSR](https://jsr.io/badges/@dschz/solid-flow)](https://jsr.io/@dschz/solid-flow)
9
11
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@dschz/solid-flow)](https://bundlephobia.com/package/@dschz/solid-flow)
10
12
  [![CI](https://github.com/dsnchz/solid-flow/actions/workflows/ci.yaml/badge.svg)](https://github.com/dsnchz/solid-flow/actions/workflows/ci.yaml)
11
13
 
12
- > Solid Flow is a port of [React Flow](https://reactflow.dev/) and [Svelte Flow](https://svelteflow.dev/) for SolidJS.
14
+ A SolidJS port of [React Flow](https://reactflow.dev/) and [Svelte Flow](https://svelteflow.dev/): build node-based editors, diagrams, and interactive graphs with the same battle-tested gesture system ([@xyflow/system](https://github.com/xyflow/xyflow)) and an API designed around Solid's fine-grained reactivity.
13
15
 
14
- ☣️ **Solid Flow is alpha and currently under development. The API intends to follow React/Svelte Flow closely but some things might change for the sake of SolidJS.** ☣️
16
+ ## Version pairing
15
17
 
16
- ## Current Unsupported Features:
18
+ | Solid Flow | SolidJS | Status |
19
+ | ---------- | --------------- | ------------------- |
20
+ | `0.3.x` | `solid-js` 2.x | Active development |
21
+ | `0.2.x` | `solid-js` 1.9+ | Maintenance (fixes) |
17
22
 
18
- - [onlyRenderVisibleElements](https://github.com/dsnchz/solid-flow/issues/15): render only visible elements.
19
- - [Custom MiniMap Nodes](https://github.com/dsnchz/solid-flow/issues/12): define custom minimap jsx node elements.
20
- - [Edge Reconnect Anchors](https://github.com/dsnchz/solid-flow/issues/13): the ability to re-connect already connected edges.
23
+ The 0.3 line is built for SolidJS 2.0 and its deferred, fine-grained reactive graph. Keep `solid-js` and `@solidjs/web` on matching 2.0 versions — mixing them breaks at import time. Upgrading from 0.2.x? See [Migrating from 0.2.x](#migrating-from-02x).
21
24
 
22
25
  ## Key Features
23
26
 
24
- - **Easy to use:** Seamless zooming and panning, single- and multi selection of graph elements and keyboard shortcuts are supported out of the box
25
- - **Customizable:** Different node types (Input, Output, Default, Group) and edge types (Bezier, Straight, Step, SmoothStep) with full support for custom nodes and edges
26
- - **Fast rendering:** Only nodes that have changed are re-rendered using SolidJS's fine-grained reactivity
27
- - **Rich Plugin Ecosystem:** Background patterns, Interactive MiniMap, Zoom Controls, Node Toolbar, and Node Resizer components
28
- - **Powerful Hooks:** Comprehensive set of reactive hooks for nodes, edges, viewport, connections, and data management
29
- - **Full Accessibility:** Complete keyboard navigation, screen reader support, ARIA labels, and focus management
30
- - **Drag & Drop:** Built-in dragging for nodes, external drag-and-drop support, and customizable drag handles
31
- - **Advanced Features:** Node grouping, intersection detection, connection validation, and subflow support
32
- - **TypeScript First:** Fully typed API with generic type support and IntelliSense integration
27
+ - **Easy to use:** Zooming and panning, single- and multi-selection of graph elements, and keyboard shortcuts out of the box
28
+ - **Customizable:** Built-in node types (Input, Output, Default, Group) and edge types (Bezier, Straight, Step, SmoothStep) with full support for custom nodes and edges — including edge reconnection via `EdgeReconnectAnchor`
29
+ - **Fine-grained by construction:** A node drag is a handful of DOM attribute writes — no virtual DOM, no component re-renders, no memoization ceremony
30
+ - **Scales to large graphs:** Off-screen elements are viewport-culled by default, and an opt-in `onlyRenderVisibleElements` mode only mounts what's visible — stress-tested at 10,000 nodes
31
+ - **Type-guided data:** Your custom components are the schema node/edge stores narrow each element's `data` by its `type` field, with autocomplete for type names
32
+ - **Rich plugins:** Background patterns, interactive MiniMap (custom minimap nodes, click handlers), zoom Controls, Node Toolbar, and Node Resizer
33
+ - **Accessible:** Keyboard navigation, screen reader support, ARIA labels, and focus management
34
+ - **SSR-ready:** Renders on the server (a dedicated SSR test lane keeps it that way)
33
35
 
34
36
  ## Installation
35
37
 
36
- The easiest way to get the latest version of Solid Flow is to install it via npm, yarn or pnpm:
37
-
38
38
  ```sh
39
- npm install @dschz/solid-flow
40
- pnpm install @dschz/solid-flow
41
- yarn install @dschz/solid-flow
42
- bun install @dschz/solid-flow
39
+ npm install @dschz/solid-flow
40
+ pnpm add @dschz/solid-flow
41
+ yarn add @dschz/solid-flow
42
+ bun add @dschz/solid-flow
43
43
  ```
44
44
 
45
- ## Quick Start
45
+ `solid-js` is a peer dependency. Also available on [JSR](https://jsr.io/@dschz/solid-flow).
46
46
 
47
- This is a basic example to get you started. For more advanced examples and full API documentation, explore the playground examples included in this repository.
47
+ ## Quick Start
48
48
 
49
49
  ```tsx
50
50
  import {
51
- SolidFlow,
52
- SolidFlowProvider,
53
- Controls,
54
51
  Background,
55
- MiniMap,
56
- addEdge,
57
- type EdgeConnection,
52
+ Controls,
58
53
  createEdgeStore,
59
54
  createNodeStore,
60
- type Viewport,
55
+ type EdgeConnection,
56
+ MiniMap,
57
+ Panel,
58
+ SolidFlow,
61
59
  } from "@dschz/solid-flow";
62
- import "@dschz/solid-flow/styles"; // Required styles
63
-
64
- import { createStore, produce } from "solid-js/store";
65
-
66
- export default function Page() {
67
- return (
68
- <SolidFlowProvider>
69
- <Flow />
70
- </SolidFlowProvider>
71
- )
72
- }
60
+ import "@dschz/solid-flow/styles"; // required styles, import once
73
61
 
74
- function Flow() {
75
- // Can invoke useSolidFlow due to parent Page + SolidFlowProvider wrapper. Contains all helper APIs
76
- const { .. } = useSolidFlow();
77
-
78
- // Use createNodeStore and createEdgeStore for reactive state management
62
+ export const Flow = () => {
79
63
  const [nodes, setNodes] = createNodeStore([
80
- {
81
- id: "1",
82
- type: "input",
83
- data: { label: "Input Node" },
84
- position: { x: 250, y: 0 },
85
- },
86
- {
87
- id: "2",
88
- type: "default",
89
- data: { label: "Default Node" },
90
- position: { x: 100, y: 100 },
91
- },
92
- {
93
- id: "3",
94
- type: "output",
95
- data: { label: "Output Node" },
96
- position: { x: 250, y: 200 },
97
- },
64
+ { id: "1", type: "input", data: { label: "Input" }, position: { x: 250, y: 0 } },
65
+ { id: "2", type: "default", data: { label: "Default" }, position: { x: 100, y: 100 } },
66
+ { id: "3", type: "output", data: { label: "Output" }, position: { x: 250, y: 200 } },
98
67
  ]);
99
68
 
100
69
  const [edges, setEdges] = createEdgeStore([
@@ -102,216 +71,269 @@ function Flow() {
102
71
  { id: "e2-3", source: "2", target: "3" },
103
72
  ]);
104
73
 
105
- const [viewport, setViewport] = createStore<Viewport>({
106
- x: 100,
107
- y: 100,
108
- zoom: 5,
109
- });
110
-
111
- const updateViewport = () => {
112
- setViewport("x", (prev) => prev + 10);
74
+ // Your stores own which elements exist — adopt new connections into
75
+ // your edge store (see "Who owns the data" below).
76
+ const onConnect = (connection: EdgeConnection) => {
77
+ setEdges((edges) => {
78
+ edges.push(connection);
79
+ });
113
80
  };
114
81
 
115
- const onConnect = (connection: EdgeConnection) => {
116
- /**
117
- * Solid Flow updates the node/edge stores internally. The user-land edge store will have the connection inserted by the time onConnect fires so we can just go ahead and update the state of it
118
- */
119
- setEdges(
120
- (edge) => edge.id === connection.id,
121
- produce((edge) => {
122
- edge.animated = true;
123
- }),
124
- );
82
+ const nudge = () => {
83
+ // SolidJS 2.0 store writes are drafts: mutate what changed.
84
+ setNodes((nodes) => {
85
+ nodes[0]!.position.x += 20;
86
+ });
125
87
  };
126
88
 
127
89
  return (
128
90
  <SolidFlow nodes={nodes} edges={edges} onConnect={onConnect} fitView>
129
91
  <Controls />
130
92
  <MiniMap />
93
+ <Background variant="dots" />
131
94
  <Panel position="top-left">
132
- <button onClick={updateViewport}>Update viewport</button>
95
+ <button onClick={nudge}>Nudge first node</button>
133
96
  </Panel>
134
- <Background variant="dots" />
135
97
  </SolidFlow>
136
98
  );
137
- }
99
+ };
138
100
  ```
139
101
 
140
- ## Core Components
102
+ ## Your components are the schema
141
103
 
142
- ### Built-in Node Types
104
+ `createNodeStore` / `createEdgeStore` derive each element's `data` type from the component registered for its `type` — the renderer map is the single source of truth, and the stores narrow against it:
143
105
 
144
- - **InputNode** - Nodes with source handles only (starting points)
145
- - **OutputNode** - Nodes with target handles only (ending points)
146
- - **DefaultNode** - Standard nodes with both source and target handles
147
- - **GroupNode** - Container nodes for organizing other nodes
148
-
149
- ### Built-in Edge Types
106
+ ```tsx
107
+ import {
108
+ createNodeStore,
109
+ Handle,
110
+ type NodeProps,
111
+ type NodeTypes,
112
+ Position,
113
+ } from "@dschz/solid-flow";
150
114
 
151
- - **BezierEdge** - Smooth curved connections (default)
152
- - **StraightEdge** - Direct straight line connections
153
- - **StepEdge** - Right-angle step connections
154
- - **SmoothStepEdge** - Rounded step connections
115
+ const CounterNode = (props: NodeProps<{ count: number }, "counter">) => (
116
+ <div style={{ padding: "10px", background: "white", border: "1px solid #333" }}>
117
+ <Handle type="target" position={Position.Top} />
118
+ <div>count: {props.data.count}</div>
119
+ <Handle type="source" position={Position.Bottom} />
120
+ </div>
121
+ );
155
122
 
156
- ### Plugin Components
123
+ const nodeTypes = { counter: CounterNode } satisfies NodeTypes;
157
124
 
158
- - **Background** - Customizable canvas backgrounds (dots, lines, cross patterns)
159
- - **Controls** - Zoom in/out, fit view, lock/unlock interactions
160
- - **MiniMap** - Interactive overview with viewport indicator
161
- - **NodeToolbar** - Context-sensitive toolbars for nodes
162
- - **NodeResizer** - Real-time node resizing with handles
125
+ const [nodes] = createNodeStore<typeof nodeTypes>([
126
+ // "counter" narrows data to { count: number }; built-ins still work
127
+ { id: "a", type: "counter", data: { count: 1 }, position: { x: 0, y: 0 } },
128
+ { id: "b", type: "default", data: { label: "hi" }, position: { x: 0, y: 100 } },
129
+ // { id: "c", type: "countr", ... } <-- compile error: unknown type name
130
+ ]);
163
131
 
164
- ## Hooks & Utilities
132
+ <SolidFlow nodes={nodes} nodeTypes={nodeTypes} />;
133
+ ```
165
134
 
166
- ### Essential Hooks
135
+ The same guided unions are exported as standalone types, so plain arrays, props, and vanilla stores get identical narrowing:
167
136
 
168
137
  ```tsx
169
- // Main flow instance with full API
170
- const solidFlow = useSolidFlow();
138
+ import type { EdgesFor, NodesFor } from "@dschz/solid-flow";
171
139
 
172
- // Reactive access to nodes and edges
173
- const nodes = useNodes();
174
- const edges = useEdges();
140
+ const initialNodes = [
141
+ { id: "a", type: "counter", data: { count: 1 }, position: { x: 0, y: 0 } },
142
+ ] satisfies NodesFor<typeof nodeTypes>[];
143
+ ```
175
144
 
176
- // Viewport control and monitoring
177
- const viewport = useViewport();
145
+ ## Who owns the data
178
146
 
179
- // Connection state during drag operations
180
- const connection = useConnection();
147
+ The stores you pass as `nodes` / `edges` props are **controlled** — a deliberate contract:
181
148
 
182
- // Reactive access to node data
183
- const nodeData = useNodesData(["node-1", "node-2"]);
149
+ - **Your store owns membership.** Which nodes and edges exist is decided by your store. Any write form works: draft mutations update in place (`O(changed)`), and wholesale replacement (`setNodes(() => next)`) re-seeds the flow — rows are keyed by `id`, so surviving elements keep their runtime state.
150
+ - **The flow writes runtime fields onto your rows.** Dragging updates `position`, selection updates `selected`, measurement fills `measured` — on the same objects you provided, so reading your store is always live.
151
+ - **Imperative commands don't write membership back.** `commands.addNodes(...)` and friends update the flow, not your store. To keep an element across a store replacement, adopt it — like the `onConnect` handler in the Quick Start pushing the new connection into the edge store.
184
152
 
185
- // Node connection information
186
- const connections = useNodeConnections("node-1");
187
- ```
153
+ ## The flow API
188
154
 
189
- ### Utility Functions
155
+ `useSolidFlow()` returns `{ flow, commands }` (with `commands` also spread at the top level for React/Svelte Flow familiarity). Both are stable identities — destructuring is safe.
190
156
 
191
157
  ```tsx
192
- // Create reactive stores (replaces signals)
193
- const [nodes, setNodes] = createNodeStore(initialNodes);
194
- const [edges, setEdges] = createEdgeStore(initialEdges);
195
-
196
- // Update stores with SolidJS patterns
197
- import { produce } from "solid-js/store";
198
- setNodes(
199
- (node) => node.id === "1",
200
- produce((node) => {
201
- node.position.x += 20;
202
- }),
158
+ import { SolidFlowProvider, useSolidFlow } from "@dschz/solid-flow";
159
+
160
+ const Toolbar = () => {
161
+ const { flow, commands } = useSolidFlow();
162
+
163
+ // flow.* reads are reactive: use them in JSX or tracked scopes.
164
+ // flow.nodes, flow.edges, flow.viewport, flow.selection,
165
+ // flow.nodesInitialized, flow.viewportInitialized, ...
166
+
167
+ return (
168
+ <div>
169
+ <span>{flow.selection.nodes.length} selected</span>
170
+ <button onClick={() => commands.fitView()}>Fit</button>
171
+ <button onClick={() => commands.zoomIn()}>+</button>
172
+ </div>
173
+ );
174
+ };
175
+
176
+ // Hooks used outside <SolidFlow> children need a provider:
177
+ export const App = () => (
178
+ <SolidFlowProvider>
179
+ <Toolbar />
180
+ <Flow />
181
+ </SolidFlowProvider>
203
182
  );
183
+ ```
204
184
 
205
- // Add new connections
206
- setEdges(addEdge(connection, edges));
185
+ Commands include viewport control (`fitView`, `fitBounds`, `zoomIn`/`zoomOut`, `setZoom`, `setCenter`, `setViewport`, `panBy`), element updates (`updateNode`, `updateNodeData`, `updateEdge`, `addNodes`, `addEdges`, `deleteElements`), coordinate conversion (`screenToFlowPosition`, `flowToScreenPosition`), and geometry queries (`isNodeIntersecting`, `getIntersectingNodes`).
207
186
 
208
- // Coordinate transformations (via useSolidFlow)
209
- const { screenToFlowPosition, flowToScreenPosition } = useSolidFlow();
187
+ ## Hooks
210
188
 
211
- // Node/edge utilities
212
- getNodesBounds(nodes);
213
- getIntersectingNodes(node, nodes);
189
+ ```tsx
190
+ const solidFlow = useSolidFlow(); // { flow, commands } — the main API
191
+ const nodes = useNodes(); // reactive readonly node array
192
+ const edges = useEdges(); // reactive readonly edge array
193
+ const viewport = useViewport(); // reactive viewport accessor
194
+ const connection = useConnection(); // in-progress connection state
195
+
196
+ // Hook parameters that feed reactive reads are accessors, so they can't
197
+ // silently go stale when derived from props:
198
+ const nodeData = useNodesData(() => ["node-1", "node-2"]);
199
+ const connections = useNodeConnections(() => ({ id: "node-1" }));
200
+ const internal = useInternalNode(() => props.nodeId); // measured/internal record
201
+
202
+ const updateInternals = useUpdateNodeInternals(); // re-measure after handle changes
203
+
204
+ // Which node/edge a nested component is rendered inside — for composable
205
+ // custom nodes and edge labels without prop drilling:
206
+ const nodeId = useNodeId();
207
+ const edgeId = useEdgeId();
214
208
  ```
215
209
 
216
- ## Advanced Features
210
+ ### Coming from React Flow?
211
+
212
+ Some React Flow hooks intentionally have no Solid Flow equivalent, because Solid's fine-grained reactivity makes them unnecessary:
217
213
 
218
- ### Custom Nodes and Edges
214
+ - `useOnSelectionChange` / `useOnViewportChange` — reading `flow.selection` or `flow.viewport` in a tracked scope IS the subscription; wrap side effects in `createEffect` over those reads.
215
+ - `useNodesState` / `useEdgesState` — use `createNodeStore` / `createEdgeStore` instead.
216
+ - `useStore` — the reactive `flow` struct is the supported read surface; there is no public escape hatch into internals.
219
217
 
220
- Create fully customized components with multiple handles:
218
+ ## Custom edges and reconnection
221
219
 
222
220
  ```tsx
223
- import { Handle, type NodeProps } from "@dschz/solid-flow";
221
+ import { BaseEdge, EdgeReconnectAnchor, type EdgeProps, getBezierPath } from "@dschz/solid-flow";
222
+
223
+ const ReconnectableEdge = (props: EdgeProps) => {
224
+ const path = () =>
225
+ getBezierPath({
226
+ sourceX: props.sourceX,
227
+ sourceY: props.sourceY,
228
+ sourcePosition: props.sourcePosition,
229
+ targetX: props.targetX,
230
+ targetY: props.targetY,
231
+ targetPosition: props.targetPosition,
232
+ })[0];
224
233
 
225
- // Type-safe custom node component
226
- function CustomNode(props: NodeProps<{ label: string }, "custom">) {
227
234
  return (
228
- <div class="custom-node" style={{ padding: "10px", background: "white" }}>
229
- <Handle type="target" position="top" />
230
- <div>{props.data.label}</div>
231
- <Handle type="source" position="bottom" id="output-a" />
232
- <Handle type="source" position="bottom" id="output-b" style={{ left: "80%" }} />
233
- </div>
235
+ <>
236
+ <BaseEdge path={path()} markerEnd={props.markerEnd} />
237
+ {/* drag either end of a selected edge onto another handle */}
238
+ <EdgeReconnectAnchor type="source" position={{ x: props.sourceX, y: props.sourceY }} />
239
+ <EdgeReconnectAnchor type="target" position={{ x: props.targetX, y: props.targetY }} />
240
+ </>
234
241
  );
235
- }
242
+ };
243
+ ```
236
244
 
237
- // Create type-safe node types
238
- const nodeTypes = {
239
- custom: CustomNode,
240
- } satisfies NodeTypes;
245
+ Reconnection lifecycle callbacks (`onReconnectStart`, `onReconnect`, `onReconnectEnd`, `onBeforeReconnect`) are available on `<SolidFlow>`.
241
246
 
242
- // Use with typed store
243
- const [nodes] = createNodeStore<typeof nodeTypes>([...]);
247
+ ## Built-in components
244
248
 
245
- <SolidFlow nodeTypes={nodeTypes} nodes={nodes} ... />
246
- ```
249
+ **Node types:** `input` (source handle only), `output` (target handle only), `default` (both), `group` (container for subflows)
247
250
 
248
- ### Connection Validation
251
+ **Edge types:** `default` (bezier), `straight`, `step`, `smoothstep`
249
252
 
250
- ```tsx
251
- import { type Connection } from "@dschz/solid-flow";
253
+ **Plugins:**
252
254
 
253
- const isValidConnection = (connection: Connection) => {
254
- // Custom validation logic
255
- return connection.source !== connection.target;
256
- };
255
+ - **Background** dots, lines, and cross patterns
256
+ - **Controls** zoom in/out, fit view, lock interactions
257
+ - **MiniMap** interactive overview with viewport indicator, custom node rendering via `nodeComponent`, and `onClick` / `onNodeClick` handlers
258
+ - **NodeToolbar** — context-sensitive toolbars for nodes
259
+ - **NodeResizer** — interactive node resizing with handles
257
260
 
258
- const onConnect = (connection: Connection) => {
259
- console.log("New connection:", connection);
260
- setEdges(addEdge(connection, edges));
261
- };
261
+ ## Performance
262
262
 
263
- <SolidFlow
264
- isValidConnection={isValidConnection}
265
- onConnect={onConnect}
266
- ...
267
- />
268
- ```
263
+ Two complementary culling tiers keep large graphs fast:
269
264
 
270
- ### Event Handling
265
+ - **CSS culling (always on):** Elements outside the (overscanned) viewport are hidden with `visibility: hidden` + `pointer-events: none`. Everything stays mounted, so component state, measurement, and accessibility semantics are untouched — this tier has no userland contract at all.
266
+ - **`onlyRenderVisibleElements` (opt-in):** Off-screen elements are **unmounted entirely** and remount as the viewport reaches them. At 10,000 nodes this cuts the DOM ~16x, roughly halves memory, and makes node drags several times faster. Positions, selection, and cached measurements live in the flow's data graph — outside your components — so elements come back exactly as they left. Component-_local_ state does not survive unmounting: keep state you care about in `node.data`. Selected elements, unmeasured nodes, and the node holding focus are never unmounted — and an element whose content must keep running off-screen (media, timers, embeds) can opt out of culling entirely with `cullable: false` on the node or edge.
271
267
 
272
- ```tsx
273
- <SolidFlow
274
- onNodeClick={(event, node) => console.log("Node clicked:", node)}
275
- onNodeDrag={(event, node) => console.log("Node dragged:", node)}
276
- onEdgeClick={(event, edge) => console.log("Edge clicked:", edge)}
277
- onPaneClick={(event) => console.log("Pane clicked")}
278
- onSelectionChange={(params) => console.log("Selection changed:", params)}
279
- />
280
- ```
268
+ The MiniMap always renders the full graph in either mode — it reads the data graph, not the DOM.
269
+
270
+ ## Server-side rendering
271
+
272
+ `<SolidFlow>` renders with `renderToString` and hydrates cleanly. For meaningful server-rendered layout, give nodes explicit `width` / `height` (there is no DOM to measure on the server) — the same guidance as React/Svelte Flow.
281
273
 
282
274
  ## Accessibility
283
275
 
284
- Solid Flow includes comprehensive accessibility features:
276
+ - Full keyboard navigation (arrow-key node movement, focus traversal, Escape to deselect)
277
+ - Screen reader support with ARIA labels and live announcements
278
+ - Focus management, including under viewport culling
279
+ - Color mode support (`light` / `dark` / `system`)
285
280
 
286
- - Full keyboard navigation support
287
- - Screen reader compatibility with ARIA labels
288
- - Focus management and visual indicators
289
- - High contrast and color mode support
290
- - Customizable keyboard shortcuts
281
+ ## Migrating from 0.2.x
291
282
 
292
- ## Performance
283
+ The 0.3 line targets SolidJS 2.0, which changes how you write to stores, and reworks the read API. The gestures, components, plugins, and commands are otherwise the same.
293
284
 
294
- - **Reactive Updates**: Only re-renders components when their specific data changes
295
- - **Viewport Optimization**: Option to render only visible elements (coming soon)
296
- - **Memory Efficient**: Optimized data structures for large graphs
297
- - **Stress Tested**: Handles hundreds of nodes smoothly
285
+ **1. Upgrade the peer dependencies.** `solid-js` and `@solidjs/web` move to matching 2.0 versions.
298
286
 
299
- ## Examples
287
+ **2. Store writes: path setters are gone (SolidJS 2.0).** Every `setNodes` / `setEdges` call site using 1.x path syntax becomes a draft callback — the callback's argument is a mutable draft, so mutation is the API. Returning a value instead replaces wholesale.
288
+
289
+ ```tsx
290
+ // 0.2.x (SolidJS 1.x) — path syntax
291
+ setNodes(0, "position", "x", (x) => x + 20);
292
+ setEdges((edge) => edge.id === "e1", "animated", true);
293
+
294
+ // 0.3.x (SolidJS 2.0) — draft callback
295
+ setNodes((nodes) => {
296
+ nodes[0]!.position.x += 20;
297
+ });
298
+ setEdges((edges) => {
299
+ const edge = edges.find((e) => e.id === "e1");
300
+ if (edge) edge.animated = true;
301
+ });
302
+
303
+ // wholesale replacement (re-seeds the flow; rows keyed by id are reused)
304
+ setNodes(() => nextNodes);
305
+ ```
306
+
307
+ **3. `useSolidFlow` reads moved to the reactive `flow` struct.** The flat getters (`getNodes()`, `getEdges()`, `getNode(id)`, `getEdge(id)`, `getInternalNode(id)`, `getViewport()`, `getZoom()`) are removed:
308
+
309
+ ```tsx
310
+ // 0.2.x // 0.3.x
311
+ solidFlow.getNodes();
312
+ flow.nodes;
313
+ solidFlow.getViewport();
314
+ flow.viewport;
315
+ solidFlow.getZoom();
316
+ flow.viewport.zoom;
317
+ solidFlow.getNode("a");
318
+ flow.nodes.find((n) => n.id === "a");
319
+ solidFlow.getInternalNode("a");
320
+ useInternalNode(() => "a");
321
+ ```
300
322
 
301
- The repository includes a comprehensive playground with 25+ examples:
323
+ `flow.*` reads are reactive — using them in JSX or a tracked scope subscribes. Commands (`fitView`, `setViewport`, `updateNode`, `deleteElements`, ...) are unchanged and now also available namespaced under `commands`.
302
324
 
303
- - **Basic Usage** - Simple flows and interactions
304
- - **Custom Nodes** - Creating specialized node types
305
- - **Edge Types** - Different connection styles
306
- - **Drag & Drop** - External elements and node creation
307
- - **Validation** - Connection rules and constraints
308
- - **Subflows** - Hierarchical node organization
309
- - **Performance** - Large dataset handling
310
- - **Accessibility** - Keyboard navigation and screen readers
325
+ **4. New connections are no longer written into your edge store.** In 0.2.x the flow inserted the connected edge into your store before `onConnect` fired. Under 0.3's ownership contract your store owns membership: adopt the connection yourself (see the Quick Start's `onConnect`). Unadopted connections still render, but won't survive a wholesale store replacement.
311
326
 
312
- Run the examples locally:
327
+ **5. `onlyRenderVisibleElements` now does what it says.** In 0.2.x the prop was accepted but inert. In 0.3 it opts into unmount culling (off-screen elements are not mounted at all — see [Performance](#performance)), while the CSS culling tier is always on and needs no prop.
328
+
329
+ **6. Smaller signature changes.** `useNodes()` / `useEdges()` return `readonly` arrays; `useHandleEdgeSelect` is removed (it was internal plumbing — select edges through `commands`).
330
+
331
+ ## Examples
332
+
333
+ The repository ships a playground with 25+ runnable examples — custom nodes and edges, edge reconnection, drag & drop, subflows, validation, minimap customization, a 10k-node stress test, accessibility, and more:
313
334
 
314
335
  ```bash
336
+ bun install
315
337
  bun start
316
338
  ```
317
339
 
@@ -341,6 +363,14 @@ bun install
341
363
  bun start
342
364
  ```
343
365
 
366
+ ### Testing
367
+
368
+ ```bash
369
+ bun run test # unit + component tests (vitest, jsdom)
370
+ bun run test:ssr # SSR lane (node environment, server builds)
371
+ bun run test:e2e # browser gesture harness (Playwright)
372
+ ```
373
+
344
374
  ### Linting & Formatting
345
375
 
346
376
  ```bash