avoid-nodes-edge 0.1.0

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 ADDED
@@ -0,0 +1,501 @@
1
+ # avoid-nodes-edge
2
+
3
+ Orthogonal edge routing for [React Flow](https://reactflow.dev/) — edges automatically route around nodes using [libavoid-js](https://github.com/nicknisi/libavoid-js) (WASM). All WASM and routing computation runs exclusively in a **Web Worker**, keeping the main thread free and your UI smooth.
4
+
5
+ ## Features
6
+
7
+ - Orthogonal (right-angle) edge routing that avoids overlapping nodes
8
+ - WASM routing engine runs entirely in a Web Worker — zero main-thread jank
9
+ - Incremental updates: dragging a node only re-routes affected edges
10
+ - Parallel edge support with automatic offset
11
+ - Configurable spacing, rounding, and grid snapping
12
+ - ER relationship labels (one-to-one, one-to-many, etc.)
13
+ - Fallback rendering (smooth-step/straight paths) while the worker loads
14
+ - Works with React Flow v12+
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm install avoid-nodes-edge
20
+ ```
21
+
22
+ ```bash
23
+ yarn add avoid-nodes-edge
24
+ ```
25
+
26
+ ```bash
27
+ pnpm add avoid-nodes-edge
28
+ ```
29
+
30
+ ### Peer Dependencies
31
+
32
+ | Package | Version |
33
+ |---|---|
34
+ | `react` | >= 18.0.0 |
35
+ | `react-dom` | >= 18.0.0 |
36
+ | `@xyflow/react` | >= 12.0.0 |
37
+ | `zustand` | >= 4.0.0 |
38
+
39
+ ## Quick Start
40
+
41
+ ### 1. Serve the WASM binary
42
+
43
+ The routing engine uses a WebAssembly binary from `libavoid-js`. Copy it to your `public/` directory so it's served at `/libavoid.wasm`:
44
+
45
+ ```bash
46
+ cp node_modules/libavoid-js/dist/libavoid.wasm public/libavoid.wasm
47
+ ```
48
+
49
+ Or automate it with a postinstall script in your `package.json`:
50
+
51
+ ```json
52
+ {
53
+ "scripts": {
54
+ "postinstall": "node scripts/copy-libavoid-wasm.cjs"
55
+ }
56
+ }
57
+ ```
58
+
59
+ Create `scripts/copy-libavoid-wasm.cjs`:
60
+
61
+ ```js
62
+ #!/usr/bin/env node
63
+ const fs = require("fs");
64
+ const path = require("path");
65
+
66
+ // Search both local and hoisted (monorepo) node_modules
67
+ const candidates = [
68
+ path.join(__dirname, "..", "node_modules", "libavoid-js", "dist", "libavoid.wasm"),
69
+ path.join(__dirname, "..", "..", "..", "node_modules", "libavoid-js", "dist", "libavoid.wasm"),
70
+ ];
71
+
72
+ const src = candidates.find((p) => fs.existsSync(p));
73
+ const dest = path.join(__dirname, "..", "public", "libavoid.wasm");
74
+
75
+ if (!src) {
76
+ console.warn("[copy-libavoid-wasm] libavoid.wasm not found — run npm install first");
77
+ process.exit(0);
78
+ }
79
+
80
+ const publicDir = path.dirname(dest);
81
+ if (!fs.existsSync(publicDir)) {
82
+ fs.mkdirSync(publicDir, { recursive: true });
83
+ }
84
+
85
+ fs.copyFileSync(src, dest);
86
+ console.log("[copy-libavoid-wasm] Copied libavoid.wasm to public/");
87
+ ```
88
+
89
+ This script handles both flat and hoisted `node_modules` layouts (npm workspaces, monorepos).
90
+
91
+ ### 2. Configure your bundler
92
+
93
+ #### Vite
94
+
95
+ ```ts
96
+ // vite.config.ts
97
+ import { defineConfig } from 'vite';
98
+ import react from '@vitejs/plugin-react';
99
+
100
+ export default defineConfig({
101
+ plugins: [react()],
102
+ worker: {
103
+ format: 'es',
104
+ },
105
+ optimizeDeps: {
106
+ exclude: ['avoid-nodes-edge'],
107
+ },
108
+ });
109
+ ```
110
+
111
+ #### Next.js / Webpack
112
+
113
+ Web Workers with ES modules require additional webpack configuration. Ensure your bundler supports the `new URL(..., import.meta.url)` pattern for worker resolution.
114
+
115
+ ### 3. Add to your React Flow app
116
+
117
+ ```tsx
118
+ import { useState, useCallback } from 'react';
119
+ import {
120
+ ReactFlow,
121
+ ReactFlowProvider,
122
+ addEdge,
123
+ applyNodeChanges,
124
+ applyEdgeChanges,
125
+ type Node,
126
+ type Edge,
127
+ type NodeChange,
128
+ type EdgeChange,
129
+ type Connection,
130
+ } from '@xyflow/react';
131
+ import '@xyflow/react/dist/style.css';
132
+
133
+ import { AvoidNodesEdge } from 'avoid-nodes-edge/edge';
134
+ import { useAvoidNodesRouterFromWorker } from 'avoid-nodes-edge';
135
+
136
+ // Register the custom edge type
137
+ const edgeTypes = { avoidNodes: AvoidNodesEdge };
138
+
139
+ const initialNodes: Node[] = [
140
+ { id: '1', position: { x: 0, y: 0 }, data: { label: 'Node 1' } },
141
+ { id: '2', position: { x: 300, y: 0 }, data: { label: 'Node 2' } },
142
+ { id: '3', position: { x: 150, y: 150 }, data: { label: 'Node 3' } },
143
+ ];
144
+
145
+ const initialEdges: Edge[] = [
146
+ { id: 'e1-2', source: '1', target: '2', type: 'avoidNodes' },
147
+ ];
148
+
149
+ function Flow() {
150
+ const [nodes, setNodes] = useState<Node[]>(initialNodes);
151
+ const [edges, setEdges] = useState<Edge[]>(initialEdges);
152
+
153
+ // Set up the worker-based router
154
+ const { updateRoutingOnNodesChange, resetRouting } =
155
+ useAvoidNodesRouterFromWorker(nodes, edges, {
156
+ edgeToNodeSpacing: 12,
157
+ edgeToEdgeSpacing: 10,
158
+ edgeRounding: 8,
159
+ });
160
+
161
+ const onNodesChange = useCallback(
162
+ (changes: NodeChange<Node>[]) => {
163
+ setNodes((nds) => applyNodeChanges(changes, nds));
164
+ updateRoutingOnNodesChange(changes);
165
+ },
166
+ [updateRoutingOnNodesChange]
167
+ );
168
+
169
+ const onEdgesChange = useCallback(
170
+ (changes: EdgeChange<Edge>[]) => {
171
+ setEdges((eds) => applyEdgeChanges(changes, eds));
172
+ const needsReset = changes.some((c) => c.type === 'add' || c.type === 'remove');
173
+ if (needsReset) requestAnimationFrame(() => resetRouting());
174
+ },
175
+ [resetRouting]
176
+ );
177
+
178
+ const onConnect = useCallback(
179
+ (params: Connection) => {
180
+ setEdges((eds) => addEdge({ ...params, type: 'avoidNodes' }, eds));
181
+ requestAnimationFrame(() => resetRouting());
182
+ },
183
+ [resetRouting]
184
+ );
185
+
186
+ return (
187
+ <ReactFlow
188
+ nodes={nodes}
189
+ edges={edges}
190
+ onNodesChange={onNodesChange}
191
+ onEdgesChange={onEdgesChange}
192
+ onConnect={onConnect}
193
+ edgeTypes={edgeTypes}
194
+ defaultEdgeOptions={{ type: 'avoidNodes' }}
195
+ fitView
196
+ />
197
+ );
198
+ }
199
+
200
+ export default function App() {
201
+ return (
202
+ <ReactFlowProvider>
203
+ <Flow />
204
+ </ReactFlowProvider>
205
+ );
206
+ }
207
+ ```
208
+
209
+ > **Important:** Edges must have `type: "avoidNodes"` to be processed by the router.
210
+
211
+ ## API Reference
212
+
213
+ ### `useAvoidNodesRouterFromWorker(nodes, edges, options?)`
214
+
215
+ The main hook. Manages the Web Worker lifecycle and routes edges around nodes.
216
+
217
+ ```ts
218
+ import { useAvoidNodesRouterFromWorker } from 'avoid-nodes-edge';
219
+
220
+ const { updateRoutingOnNodesChange, resetRouting, refreshRouting, updateRoutingForNodeIds } =
221
+ useAvoidNodesRouterFromWorker(nodes, edges, options);
222
+ ```
223
+
224
+ #### Options
225
+
226
+ | Option | Type | Default | Description |
227
+ |---|---|---|---|
228
+ | `edgeToNodeSpacing` | `number` | `8` | Buffer distance (px) between edges and node boundaries |
229
+ | `edgeToEdgeSpacing` | `number` | `10` | Distance (px) between parallel edge segments |
230
+ | `edgeRounding` | `number` | `0` | Corner radius (px) for rounded orthogonal bends |
231
+ | `diagramGridSize` | `number` | `0` | Snap edge waypoints to a grid of this size (0 = no grid) |
232
+ | `shouldSplitEdgesNearHandle` | `boolean` | `undefined` | Split edges near connection handles |
233
+
234
+ #### Return Value
235
+
236
+ | Property | Type | Description |
237
+ |---|---|---|
238
+ | `updateRoutingOnNodesChange` | `(changes: NodeChange[]) => void` | Call from `onNodesChange`. Handles incremental updates on drag/resize and full resets on add/remove. |
239
+ | `resetRouting` | `() => void` | Force a full re-route of all edges |
240
+ | `refreshRouting` | `() => void` | Re-route using current node/edge state |
241
+ | `updateRoutingForNodeIds` | `(nodeIds: string[]) => void` | Incrementally re-route edges for specific nodes |
242
+
243
+ ---
244
+
245
+ ### `AvoidNodesEdge`
246
+
247
+ Custom React Flow edge component that renders the routed path.
248
+
249
+ ```tsx
250
+ import { AvoidNodesEdge } from 'avoid-nodes-edge/edge';
251
+
252
+ const edgeTypes = { avoidNodes: AvoidNodesEdge };
253
+ ```
254
+
255
+ Falls back to a smooth-step or straight path while the worker is loading WASM. Once loaded, renders the computed orthogonal route.
256
+
257
+ #### Edge Data Properties
258
+
259
+ Customize individual edges via the `data` property:
260
+
261
+ ```ts
262
+ const edges: Edge[] = [
263
+ {
264
+ id: 'e1-2',
265
+ source: '1',
266
+ target: '2',
267
+ type: 'avoidNodes',
268
+ data: {
269
+ label: 'connects to',
270
+ strokeColor: '#3b82f6',
271
+ strokeWidth: 2,
272
+ strokeDasharray: '5,5',
273
+ flowDirection: 'mono', // 'mono' | 'bi' | 'none'
274
+ erRelation: 'one-to-many', // ER relationship label
275
+ connectorType: 'default', // 'default' | 'straight' | 'smoothstep' | 'step'
276
+ },
277
+ },
278
+ ];
279
+ ```
280
+
281
+ | Data Property | Type | Default | Description |
282
+ |---|---|---|---|
283
+ | `label` | `string` | `""` | Text label displayed at the edge midpoint |
284
+ | `strokeColor` | `string` | `"#94a3b8"` | Edge stroke color |
285
+ | `strokeWidth` | `number` | `1.5` | Edge stroke width (px) |
286
+ | `strokeDasharray` | `string` | `undefined` | SVG dash pattern (e.g. `"5,5"`) |
287
+ | `flowDirection` | `"mono" \| "bi" \| "none"` | `"mono"` | Arrow direction: one-way, bidirectional, or none |
288
+ | `markerEnd` | `string` | `undefined` | Custom SVG marker at the end |
289
+ | `markerStart` | `string` | `undefined` | Custom SVG marker at the start |
290
+ | `erRelation` | `string` | `null` | ER relationship: `"one-to-one"`, `"one-to-many"`, `"many-to-one"`, `"many-to-many"` |
291
+ | `connectorType` | `string` | `"default"` | Path shape for parallel offsets |
292
+
293
+ ---
294
+
295
+ ### `useAvoidNodesPath(params)`
296
+
297
+ Low-level hook that reads the routed path for a single edge from the store. Used internally by `AvoidNodesEdge` — useful if you're building a custom edge component.
298
+
299
+ ```ts
300
+ import { useAvoidNodesPath } from 'avoid-nodes-edge';
301
+
302
+ const [path, labelX, labelY, wasRouted] = useAvoidNodesPath({
303
+ id: 'edge-1',
304
+ sourceX: 100,
305
+ sourceY: 50,
306
+ targetX: 400,
307
+ targetY: 200,
308
+ sourcePosition: 'right',
309
+ targetPosition: 'left',
310
+ });
311
+ ```
312
+
313
+ | Parameter | Type | Description |
314
+ |---|---|---|
315
+ | `id` | `string` | Edge ID to look up in the routes store |
316
+ | `sourceX/Y` | `number` | Source handle coordinates |
317
+ | `targetX/Y` | `number` | Target handle coordinates |
318
+ | `sourcePosition` | `"left" \| "right" \| "top" \| "bottom"` | Source handle side |
319
+ | `targetPosition` | `"left" \| "right" \| "top" \| "bottom"` | Target handle side |
320
+ | `borderRadius` | `number` | Fallback smooth-step border radius |
321
+ | `offset` | `number` | Fallback smooth-step offset |
322
+
323
+ Returns `[path, labelX, labelY, wasRouted]` — `wasRouted` is `false` while the worker is loading.
324
+
325
+ ---
326
+
327
+ ### `useAvoidRoutesStore`
328
+
329
+ Zustand store holding the computed routes from the worker.
330
+
331
+ ```ts
332
+ import { useAvoidRoutesStore } from 'avoid-nodes-edge';
333
+
334
+ // Read routes
335
+ const routes = useAvoidRoutesStore((s) => s.routes);
336
+ const loaded = useAvoidRoutesStore((s) => s.loaded);
337
+
338
+ // Check a specific edge
339
+ const edgeRoute = useAvoidRoutesStore((s) => s.routes['edge-1']);
340
+ // => { path: "M 100 50 L 100 200 L 400 200", labelX: 250, labelY: 200 }
341
+ ```
342
+
343
+ | Property | Type | Description |
344
+ |---|---|---|
345
+ | `routes` | `Record<string, AvoidRoute>` | Map of edge ID to `{ path, labelX, labelY }` |
346
+ | `loaded` | `boolean` | Whether the WASM worker has finished loading |
347
+ | `setRoutes` | `(routes) => void` | Update routes (called by worker listener) |
348
+ | `setLoaded` | `(loaded) => void` | Update loaded state |
349
+
350
+ ---
351
+
352
+ ### `useAvoidRouterActionsStore`
353
+
354
+ Zustand store holding imperative routing actions. Useful for triggering re-routes from outside the component that owns the router.
355
+
356
+ ```ts
357
+ import { useAvoidRouterActionsStore } from 'avoid-nodes-edge';
358
+
359
+ const { resetRouting, updateRoutesForNodeId } =
360
+ useAvoidRouterActionsStore((s) => s.actions);
361
+
362
+ // Force full re-route
363
+ resetRouting();
364
+
365
+ // Re-route edges for a specific node
366
+ updateRoutesForNodeId('node-1');
367
+ ```
368
+
369
+ ---
370
+
371
+ ### `useAvoidWorker(options?)`
372
+
373
+ Low-level hook that creates and manages the Web Worker. Used internally by `useAvoidNodesRouterFromWorker` — useful if you need direct control over the worker.
374
+
375
+ ```ts
376
+ import { useAvoidWorker } from 'avoid-nodes-edge';
377
+
378
+ const { workerLoaded, post, close } = useAvoidWorker({
379
+ create: true,
380
+ onLoaded: (success) => console.log('WASM loaded:', success),
381
+ onRouted: (routes) => console.log('Routes computed:', routes),
382
+ });
383
+
384
+ // Send a command to the worker
385
+ post({ command: 'reset', nodes, edges, options });
386
+ ```
387
+
388
+ ---
389
+
390
+ ### Constants
391
+
392
+ Configurable constants exported from the package:
393
+
394
+ ```ts
395
+ import {
396
+ DEBOUNCE_ROUTING_MS, // 0 — debounce before routing (ms)
397
+ EDGE_BORDER_RADIUS, // 0 — default corner radius (px)
398
+ SHOULD_START_EDGE_AT_HANDLE_BORDER, // true
399
+ DEV_LOG_WEB_WORKER_MESSAGES, // false
400
+ } from 'avoid-nodes-edge';
401
+ ```
402
+
403
+ ---
404
+
405
+ ### Types
406
+
407
+ All types are exported for TypeScript users:
408
+
409
+ ```ts
410
+ import type {
411
+ // Router
412
+ AvoidRoute,
413
+ AvoidRouterOptions,
414
+ HandlePosition,
415
+
416
+ // Hooks
417
+ UseAvoidNodesRouterOptions,
418
+ UseAvoidNodesRouterResult,
419
+ UseAvoidWorkerOptions,
420
+ UseAvoidWorkerResult,
421
+ UseAvoidNodesPathParams,
422
+ Position,
423
+
424
+ // Stores
425
+ AvoidRoutesState,
426
+ AvoidRouterActions,
427
+
428
+ // Worker messages
429
+ AvoidRouterWorkerCommand,
430
+ AvoidRouterWorkerResponse,
431
+ } from 'avoid-nodes-edge';
432
+ ```
433
+
434
+ ## Architecture
435
+
436
+ ```
437
+ Main Thread Worker Thread
438
+ ─────────── ─────────────
439
+ avoid-router.worker.js
440
+ useAvoidNodesRouterFromWorker ─────────► loads libavoid WASM
441
+ posts commands: runs routeAll()
442
+ • reset (full graph) computes orthogonal paths
443
+ • updateNodes (incremental) ◄──────────────────────
444
+ • route (one-shot) posts { routed, routes }
445
+
446
+ useAvoidRoutesStore ◄──────────────────
447
+ stores routes map
448
+
449
+ AvoidNodesEdge
450
+ reads route from store
451
+ renders SVG path
452
+ ```
453
+
454
+ ### Why WASM never loads on the main thread
455
+
456
+ The `libavoid-js` WASM binary (~200KB) and the routing algorithm are computationally expensive. Loading and running them on the main thread would cause frame drops during initial load and every re-route. By running everything in a Web Worker:
457
+
458
+ - **Initial load**: WASM downloads and compiles in the background
459
+ - **Routing**: Heavy graph computation doesn't block React renders
460
+ - **Dragging**: Incremental updates keep the UI at 60fps
461
+ - **Fallback**: Edges render immediately as straight/smooth-step paths, then snap to routed paths once the worker responds
462
+
463
+ ### Worker Message Protocol
464
+
465
+ | Command | Direction | Description |
466
+ |---|---|---|
467
+ | `reset` | Main -> Worker | Send full graph for re-routing |
468
+ | `updateNodes` | Main -> Worker | Send changed nodes for incremental routing |
469
+ | `change` | Main -> Worker | Update a single node or edge |
470
+ | `add` | Main -> Worker | Add a node or edge |
471
+ | `remove` | Main -> Worker | Remove a node or edge by ID |
472
+ | `route` | Main -> Worker | One-shot route (doesn't update internal state) |
473
+ | `close` | Main -> Worker | Shut down the worker |
474
+ | `loaded` | Worker -> Main | WASM load result (`{ success: boolean }`) |
475
+ | `routed` | Worker -> Main | Computed routes (`{ routes: Record<string, AvoidRoute> }`) |
476
+
477
+ ## Troubleshooting
478
+
479
+ ### Worker fails to load (MIME type error)
480
+
481
+ If you see `Failed to load module script: The server responded with a non-JavaScript MIME type`, make sure:
482
+
483
+ 1. Your Vite config includes `worker: { format: 'es' }`
484
+ 2. Your Vite config includes `optimizeDeps: { exclude: ['avoid-nodes-edge'] }`
485
+ 3. Clear the Vite cache: `rm -rf node_modules/.vite`
486
+
487
+ ### WASM not found
488
+
489
+ If the worker loads but WASM fails, ensure `libavoid.wasm` is served at `/libavoid.wasm` from your `public/` directory.
490
+
491
+ ### Edges render as straight lines
492
+
493
+ This is the expected fallback while the worker loads WASM. Once loaded, edges will snap to routed paths. If they stay straight, check the browser console for worker errors.
494
+
495
+ ### Edges don't route around a node
496
+
497
+ Make sure the node has `type` set to something other than `"group"`. Group nodes are treated as containers, not obstacles.
498
+
499
+ ## License
500
+
501
+ MIT
@@ -0,0 +1,87 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/store.ts
6
+ var _zustand = require('zustand');
7
+ var useAvoidRoutesStore = _zustand.create.call(void 0, (set) => ({
8
+ loaded: false,
9
+ routes: {},
10
+ setLoaded: (loaded) => set({ loaded }),
11
+ setRoutes: (routes) => set({ routes })
12
+ }));
13
+ var noop = () => {
14
+ };
15
+ var noopId = (_nodeId) => {
16
+ };
17
+ var useAvoidRouterActionsStore = _zustand.create.call(void 0, (set) => ({
18
+ actions: { resetRouting: noop, updateRoutesForNodeId: noopId },
19
+ setActions: (actions) => set({ actions })
20
+ }));
21
+
22
+ // src/constants.ts
23
+ var DEV_LOG_WEB_WORKER_MESSAGES = false;
24
+ var DEBOUNCE_ROUTING_MS = 0;
25
+ var SHOULD_START_EDGE_AT_HANDLE_BORDER = true;
26
+ var EDGE_BORDER_RADIUS = 0;
27
+
28
+ // src/useAvoidNodesPath.ts
29
+ var _react = require('react');
30
+ var _react3 = require('@xyflow/react');
31
+ var RF_POS = {
32
+ left: "left",
33
+ right: "right",
34
+ top: "top",
35
+ bottom: "bottom"
36
+ };
37
+ function useAvoidNodesPath(params) {
38
+ const {
39
+ id,
40
+ sourceX,
41
+ sourceY,
42
+ targetX,
43
+ targetY,
44
+ sourcePosition,
45
+ targetPosition,
46
+ offset
47
+ } = params;
48
+ const loaded = useAvoidRoutesStore((s) => s.loaded);
49
+ const route = useAvoidRoutesStore((s) => s.routes[id]);
50
+ return _react.useMemo.call(void 0, () => {
51
+ if (loaded && route) {
52
+ return [route.path, route.labelX, route.labelY, true];
53
+ }
54
+ if (sourcePosition && targetPosition) {
55
+ const [smoothPath, sLabelX, sLabelY] = _react3.getSmoothStepPath.call(void 0, {
56
+ sourceX,
57
+ sourceY,
58
+ targetX,
59
+ targetY,
60
+ sourcePosition: RF_POS[sourcePosition],
61
+ targetPosition: RF_POS[targetPosition],
62
+ borderRadius: _nullishCoalesce(params.borderRadius, () => ( EDGE_BORDER_RADIUS)),
63
+ offset: _nullishCoalesce(offset, () => ( 20))
64
+ });
65
+ return [smoothPath, sLabelX, sLabelY, false];
66
+ }
67
+ const [straightPath, labelX, labelY] = _react3.getStraightPath.call(void 0, {
68
+ sourceX,
69
+ sourceY,
70
+ targetX,
71
+ targetY
72
+ });
73
+ return [straightPath, labelX, labelY, false];
74
+ }, [loaded, route, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, offset, params.borderRadius]);
75
+ }
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
+ exports.__publicField = __publicField; exports.useAvoidRoutesStore = useAvoidRoutesStore; exports.useAvoidRouterActionsStore = useAvoidRouterActionsStore; exports.DEV_LOG_WEB_WORKER_MESSAGES = DEV_LOG_WEB_WORKER_MESSAGES; exports.DEBOUNCE_ROUTING_MS = DEBOUNCE_ROUTING_MS; exports.SHOULD_START_EDGE_AT_HANDLE_BORDER = SHOULD_START_EDGE_AT_HANDLE_BORDER; exports.EDGE_BORDER_RADIUS = EDGE_BORDER_RADIUS; exports.useAvoidNodesPath = useAvoidNodesPath;
87
+ //# sourceMappingURL=chunk-TO34Q3ID.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/Users/awaisshah228/Documents/coding/ai-diagram-generator/avoid-nodes-pro-example/packages/avoid-nodes-edge/dist/chunk-TO34Q3ID.cjs","../src/store.ts","../src/constants.ts","../src/useAvoidNodesPath.ts"],"names":[],"mappings":"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","file":"/Users/awaisshah228/Documents/coding/ai-diagram-generator/avoid-nodes-pro-example/packages/avoid-nodes-edge/dist/chunk-TO34Q3ID.cjs","sourcesContent":[null,"import { create } from \"zustand\";\nimport type { AvoidRoute } from \"./router\";\n\nexport interface AvoidRoutesState {\n loaded: boolean;\n routes: Record<string, AvoidRoute>;\n setLoaded: (loaded: boolean) => void;\n setRoutes: (routes: Record<string, AvoidRoute>) => void;\n}\n\nexport const useAvoidRoutesStore = create<AvoidRoutesState>((set) => ({\n loaded: false,\n routes: {},\n setLoaded: (loaded) => set({ loaded }),\n setRoutes: (routes) => set({ routes }),\n}));\n\nexport interface AvoidRouterActions {\n resetRouting: () => void;\n updateRoutesForNodeId: (nodeId: string) => void;\n}\n\nconst noop = () => {};\nconst noopId = (_nodeId: string) => {};\n\nexport const useAvoidRouterActionsStore = create<{\n actions: AvoidRouterActions;\n setActions: (a: AvoidRouterActions) => void;\n}>((set) => ({\n actions: { resetRouting: noop, updateRoutesForNodeId: noopId },\n setActions: (actions) => set({ actions }),\n}));\n","/** Log web worker / routing messages in development. */\nexport const DEV_LOG_WEB_WORKER_MESSAGES = false;\n\n/**\n * Debounce (ms) before routing runs after diagram changes.\n * 10 ms merges drag events into a single update.\n * For larger diagrams (500+ nodes) use ~50-100 ms.\n */\nexport const DEBOUNCE_ROUTING_MS = 0;\n\n/** Whether edges start at handle border (true) or center (false). */\nexport const SHOULD_START_EDGE_AT_HANDLE_BORDER = true;\n\n/** Default border radius (px) for routed path corners. */\nexport const EDGE_BORDER_RADIUS = 0;\n","import { useMemo } from \"react\";\nimport { getStraightPath, getSmoothStepPath, Position as RFPosition } from \"@xyflow/react\";\nimport { useAvoidRoutesStore } from \"./store\";\nimport { EDGE_BORDER_RADIUS } from \"./constants\";\n\nexport type Position = \"left\" | \"right\" | \"top\" | \"bottom\";\n\nconst RF_POS: Record<Position, RFPosition> = {\n left: \"left\" as RFPosition,\n right: \"right\" as RFPosition,\n top: \"top\" as RFPosition,\n bottom: \"bottom\" as RFPosition,\n};\n\nexport interface UseAvoidNodesPathParams {\n id: string;\n sourceX: number;\n sourceY: number;\n targetX: number;\n targetY: number;\n sourcePosition?: Position;\n targetPosition?: Position;\n points?: { x: number; y: number }[];\n borderRadius?: number;\n offset?: number;\n}\n\n/**\n * Returns [path, labelX, labelY, wasRouted] for an avoid-nodes edge.\n * Reads from the avoid store (set by the worker); falls back to straight/smooth-step.\n */\nexport function useAvoidNodesPath(\n params: UseAvoidNodesPathParams\n): [path: string, labelX: number, labelY: number, wasRouted: boolean] {\n const {\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n offset,\n } = params;\n\n const loaded = useAvoidRoutesStore((s) => s.loaded);\n const route = useAvoidRoutesStore((s) => s.routes[id]);\n\n return useMemo(() => {\n if (loaded && route) {\n return [route.path, route.labelX, route.labelY, true];\n }\n\n if (sourcePosition && targetPosition) {\n const [smoothPath, sLabelX, sLabelY] = getSmoothStepPath({\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition: RF_POS[sourcePosition],\n targetPosition: RF_POS[targetPosition],\n borderRadius: params.borderRadius ?? EDGE_BORDER_RADIUS,\n offset: offset ?? 20,\n });\n return [smoothPath, sLabelX, sLabelY, false];\n }\n\n const [straightPath, labelX, labelY] = getStraightPath({\n sourceX,\n sourceY,\n targetX,\n targetY,\n });\n return [straightPath, labelX, labelY, false];\n }, [loaded, route, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, offset, params.borderRadius]);\n}\n"]}
@@ -0,0 +1,87 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/store.ts
6
+ import { create } from "zustand";
7
+ var useAvoidRoutesStore = create((set) => ({
8
+ loaded: false,
9
+ routes: {},
10
+ setLoaded: (loaded) => set({ loaded }),
11
+ setRoutes: (routes) => set({ routes })
12
+ }));
13
+ var noop = () => {
14
+ };
15
+ var noopId = (_nodeId) => {
16
+ };
17
+ var useAvoidRouterActionsStore = create((set) => ({
18
+ actions: { resetRouting: noop, updateRoutesForNodeId: noopId },
19
+ setActions: (actions) => set({ actions })
20
+ }));
21
+
22
+ // src/constants.ts
23
+ var DEV_LOG_WEB_WORKER_MESSAGES = false;
24
+ var DEBOUNCE_ROUTING_MS = 0;
25
+ var SHOULD_START_EDGE_AT_HANDLE_BORDER = true;
26
+ var EDGE_BORDER_RADIUS = 0;
27
+
28
+ // src/useAvoidNodesPath.ts
29
+ import { useMemo } from "react";
30
+ import { getStraightPath, getSmoothStepPath } from "@xyflow/react";
31
+ var RF_POS = {
32
+ left: "left",
33
+ right: "right",
34
+ top: "top",
35
+ bottom: "bottom"
36
+ };
37
+ function useAvoidNodesPath(params) {
38
+ const {
39
+ id,
40
+ sourceX,
41
+ sourceY,
42
+ targetX,
43
+ targetY,
44
+ sourcePosition,
45
+ targetPosition,
46
+ offset
47
+ } = params;
48
+ const loaded = useAvoidRoutesStore((s) => s.loaded);
49
+ const route = useAvoidRoutesStore((s) => s.routes[id]);
50
+ return useMemo(() => {
51
+ if (loaded && route) {
52
+ return [route.path, route.labelX, route.labelY, true];
53
+ }
54
+ if (sourcePosition && targetPosition) {
55
+ const [smoothPath, sLabelX, sLabelY] = getSmoothStepPath({
56
+ sourceX,
57
+ sourceY,
58
+ targetX,
59
+ targetY,
60
+ sourcePosition: RF_POS[sourcePosition],
61
+ targetPosition: RF_POS[targetPosition],
62
+ borderRadius: params.borderRadius ?? EDGE_BORDER_RADIUS,
63
+ offset: offset ?? 20
64
+ });
65
+ return [smoothPath, sLabelX, sLabelY, false];
66
+ }
67
+ const [straightPath, labelX, labelY] = getStraightPath({
68
+ sourceX,
69
+ sourceY,
70
+ targetX,
71
+ targetY
72
+ });
73
+ return [straightPath, labelX, labelY, false];
74
+ }, [loaded, route, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, offset, params.borderRadius]);
75
+ }
76
+
77
+ export {
78
+ __publicField,
79
+ useAvoidRoutesStore,
80
+ useAvoidRouterActionsStore,
81
+ DEV_LOG_WEB_WORKER_MESSAGES,
82
+ DEBOUNCE_ROUTING_MS,
83
+ SHOULD_START_EDGE_AT_HANDLE_BORDER,
84
+ EDGE_BORDER_RADIUS,
85
+ useAvoidNodesPath
86
+ };
87
+ //# sourceMappingURL=chunk-VPHZVUPR.js.map