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/dist/index.js ADDED
@@ -0,0 +1,558 @@
1
+ import {
2
+ DEBOUNCE_ROUTING_MS,
3
+ DEV_LOG_WEB_WORKER_MESSAGES,
4
+ EDGE_BORDER_RADIUS,
5
+ SHOULD_START_EDGE_AT_HANDLE_BORDER,
6
+ __publicField,
7
+ useAvoidNodesPath,
8
+ useAvoidRouterActionsStore,
9
+ useAvoidRoutesStore
10
+ } from "./chunk-VPHZVUPR.js";
11
+
12
+ // src/router.ts
13
+ var LIBAVOID_WASM_URL = "/libavoid.wasm";
14
+ var WASM_RETRY_DELAY_MS = 2e3;
15
+ var WASM_MAX_RETRIES = 5;
16
+ var _AvoidRouter = class _AvoidRouter {
17
+ // Loads the libavoid WASM module with retry logic.
18
+ // Retries up to WASM_MAX_RETRIES times with a delay between attempts.
19
+ // Returns true if the library loaded successfully, false otherwise.
20
+ static async load(wasmUrl = LIBAVOID_WASM_URL) {
21
+ if (_AvoidRouter.lib != null) return true;
22
+ if (typeof globalThis === "undefined") return false;
23
+ for (let attempt = 1; attempt <= WASM_MAX_RETRIES; attempt++) {
24
+ const ok = await _AvoidRouter.loadOnce(wasmUrl);
25
+ if (ok) return true;
26
+ if (attempt < WASM_MAX_RETRIES) {
27
+ await new Promise((r) => setTimeout(r, WASM_RETRY_DELAY_MS));
28
+ }
29
+ }
30
+ return false;
31
+ }
32
+ // Single attempt to load the WASM module.
33
+ // Resolves the WASM URL to an absolute path, dynamically imports libavoid-js,
34
+ // calls its load() to initialize the WASM, then stores the library instance.
35
+ static async loadOnce(wasmUrl) {
36
+ const origin = globalThis.location?.origin;
37
+ const absoluteWasmUrl = origin && wasmUrl.startsWith("/") ? `${origin}${wasmUrl}` : wasmUrl;
38
+ try {
39
+ const mod = await import("libavoid-js");
40
+ const AvoidLib = mod.AvoidLib ?? mod.default;
41
+ if (!AvoidLib?.load) return false;
42
+ await AvoidLib.load(absoluteWasmUrl);
43
+ const lib = AvoidLib.getInstance?.();
44
+ if (lib == null) return false;
45
+ _AvoidRouter.lib = lib;
46
+ return true;
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+ // Returns the singleton AvoidRouter instance.
52
+ // Throws if the WASM library hasn't been loaded yet via load().
53
+ static getInstance() {
54
+ if (_AvoidRouter.instance == null) _AvoidRouter.instance = new _AvoidRouter();
55
+ if (_AvoidRouter.lib == null) throw new Error("AvoidRouter.load() must be called first.");
56
+ return _AvoidRouter.instance;
57
+ }
58
+ // Main routing method — takes all nodes and edges, computes obstacle-avoiding
59
+ // orthogonal paths for every edge, and returns a map of edgeId -> AvoidRoute.
60
+ //
61
+ // Steps:
62
+ // 1. Filter out group nodes (only real nodes are obstacles)
63
+ // 2. Create a libavoid Router and configure its parameters
64
+ // 3. Register each node as a rectangular obstacle shape with connection pins
65
+ // 4. Create a connector (ConnRef) for each edge between source/target pins
66
+ // 5. Run the routing algorithm (processTransaction)
67
+ // 6. Extract the routed polyline for each edge and convert to SVG path
68
+ // 7. Clean up all libavoid objects to free WASM memory
69
+ routeAll(nodes, edges, options) {
70
+ const Avoid = _AvoidRouter.lib;
71
+ if (!Avoid) return {};
72
+ const shapeBuffer = options?.shapeBufferDistance ?? 8;
73
+ const idealNudging = options?.idealNudgingDistance ?? 10;
74
+ const cornerRadius = options?.edgeRounding ?? 0;
75
+ const gridSize = options?.diagramGridSize ?? 0;
76
+ const obstacleNodes = nodes.filter((n) => n.type !== "group");
77
+ const nodeById = new Map(nodes.map((n) => [n.id, n]));
78
+ const nodeBounds = new Map(obstacleNodes.map((n) => [n.id, this.getNodeBoundsAbsolute(n, nodeById)]));
79
+ const router = new Avoid.Router(Avoid.OrthogonalRouting);
80
+ router.setRoutingParameter(Avoid.shapeBufferDistance, shapeBuffer);
81
+ router.setRoutingParameter(Avoid.idealNudgingDistance, idealNudging);
82
+ router.setRoutingOption(Avoid.nudgeOrthogonalSegmentsConnectedToShapes, true);
83
+ router.setRoutingOption(Avoid.nudgeSharedPathsWithCommonEndPoint, true);
84
+ router.setRoutingOption(Avoid.performUnifyingNudgingPreprocessingStep, true);
85
+ const PIN_CENTER = 1;
86
+ const PIN_TOP = 2;
87
+ const PIN_BOTTOM = 3;
88
+ const PIN_LEFT = 4;
89
+ const PIN_RIGHT = 5;
90
+ const pinIdForPosition = {
91
+ top: PIN_TOP,
92
+ bottom: PIN_BOTTOM,
93
+ left: PIN_LEFT,
94
+ right: PIN_RIGHT
95
+ };
96
+ const pinProportions = {
97
+ [PIN_CENTER]: { x: 0.5, y: 0.5, dir: Avoid.ConnDirAll },
98
+ [PIN_TOP]: { x: 0.5, y: 0, dir: Avoid.ConnDirUp },
99
+ [PIN_BOTTOM]: { x: 0.5, y: 1, dir: Avoid.ConnDirDown },
100
+ [PIN_LEFT]: { x: 0, y: 0.5, dir: Avoid.ConnDirLeft },
101
+ [PIN_RIGHT]: { x: 1, y: 0.5, dir: Avoid.ConnDirRight }
102
+ };
103
+ const shapeRefMap = /* @__PURE__ */ new Map();
104
+ const shapeRefs = [];
105
+ for (const node of obstacleNodes) {
106
+ const b = nodeBounds.get(node.id);
107
+ const topLeft = new Avoid.Point(b.x, b.y);
108
+ const bottomRight = new Avoid.Point(b.x + b.w, b.y + b.h);
109
+ const rect = new Avoid.Rectangle(topLeft, bottomRight);
110
+ const shapeRef = new Avoid.ShapeRef(router, rect);
111
+ shapeRefs.push({ ref: shapeRef });
112
+ shapeRefMap.set(node.id, shapeRef);
113
+ for (const pinId of [PIN_CENTER, PIN_TOP, PIN_BOTTOM, PIN_LEFT, PIN_RIGHT]) {
114
+ const p = pinProportions[pinId];
115
+ const pin = new Avoid.ShapeConnectionPin(shapeRef, pinId, p.x, p.y, true, 0, p.dir);
116
+ pin.setExclusive(false);
117
+ }
118
+ }
119
+ const connRefs = [];
120
+ for (const edge of edges) {
121
+ const src = nodeById.get(edge.source);
122
+ const tgt = nodeById.get(edge.target);
123
+ if (!src || !tgt) continue;
124
+ const srcShapeRef = shapeRefMap.get(edge.source);
125
+ const tgtShapeRef = shapeRefMap.get(edge.target);
126
+ const sourcePos = this.getHandlePosition(src, "source");
127
+ const targetPos = this.getHandlePosition(tgt, "target");
128
+ let srcEnd;
129
+ let tgtEnd;
130
+ if (srcShapeRef) {
131
+ const pinId = pinIdForPosition[sourcePos] ?? PIN_CENTER;
132
+ srcEnd = new Avoid.ConnEnd(srcShapeRef, pinId);
133
+ } else {
134
+ const sb = this.getNodeBoundsAbsolute(src, nodeById);
135
+ const sourcePt = this.getHandlePoint(sb, sourcePos);
136
+ srcEnd = new Avoid.ConnEnd(new Avoid.Point(sourcePt.x, sourcePt.y));
137
+ }
138
+ if (tgtShapeRef) {
139
+ const pinId = pinIdForPosition[targetPos] ?? PIN_CENTER;
140
+ tgtEnd = new Avoid.ConnEnd(tgtShapeRef, pinId);
141
+ } else {
142
+ const tb = this.getNodeBoundsAbsolute(tgt, nodeById);
143
+ const targetPt = this.getHandlePoint(tb, targetPos);
144
+ tgtEnd = new Avoid.ConnEnd(new Avoid.Point(targetPt.x, targetPt.y));
145
+ }
146
+ const connRef = new Avoid.ConnRef(router, srcEnd, tgtEnd);
147
+ connRef.setRoutingType(Avoid.ConnType_Orthogonal);
148
+ connRefs.push({ edgeId: edge.id, connRef });
149
+ }
150
+ try {
151
+ router.processTransaction();
152
+ } catch {
153
+ this.cleanup(router, connRefs, shapeRefs);
154
+ return {};
155
+ }
156
+ const result = {};
157
+ for (const { edgeId, connRef } of connRefs) {
158
+ try {
159
+ const route = connRef.displayRoute();
160
+ const size = route.size();
161
+ if (size < 2) continue;
162
+ const path = this.polylineToPath(size, (i) => {
163
+ const p = route.get_ps(i);
164
+ return { x: p.x, y: p.y };
165
+ }, { gridSize: gridSize || void 0, cornerRadius });
166
+ const mid = Math.floor(size / 2);
167
+ const midP = route.get_ps(mid);
168
+ const labelP = gridSize > 0 ? this.snapToGrid(midP.x, midP.y, gridSize) : { x: midP.x, y: midP.y };
169
+ result[edgeId] = { path, labelX: labelP.x, labelY: labelP.y };
170
+ } catch {
171
+ }
172
+ }
173
+ this.cleanup(router, connRefs, shapeRefs);
174
+ return result;
175
+ }
176
+ // Gets the local position and dimensions of a node.
177
+ // Tries measured size first, then explicit width/height, then style, with fallback defaults.
178
+ getNodeBounds(node) {
179
+ const x = node.position?.x ?? 0;
180
+ const y = node.position?.y ?? 0;
181
+ const w = Number(node.measured?.width ?? node.width ?? node.style?.width ?? 150);
182
+ const h = Number(node.measured?.height ?? node.height ?? node.style?.height ?? 50);
183
+ return { x, y, w, h };
184
+ }
185
+ // Computes the absolute (world-space) bounds of a node by walking up
186
+ // the parent chain and accumulating parent positions. This is needed
187
+ // because child nodes have positions relative to their parent.
188
+ getNodeBoundsAbsolute(node, nodeById) {
189
+ const b = this.getNodeBounds(node);
190
+ let current = node;
191
+ while (current?.parentId) {
192
+ const parent = nodeById.get(current.parentId);
193
+ if (!parent) break;
194
+ b.x += parent.position?.x ?? 0;
195
+ b.y += parent.position?.y ?? 0;
196
+ current = parent;
197
+ }
198
+ return b;
199
+ }
200
+ // Determines which side of a node a handle is on (left/right/top/bottom).
201
+ // Checks both the node's direct properties and its data object.
202
+ // Defaults to "right" for source handles and "left" for target handles.
203
+ getHandlePosition(node, kind) {
204
+ const raw = kind === "source" ? node.sourcePosition ?? node.data?.sourcePosition : node.targetPosition ?? node.data?.targetPosition;
205
+ const s = String(raw ?? "").toLowerCase();
206
+ if (s === "left" || s === "right" || s === "top" || s === "bottom") return s;
207
+ return kind === "source" ? "right" : "left";
208
+ }
209
+ // Converts a handle position (left/right/top/bottom) into an actual x/y coordinate
210
+ // on the node's boundary. The point is at the center of the respective edge.
211
+ getHandlePoint(bounds, position) {
212
+ const { x, y, w, h } = bounds;
213
+ const cx = x + w / 2;
214
+ const cy = y + h / 2;
215
+ switch (position) {
216
+ case "left":
217
+ return { x, y: cy };
218
+ case "right":
219
+ return { x: x + w, y: cy };
220
+ case "top":
221
+ return { x: cx, y };
222
+ case "bottom":
223
+ return { x: cx, y: y + h };
224
+ default:
225
+ return { x: x + w, y: cy };
226
+ }
227
+ }
228
+ // Snaps x/y coordinates to the nearest grid point (rounds to nearest multiple of gridSize)
229
+ snapToGrid(x, y, gridSize) {
230
+ if (gridSize <= 0) return { x, y };
231
+ return { x: Math.round(x / gridSize) * gridSize, y: Math.round(y / gridSize) * gridSize };
232
+ }
233
+ // Converts a polyline (series of waypoints) into an SVG path string.
234
+ // If cornerRadius > 0, adds quadratic bezier curves (Q commands) at each bend
235
+ // to create smooth rounded corners instead of sharp right angles.
236
+ // If cornerRadius is 0, produces a simple M/L path with straight segments.
237
+ polylineToPath(size, getPoint, options = {}) {
238
+ if (size < 2) return "";
239
+ const gridSize = options.gridSize ?? 0;
240
+ const r = Math.max(0, options.cornerRadius ?? 0);
241
+ const pt = (i) => {
242
+ const p = getPoint(i);
243
+ return gridSize > 0 ? this.snapToGrid(p.x, p.y, gridSize) : p;
244
+ };
245
+ if (r <= 0) {
246
+ let d2 = `M ${pt(0).x} ${pt(0).y}`;
247
+ for (let i = 1; i < size; i++) {
248
+ const p = pt(i);
249
+ d2 += ` L ${p.x} ${p.y}`;
250
+ }
251
+ return d2;
252
+ }
253
+ const dist = (a, b) => Math.hypot(b.x - a.x, b.y - a.y);
254
+ const unit = (a, b) => {
255
+ const d2 = dist(a, b);
256
+ if (d2 < 1e-6) return { x: 0, y: 0 };
257
+ return { x: (b.x - a.x) / d2, y: (b.y - a.y) / d2 };
258
+ };
259
+ let d = `M ${pt(0).x} ${pt(0).y}`;
260
+ for (let i = 1; i < size - 1; i++) {
261
+ const prev = pt(i - 1);
262
+ const curr = pt(i);
263
+ const next = pt(i + 1);
264
+ const dirIn = unit(curr, prev);
265
+ const dirOut = unit(curr, next);
266
+ const lenIn = dist(curr, prev);
267
+ const lenOut = dist(curr, next);
268
+ const rIn = Math.min(r, lenIn / 2, lenOut / 2);
269
+ const rOut = Math.min(r, lenIn / 2, lenOut / 2);
270
+ const endPrev = { x: curr.x + dirIn.x * rIn, y: curr.y + dirIn.y * rIn };
271
+ const startNext = { x: curr.x + dirOut.x * rOut, y: curr.y + dirOut.y * rOut };
272
+ d += ` L ${endPrev.x} ${endPrev.y} Q ${curr.x} ${curr.y} ${startNext.x} ${startNext.y}`;
273
+ }
274
+ const last = pt(size - 1);
275
+ d += ` L ${last.x} ${last.y}`;
276
+ return d;
277
+ }
278
+ // Frees all libavoid objects (connectors and shapes) from the router.
279
+ // This is important to prevent WASM memory leaks since libavoid
280
+ // allocates memory in the WASM heap that isn't garbage collected by JS.
281
+ cleanup(router, connRefs, shapeRefs) {
282
+ try {
283
+ for (const { connRef } of connRefs) router.deleteConnector(connRef);
284
+ for (const { ref } of shapeRefs) router.deleteShape(ref);
285
+ } catch {
286
+ }
287
+ }
288
+ };
289
+ // Singleton references — the WASM library and the single router instance
290
+ __publicField(_AvoidRouter, "lib", null);
291
+ __publicField(_AvoidRouter, "instance", null);
292
+ var AvoidRouter = _AvoidRouter;
293
+ async function loadAvoidRouter() {
294
+ return AvoidRouter.load();
295
+ }
296
+ function routeAll(nodes, edges, options) {
297
+ try {
298
+ return AvoidRouter.getInstance().routeAll(nodes, edges, options);
299
+ } catch {
300
+ return {};
301
+ }
302
+ }
303
+
304
+ // src/useAvoidNodesRouterFromWorker.ts
305
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2 } from "react";
306
+
307
+ // src/useAvoidWorker.ts
308
+ import { useCallback, useEffect, useRef, useState } from "react";
309
+
310
+ // src/worker-listener.ts
311
+ function attachAvoidWorkerListener(worker, options = {}) {
312
+ const { onRouted, onLoaded } = options;
313
+ const setLoaded = useAvoidRoutesStore.getState().setLoaded;
314
+ const setRoutes = useAvoidRoutesStore.getState().setRoutes;
315
+ const handler = (e) => {
316
+ const msg = e.data;
317
+ if (!msg || typeof msg !== "object" || !("command" in msg)) return;
318
+ switch (msg.command) {
319
+ case "loaded":
320
+ setLoaded(msg.success);
321
+ onLoaded?.(msg.success);
322
+ break;
323
+ case "routed":
324
+ setRoutes(msg.routes);
325
+ onRouted?.(msg.routes);
326
+ break;
327
+ default:
328
+ break;
329
+ }
330
+ };
331
+ worker.addEventListener("message", handler);
332
+ return () => worker.removeEventListener("message", handler);
333
+ }
334
+
335
+ // src/useAvoidWorker.ts
336
+ function useAvoidWorker(options) {
337
+ const workerRef = useRef(null);
338
+ const [workerLoaded, setWorkerLoaded] = useState(false);
339
+ const onRoutedRef = useRef(options?.onRouted);
340
+ const onLoadedRef = useRef(options?.onLoaded);
341
+ onRoutedRef.current = options?.onRouted;
342
+ onLoadedRef.current = options?.onLoaded;
343
+ const createWorker = options?.create !== false;
344
+ useEffect(() => {
345
+ if (!createWorker) {
346
+ workerRef.current = null;
347
+ setWorkerLoaded(false);
348
+ return;
349
+ }
350
+ let worker;
351
+ try {
352
+ worker = new Worker(new URL("./workers/avoid-router.worker.js", import.meta.url), { type: "module" });
353
+ } catch (e) {
354
+ console.error("[avoid-worker] Failed to create worker:", e);
355
+ return;
356
+ }
357
+ workerRef.current = worker;
358
+ worker.addEventListener("error", (e) => {
359
+ console.error("[avoid-worker] Worker error:", e.message);
360
+ });
361
+ const cleanup = attachAvoidWorkerListener(worker, {
362
+ onRouted: (routes) => onRoutedRef.current?.(routes),
363
+ onLoaded: (success) => {
364
+ setWorkerLoaded(success);
365
+ onLoadedRef.current?.(success);
366
+ }
367
+ });
368
+ return () => {
369
+ cleanup();
370
+ worker.postMessage({ command: "close" });
371
+ worker.terminate();
372
+ workerRef.current = null;
373
+ setWorkerLoaded(false);
374
+ };
375
+ }, [createWorker]);
376
+ const post = useCallback((cmd) => {
377
+ if (workerRef.current) {
378
+ workerRef.current.postMessage(cmd);
379
+ }
380
+ }, []);
381
+ const close = useCallback(() => {
382
+ if (workerRef.current) {
383
+ workerRef.current.postMessage({ command: "close" });
384
+ workerRef.current.terminate();
385
+ workerRef.current = null;
386
+ setWorkerLoaded(false);
387
+ }
388
+ }, []);
389
+ return { workerLoaded, post, close };
390
+ }
391
+
392
+ // src/useAvoidNodesRouterFromWorker.ts
393
+ var DEFAULT_OPTIONS = {
394
+ edgeToEdgeSpacing: 10,
395
+ edgeToNodeSpacing: 8
396
+ };
397
+ function toRouterOptions(opts) {
398
+ return {
399
+ idealNudgingDistance: opts?.edgeToEdgeSpacing ?? DEFAULT_OPTIONS.edgeToEdgeSpacing,
400
+ shapeBufferDistance: opts?.edgeToNodeSpacing ?? DEFAULT_OPTIONS.edgeToNodeSpacing,
401
+ edgeRounding: opts?.edgeRounding,
402
+ diagramGridSize: opts?.diagramGridSize,
403
+ shouldSplitEdgesNearHandle: opts?.shouldSplitEdgesNearHandle
404
+ };
405
+ }
406
+ function useAvoidNodesRouterFromWorker(nodes, edges, options) {
407
+ const nodesRef = useRef2(nodes);
408
+ const edgesRef = useRef2(edges);
409
+ const opts = toRouterOptions(options);
410
+ const optsRef = useRef2(opts);
411
+ useEffect2(() => {
412
+ nodesRef.current = nodes;
413
+ }, [nodes]);
414
+ useEffect2(() => {
415
+ edgesRef.current = edges;
416
+ }, [edges]);
417
+ useEffect2(() => {
418
+ optsRef.current = opts;
419
+ });
420
+ const setRoutes = useAvoidRoutesStore((s) => s.setRoutes);
421
+ const setActions = useAvoidRouterActionsStore((s) => s.setActions);
422
+ const { post, workerLoaded } = useAvoidWorker({ create: true });
423
+ const didResetRef = useRef2(false);
424
+ const nodesMeasuredRef = useRef2(false);
425
+ const sendReset = useCallback2(() => {
426
+ if (!workerLoaded) return;
427
+ const nodes2 = nodesRef.current;
428
+ const hasMeasured = nodes2.length === 0 || nodes2.some((n) => n.measured?.width != null);
429
+ if (!hasMeasured) return;
430
+ nodesMeasuredRef.current = true;
431
+ const avoidEdges = edgesRef.current.filter((e) => e.type === "avoidNodes");
432
+ if (avoidEdges.length === 0) {
433
+ setRoutes({});
434
+ return;
435
+ }
436
+ post({
437
+ command: "reset",
438
+ nodes: nodes2,
439
+ edges: avoidEdges,
440
+ options: optsRef.current
441
+ });
442
+ didResetRef.current = true;
443
+ }, [post, setRoutes, workerLoaded]);
444
+ const sendIncrementalChanges = useCallback2(
445
+ (nodeIds) => {
446
+ if (!workerLoaded || !didResetRef.current) return;
447
+ const nodeMap = new Map(nodesRef.current.map((n) => [n.id, n]));
448
+ const changedNodes = nodeIds.map((id) => nodeMap.get(id)).filter((n) => n != null);
449
+ if (changedNodes.length === 0) return;
450
+ post({ command: "updateNodes", nodes: changedNodes });
451
+ },
452
+ [post, workerLoaded]
453
+ );
454
+ const debounceRef = useRef2(null);
455
+ const pendingChangeIdsRef = useRef2(/* @__PURE__ */ new Set());
456
+ const resetRouting = useCallback2(() => {
457
+ sendReset();
458
+ }, [sendReset]);
459
+ const refreshRouting = useCallback2(() => {
460
+ sendReset();
461
+ }, [sendReset]);
462
+ const updateRoutingForNodeIds = useCallback2(
463
+ (nodeIds) => {
464
+ sendIncrementalChanges(nodeIds);
465
+ },
466
+ [sendIncrementalChanges]
467
+ );
468
+ const updateRoutingOnNodesChange = useCallback2(
469
+ (changes) => {
470
+ if (!workerLoaded) return;
471
+ let hasPosition = false;
472
+ let hasDimensions = false;
473
+ let hasAddOrRemove = false;
474
+ for (const c of changes) {
475
+ if (c.type === "position") {
476
+ hasPosition = true;
477
+ pendingChangeIdsRef.current.add(c.id);
478
+ } else if (c.type === "dimensions") {
479
+ hasDimensions = true;
480
+ pendingChangeIdsRef.current.add(c.id);
481
+ } else if (c.type === "add" || c.type === "remove") {
482
+ hasAddOrRemove = true;
483
+ }
484
+ }
485
+ if (!hasPosition && !hasDimensions && !hasAddOrRemove) return;
486
+ const needsFullReset = hasAddOrRemove || hasDimensions && !nodesMeasuredRef.current;
487
+ if (needsFullReset) {
488
+ if (debounceRef.current) {
489
+ clearTimeout(debounceRef.current);
490
+ debounceRef.current = null;
491
+ }
492
+ pendingChangeIdsRef.current.clear();
493
+ debounceRef.current = setTimeout(() => {
494
+ debounceRef.current = null;
495
+ requestAnimationFrame(() => sendReset());
496
+ }, DEBOUNCE_ROUTING_MS);
497
+ return;
498
+ }
499
+ if (!didResetRef.current) return;
500
+ if (debounceRef.current) clearTimeout(debounceRef.current);
501
+ debounceRef.current = setTimeout(() => {
502
+ debounceRef.current = null;
503
+ requestAnimationFrame(() => {
504
+ const ids = Array.from(pendingChangeIdsRef.current);
505
+ pendingChangeIdsRef.current.clear();
506
+ if (ids.length > 0) {
507
+ sendIncrementalChanges(ids);
508
+ }
509
+ });
510
+ }, DEBOUNCE_ROUTING_MS);
511
+ },
512
+ [workerLoaded, sendReset, sendIncrementalChanges]
513
+ );
514
+ useEffect2(() => {
515
+ setActions({
516
+ resetRouting,
517
+ updateRoutesForNodeId: (nodeId) => updateRoutingForNodeIds([nodeId])
518
+ });
519
+ return () => setActions({ resetRouting: () => {
520
+ }, updateRoutesForNodeId: () => {
521
+ } });
522
+ }, [resetRouting, updateRoutingForNodeIds, setActions]);
523
+ useEffect2(() => {
524
+ if (workerLoaded) sendReset();
525
+ }, [
526
+ workerLoaded,
527
+ nodes.length,
528
+ edges.length,
529
+ opts.shapeBufferDistance,
530
+ opts.idealNudgingDistance,
531
+ opts.edgeRounding,
532
+ opts.diagramGridSize,
533
+ opts.shouldSplitEdgesNearHandle,
534
+ sendReset
535
+ ]);
536
+ return {
537
+ updateRoutingOnNodesChange,
538
+ resetRouting,
539
+ refreshRouting,
540
+ updateRoutingForNodeIds
541
+ };
542
+ }
543
+ export {
544
+ AvoidRouter,
545
+ DEBOUNCE_ROUTING_MS,
546
+ DEV_LOG_WEB_WORKER_MESSAGES,
547
+ EDGE_BORDER_RADIUS,
548
+ SHOULD_START_EDGE_AT_HANDLE_BORDER,
549
+ attachAvoidWorkerListener,
550
+ loadAvoidRouter,
551
+ routeAll,
552
+ useAvoidNodesPath,
553
+ useAvoidNodesRouterFromWorker,
554
+ useAvoidRouterActionsStore,
555
+ useAvoidRoutesStore,
556
+ useAvoidWorker
557
+ };
558
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/router.ts","../src/useAvoidNodesRouterFromWorker.ts","../src/useAvoidWorker.ts","../src/worker-listener.ts"],"sourcesContent":["/**\n * React Flow integration for libavoid-js: route edges so they avoid nodes (excluding group nodes).\n * Use AvoidRouter.load() once, then AvoidRouter.getInstance().routeAll(nodes, edges).\n */\n\n// Import React Flow types for nodes and edges used throughout the router\nimport type { Node, Edge } from \"@xyflow/react\";\n\n// Represents the routed result for a single edge — the SVG path string and the x/y position for a label\nexport type AvoidRoute = { path: string; labelX: number; labelY: number };\n\n// Configuration options that control how edges are routed around nodes\nexport type AvoidRouterOptions = {\n shapeBufferDistance?: number; // Extra padding around each node that edges must avoid\n idealNudgingDistance?: number; // How far apart parallel edge segments should be nudged\n edgeRounding?: number; // Corner radius for rounded orthogonal bends\n diagramGridSize?: number; // Snap edge waypoints to a grid of this size\n shouldSplitEdgesNearHandle?: boolean;\n};\n\n// Which side of a node a handle (connection point) is on\nexport type HandlePosition = \"left\" | \"right\" | \"top\" | \"bottom\";\n\n// Default URL to load the libavoid WebAssembly binary from\nconst LIBAVOID_WASM_URL = \"/libavoid.wasm\";\n// How long to wait between retries if WASM loading fails (in milliseconds)\nconst WASM_RETRY_DELAY_MS = 2000;\n// Maximum number of times to retry loading the WASM module\nconst WASM_MAX_RETRIES = 5;\n\n// Type definition for the libavoid-js WASM library instance.\n// Maps all the constructors (Router, Point, Rectangle, etc.) and constants\n// (direction flags, routing options) that we use from the C++ libavoid library.\ntype AvoidLibInstance = {\n Router: new (flags: number) => unknown;\n Point: new (x: number, y: number) => { x: number; y: number };\n Rectangle: new (a: unknown, b: unknown) => unknown;\n ShapeRef: new (router: unknown, poly: unknown) => unknown;\n ShapeConnectionPin: new (\n shapeRef: unknown,\n classId: number,\n xProportion: number,\n yProportion: number,\n proportional: boolean,\n insideOffset: number,\n directions: number\n ) => { setExclusive: (exclusive: boolean) => void };\n ConnEnd: new (shapeRefOrPoint: unknown, pinClassId?: number) => unknown;\n ConnRef: new (router: unknown, src?: unknown, dst?: unknown) => {\n setRoutingType: (t: number) => void;\n displayRoute: () => { size: () => number; get_ps: (i: number) => { x: number; y: number } };\n };\n OrthogonalRouting: number;\n ConnType_Orthogonal: number;\n ConnDirUp: number;\n ConnDirDown: number;\n ConnDirLeft: number;\n ConnDirRight: number;\n ConnDirAll: number;\n shapeBufferDistance: number;\n idealNudgingDistance: number;\n nudgeOrthogonalSegmentsConnectedToShapes: number;\n nudgeSharedPathsWithCommonEndPoint: number;\n performUnifyingNudgingPreprocessingStep: number;\n};\n\n/**\n * AvoidRouter: routes diagram edges around nodes using libavoid-js (WASM).\n * Use static load() once, then getInstance().routeAll(nodes, edges).\n */\nexport class AvoidRouter {\n // Singleton references — the WASM library and the single router instance\n private static lib: AvoidLibInstance | null = null;\n private static instance: AvoidRouter | null = null;\n\n // Loads the libavoid WASM module with retry logic.\n // Retries up to WASM_MAX_RETRIES times with a delay between attempts.\n // Returns true if the library loaded successfully, false otherwise.\n static async load(wasmUrl: string = LIBAVOID_WASM_URL): Promise<boolean> {\n if (AvoidRouter.lib != null) return true;\n if (typeof globalThis === \"undefined\") return false;\n for (let attempt = 1; attempt <= WASM_MAX_RETRIES; attempt++) {\n const ok = await AvoidRouter.loadOnce(wasmUrl);\n if (ok) return true;\n if (attempt < WASM_MAX_RETRIES) {\n await new Promise((r) => setTimeout(r, WASM_RETRY_DELAY_MS));\n }\n }\n return false;\n }\n\n // Single attempt to load the WASM module.\n // Resolves the WASM URL to an absolute path, dynamically imports libavoid-js,\n // calls its load() to initialize the WASM, then stores the library instance.\n private static async loadOnce(wasmUrl: string): Promise<boolean> {\n const origin = (globalThis as unknown as { location?: { origin?: string } }).location?.origin;\n const absoluteWasmUrl = origin && wasmUrl.startsWith(\"/\") ? `${origin}${wasmUrl}` : wasmUrl;\n\n try {\n const mod = (await import(\"libavoid-js\")) as unknown as {\n default?: { load?: (filePath?: string) => Promise<void>; getInstance?: () => AvoidLibInstance };\n AvoidLib?: { load?: (filePath?: string) => Promise<void>; getInstance?: () => AvoidLibInstance };\n };\n const AvoidLib = mod.AvoidLib ?? mod.default;\n if (!AvoidLib?.load) return false;\n await AvoidLib.load(absoluteWasmUrl);\n const lib = AvoidLib.getInstance?.();\n if (lib == null) return false;\n AvoidRouter.lib = lib;\n return true;\n } catch {\n return false;\n }\n }\n\n // Returns the singleton AvoidRouter instance.\n // Throws if the WASM library hasn't been loaded yet via load().\n static getInstance(): AvoidRouter {\n if (AvoidRouter.instance == null) AvoidRouter.instance = new AvoidRouter();\n if (AvoidRouter.lib == null) throw new Error(\"AvoidRouter.load() must be called first.\");\n return AvoidRouter.instance;\n }\n\n // Main routing method — takes all nodes and edges, computes obstacle-avoiding\n // orthogonal paths for every edge, and returns a map of edgeId -> AvoidRoute.\n //\n // Steps:\n // 1. Filter out group nodes (only real nodes are obstacles)\n // 2. Create a libavoid Router and configure its parameters\n // 3. Register each node as a rectangular obstacle shape with connection pins\n // 4. Create a connector (ConnRef) for each edge between source/target pins\n // 5. Run the routing algorithm (processTransaction)\n // 6. Extract the routed polyline for each edge and convert to SVG path\n // 7. Clean up all libavoid objects to free WASM memory\n routeAll(nodes: Node[], edges: Edge[], options?: AvoidRouterOptions): Record<string, AvoidRoute> {\n const Avoid = AvoidRouter.lib;\n if (!Avoid) return {};\n\n // Extract options with defaults\n const shapeBuffer = options?.shapeBufferDistance ?? 8;\n const idealNudging = options?.idealNudgingDistance ?? 10;\n const cornerRadius = options?.edgeRounding ?? 0;\n const gridSize = options?.diagramGridSize ?? 0;\n\n // Filter out group nodes — they aren't obstacles, only containers.\n // Build lookup maps for quick node access and pre-compute absolute bounds.\n const obstacleNodes = nodes.filter((n) => n.type !== \"group\");\n const nodeById = new Map(nodes.map((n) => [n.id, n]));\n const nodeBounds = new Map(obstacleNodes.map((n) => [n.id, this.getNodeBoundsAbsolute(n, nodeById)]));\n\n // Create the libavoid orthogonal router and configure its routing parameters\n const router = new Avoid.Router(Avoid.OrthogonalRouting) as {\n setRoutingParameter: (p: number, v: number) => void;\n setRoutingOption: (o: number, v: boolean) => void;\n processTransaction: () => void;\n deleteConnector: (c: unknown) => void;\n deleteShape: (s: unknown) => void;\n };\n router.setRoutingParameter(Avoid.shapeBufferDistance, shapeBuffer);\n router.setRoutingParameter(Avoid.idealNudgingDistance, idealNudging);\n router.setRoutingOption(Avoid.nudgeOrthogonalSegmentsConnectedToShapes, true);\n router.setRoutingOption(Avoid.nudgeSharedPathsWithCommonEndPoint, true);\n router.setRoutingOption(Avoid.performUnifyingNudgingPreprocessingStep, true);\n\n // Pin class IDs — each represents a connection point position on a shape.\n // These IDs are used by libavoid to know where edges can attach to nodes.\n const PIN_CENTER = 1;\n const PIN_TOP = 2;\n const PIN_BOTTOM = 3;\n const PIN_LEFT = 4;\n const PIN_RIGHT = 5;\n\n // Maps handle positions (top/bottom/left/right) to their pin class IDs\n const pinIdForPosition: Record<HandlePosition, number> = {\n top: PIN_TOP,\n bottom: PIN_BOTTOM,\n left: PIN_LEFT,\n right: PIN_RIGHT,\n };\n\n // Defines the proportional x/y position on the shape boundary and\n // the allowed connection direction for each pin type.\n // e.g. PIN_TOP is at (0.5, 0) = top-center, only allows upward connections.\n const pinProportions: Record<number, { x: number; y: number; dir: number }> = {\n [PIN_CENTER]: { x: 0.5, y: 0.5, dir: Avoid.ConnDirAll },\n [PIN_TOP]: { x: 0.5, y: 0, dir: Avoid.ConnDirUp },\n [PIN_BOTTOM]: { x: 0.5, y: 1, dir: Avoid.ConnDirDown },\n [PIN_LEFT]: { x: 0, y: 0.5, dir: Avoid.ConnDirLeft },\n [PIN_RIGHT]: { x: 1, y: 0.5, dir: Avoid.ConnDirRight },\n };\n\n // Register each obstacle node as a rectangle shape in the libavoid router.\n // For each shape, create 5 connection pins (center, top, bottom, left, right)\n // so edges can attach at any of these positions.\n const shapeRefMap = new Map<string, unknown>();\n const shapeRefs: { ref: unknown }[] = [];\n for (const node of obstacleNodes) {\n const b = nodeBounds.get(node.id)!;\n const topLeft = new Avoid.Point(b.x, b.y);\n const bottomRight = new Avoid.Point(b.x + b.w, b.y + b.h);\n const rect = new Avoid.Rectangle(topLeft, bottomRight);\n const shapeRef = new Avoid.ShapeRef(router, rect);\n shapeRefs.push({ ref: shapeRef });\n shapeRefMap.set(node.id, shapeRef);\n\n for (const pinId of [PIN_CENTER, PIN_TOP, PIN_BOTTOM, PIN_LEFT, PIN_RIGHT]) {\n const p = pinProportions[pinId];\n const pin = new Avoid.ShapeConnectionPin(shapeRef, pinId, p.x, p.y, true, 0, p.dir);\n pin.setExclusive(false);\n }\n }\n\n // Create a connector (ConnRef) for each edge.\n // Determines the source and target connection points:\n // - If the node is an obstacle, connect via its shape pin (attached routing)\n // - If the node is a group (not an obstacle), connect via a free-floating point\n // Each connector is set to orthogonal routing (right-angle segments only).\n const connRefs: { edgeId: string; connRef: unknown }[] = [];\n for (const edge of edges) {\n const src = nodeById.get(edge.source);\n const tgt = nodeById.get(edge.target);\n if (!src || !tgt) continue;\n\n const srcShapeRef = shapeRefMap.get(edge.source);\n const tgtShapeRef = shapeRefMap.get(edge.target);\n\n const sourcePos = this.getHandlePosition(src, \"source\");\n const targetPos = this.getHandlePosition(tgt, \"target\");\n\n let srcEnd: unknown;\n let tgtEnd: unknown;\n\n if (srcShapeRef) {\n const pinId = pinIdForPosition[sourcePos] ?? PIN_CENTER;\n srcEnd = new Avoid.ConnEnd(srcShapeRef, pinId);\n } else {\n const sb = this.getNodeBoundsAbsolute(src, nodeById);\n const sourcePt = this.getHandlePoint(sb, sourcePos);\n srcEnd = new Avoid.ConnEnd(new Avoid.Point(sourcePt.x, sourcePt.y));\n }\n\n if (tgtShapeRef) {\n const pinId = pinIdForPosition[targetPos] ?? PIN_CENTER;\n tgtEnd = new Avoid.ConnEnd(tgtShapeRef, pinId);\n } else {\n const tb = this.getNodeBoundsAbsolute(tgt, nodeById);\n const targetPt = this.getHandlePoint(tb, targetPos);\n tgtEnd = new Avoid.ConnEnd(new Avoid.Point(targetPt.x, targetPt.y));\n }\n\n const connRef = new Avoid.ConnRef(router, srcEnd, tgtEnd);\n connRef.setRoutingType(Avoid.ConnType_Orthogonal);\n connRefs.push({ edgeId: edge.id, connRef });\n }\n\n // Run the libavoid routing algorithm — this computes all edge paths at once,\n // finding routes that avoid overlapping with obstacle shapes.\n try {\n router.processTransaction();\n } catch {\n this.cleanup(router, connRefs, shapeRefs);\n return {};\n }\n\n // Extract the computed route for each edge.\n // Convert the polyline (list of waypoints) into an SVG path string,\n // and pick the midpoint as the label position.\n const result: Record<string, AvoidRoute> = {};\n for (const { edgeId, connRef } of connRefs) {\n try {\n const route = (connRef as { displayRoute(): { size(): number; get_ps(i: number): { x: number; y: number } } }).displayRoute();\n const size = route.size();\n if (size < 2) continue;\n const path = this.polylineToPath(size, (i) => {\n const p = route.get_ps(i);\n return { x: p.x, y: p.y };\n }, { gridSize: gridSize || undefined, cornerRadius });\n const mid = Math.floor(size / 2);\n const midP = route.get_ps(mid);\n const labelP = gridSize > 0 ? this.snapToGrid(midP.x, midP.y, gridSize) : { x: midP.x, y: midP.y };\n result[edgeId] = { path, labelX: labelP.x, labelY: labelP.y };\n } catch {\n // skip\n }\n }\n\n this.cleanup(router, connRefs, shapeRefs);\n return result;\n }\n\n // Gets the local position and dimensions of a node.\n // Tries measured size first, then explicit width/height, then style, with fallback defaults.\n private getNodeBounds(node: Node): { x: number; y: number; w: number; h: number } {\n const x = node.position?.x ?? 0;\n const y = node.position?.y ?? 0;\n const w = Number((node.measured?.width ?? node.width ?? (node.style as { width?: number })?.width) ?? 150);\n const h = Number((node.measured?.height ?? node.height ?? (node.style as { height?: number })?.height) ?? 50);\n return { x, y, w, h };\n }\n\n // Computes the absolute (world-space) bounds of a node by walking up\n // the parent chain and accumulating parent positions. This is needed\n // because child nodes have positions relative to their parent.\n private getNodeBoundsAbsolute(\n node: Node,\n nodeById: Map<string, Node>\n ): { x: number; y: number; w: number; h: number } {\n const b = this.getNodeBounds(node);\n let current: Node | undefined = node;\n while (current?.parentId) {\n const parent = nodeById.get(current.parentId);\n if (!parent) break;\n b.x += parent.position?.x ?? 0;\n b.y += parent.position?.y ?? 0;\n current = parent;\n }\n return b;\n }\n\n // Determines which side of a node a handle is on (left/right/top/bottom).\n // Checks both the node's direct properties and its data object.\n // Defaults to \"right\" for source handles and \"left\" for target handles.\n private getHandlePosition(node: Node, kind: \"source\" | \"target\"): HandlePosition {\n const raw =\n kind === \"source\"\n ? (node.sourcePosition as string | undefined) ?? (node as { data?: { sourcePosition?: string } }).data?.sourcePosition\n : (node.targetPosition as string | undefined) ?? (node as { data?: { targetPosition?: string } }).data?.targetPosition;\n const s = String(raw ?? \"\").toLowerCase();\n if (s === \"left\" || s === \"right\" || s === \"top\" || s === \"bottom\") return s;\n return kind === \"source\" ? \"right\" : \"left\";\n }\n\n // Converts a handle position (left/right/top/bottom) into an actual x/y coordinate\n // on the node's boundary. The point is at the center of the respective edge.\n private getHandlePoint(\n bounds: { x: number; y: number; w: number; h: number },\n position: HandlePosition\n ): { x: number; y: number } {\n const { x, y, w, h } = bounds;\n const cx = x + w / 2;\n const cy = y + h / 2;\n switch (position) {\n case \"left\":\n return { x, y: cy };\n case \"right\":\n return { x: x + w, y: cy };\n case \"top\":\n return { x: cx, y };\n case \"bottom\":\n return { x: cx, y: y + h };\n default:\n return { x: x + w, y: cy };\n }\n }\n\n // Snaps x/y coordinates to the nearest grid point (rounds to nearest multiple of gridSize)\n private snapToGrid(x: number, y: number, gridSize: number): { x: number; y: number } {\n if (gridSize <= 0) return { x, y };\n return { x: Math.round(x / gridSize) * gridSize, y: Math.round(y / gridSize) * gridSize };\n }\n\n // Converts a polyline (series of waypoints) into an SVG path string.\n // If cornerRadius > 0, adds quadratic bezier curves (Q commands) at each bend\n // to create smooth rounded corners instead of sharp right angles.\n // If cornerRadius is 0, produces a simple M/L path with straight segments.\n private polylineToPath(\n size: number,\n getPoint: (i: number) => { x: number; y: number },\n options: { gridSize?: number; cornerRadius?: number } = {}\n ): string {\n if (size < 2) return \"\";\n const gridSize = options.gridSize ?? 0;\n const r = Math.max(0, options.cornerRadius ?? 0);\n const pt = (i: number) => {\n const p = getPoint(i);\n return gridSize > 0 ? this.snapToGrid(p.x, p.y, gridSize) : p;\n };\n if (r <= 0) {\n let d = `M ${pt(0).x} ${pt(0).y}`;\n for (let i = 1; i < size; i++) {\n const p = pt(i);\n d += ` L ${p.x} ${p.y}`;\n }\n return d;\n }\n const dist = (a: { x: number; y: number }, b: { x: number; y: number }) =>\n Math.hypot(b.x - a.x, b.y - a.y);\n const unit = (a: { x: number; y: number }, b: { x: number; y: number }) => {\n const d = dist(a, b);\n if (d < 1e-6) return { x: 0, y: 0 };\n return { x: (b.x - a.x) / d, y: (b.y - a.y) / d };\n };\n let d = `M ${pt(0).x} ${pt(0).y}`;\n for (let i = 1; i < size - 1; i++) {\n const prev = pt(i - 1);\n const curr = pt(i);\n const next = pt(i + 1);\n const dirIn = unit(curr, prev);\n const dirOut = unit(curr, next);\n const lenIn = dist(curr, prev);\n const lenOut = dist(curr, next);\n const rIn = Math.min(r, lenIn / 2, lenOut / 2);\n const rOut = Math.min(r, lenIn / 2, lenOut / 2);\n const endPrev = { x: curr.x + dirIn.x * rIn, y: curr.y + dirIn.y * rIn };\n const startNext = { x: curr.x + dirOut.x * rOut, y: curr.y + dirOut.y * rOut };\n d += ` L ${endPrev.x} ${endPrev.y} Q ${curr.x} ${curr.y} ${startNext.x} ${startNext.y}`;\n }\n const last = pt(size - 1);\n d += ` L ${last.x} ${last.y}`;\n return d;\n }\n\n // Frees all libavoid objects (connectors and shapes) from the router.\n // This is important to prevent WASM memory leaks since libavoid\n // allocates memory in the WASM heap that isn't garbage collected by JS.\n private cleanup(\n router: { deleteConnector: (c: unknown) => void; deleteShape: (s: unknown) => void },\n connRefs: { connRef: unknown }[],\n shapeRefs: { ref: unknown }[]\n ): void {\n try {\n for (const { connRef } of connRefs) router.deleteConnector(connRef);\n for (const { ref } of shapeRefs) router.deleteShape(ref);\n } catch {\n // ignore\n }\n }\n}\n\n// Convenience function to load the WASM module — wraps AvoidRouter.load()\nexport async function loadAvoidRouter(): Promise<boolean> {\n return AvoidRouter.load();\n}\n\n// Convenience function to route all edges — wraps AvoidRouter.getInstance().routeAll().\n// Returns an empty object if routing fails for any reason.\nexport function routeAll(\n nodes: Node[],\n edges: Edge[],\n options?: AvoidRouterOptions\n): Record<string, AvoidRoute> {\n try {\n return AvoidRouter.getInstance().routeAll(nodes, edges, options);\n } catch {\n return {};\n }\n}\n","/**\n * useAvoidNodesRouterFromWorker\n * ---------------------------------------------------------------------------\n * Routes edges around nodes using a Web Worker running WASM.\n *\n * How it works:\n * 1. On load, sends ALL nodes + edges to the worker (\"reset\").\n * 2. On drag/resize, sends ONLY that node (\"updateNodes\") — much faster.\n * 3. On add/remove, does a full reset (graph structure changed).\n * 4. On settings change (spacing, rounding), does a full reset.\n *\n * The worker batches rapid changes (debounce) so dragging doesn't flood it.\n */\n\nimport { useCallback, useEffect, useRef } from \"react\";\nimport type { Node, NodeChange, Edge } from \"@xyflow/react\";\nimport { useAvoidRoutesStore, useAvoidRouterActionsStore } from \"./store\";\nimport { DEBOUNCE_ROUTING_MS } from \"./constants\";\nimport type { AvoidRouterOptions } from \"./router\";\nimport { useAvoidWorker } from \"./useAvoidWorker\";\n\nexport interface UseAvoidNodesRouterOptions {\n shouldSplitEdgesNearHandle?: boolean;\n edgeToEdgeSpacing?: number;\n edgeToNodeSpacing?: number;\n edgeRounding?: number;\n diagramGridSize?: number;\n}\n\nexport interface UseAvoidNodesRouterResult {\n updateRoutingOnNodesChange: (changes: NodeChange<Node>[]) => void;\n resetRouting: () => void;\n refreshRouting: () => void;\n updateRoutingForNodeIds: (nodeIds: string[]) => void;\n}\n\nconst DEFAULT_OPTIONS: UseAvoidNodesRouterOptions = {\n edgeToEdgeSpacing: 10,\n edgeToNodeSpacing: 8,\n};\n\nfunction toRouterOptions(opts?: UseAvoidNodesRouterOptions): AvoidRouterOptions {\n return {\n idealNudgingDistance: opts?.edgeToEdgeSpacing ?? DEFAULT_OPTIONS.edgeToEdgeSpacing,\n shapeBufferDistance: opts?.edgeToNodeSpacing ?? DEFAULT_OPTIONS.edgeToNodeSpacing,\n edgeRounding: opts?.edgeRounding,\n diagramGridSize: opts?.diagramGridSize,\n shouldSplitEdgesNearHandle: opts?.shouldSplitEdgesNearHandle,\n };\n}\n\n/**\n * Drop-in replacement for useAvoidNodesRouter — same API, but all heavy\n * routing calculations happen on a separate thread so the UI stays smooth.\n */\nexport function useAvoidNodesRouterFromWorker(\n nodes: Node[],\n edges: Edge[],\n options?: UseAvoidNodesRouterOptions\n): UseAvoidNodesRouterResult {\n const nodesRef = useRef<Node[]>(nodes);\n const edgesRef = useRef<Edge[]>(edges);\n const opts = toRouterOptions(options);\n const optsRef = useRef<AvoidRouterOptions>(opts);\n\n useEffect(() => { nodesRef.current = nodes; }, [nodes]);\n useEffect(() => { edgesRef.current = edges; }, [edges]);\n useEffect(() => { optsRef.current = opts; });\n\n const setRoutes = useAvoidRoutesStore((s) => s.setRoutes);\n const setActions = useAvoidRouterActionsStore((s) => s.setActions);\n\n const { post, workerLoaded } = useAvoidWorker({ create: true });\n\n const didResetRef = useRef(false);\n const nodesMeasuredRef = useRef(false);\n\n const sendReset = useCallback(() => {\n if (!workerLoaded) return;\n // Don't route until React Flow has measured at least some nodes —\n // without measured data, obstacles get wrong sizes and edges clip through.\n const nodes = nodesRef.current;\n const hasMeasured = nodes.length === 0 || nodes.some((n) => n.measured?.width != null);\n if (!hasMeasured) return;\n nodesMeasuredRef.current = true;\n const avoidEdges = edgesRef.current.filter((e) => e.type === \"avoidNodes\");\n if (avoidEdges.length === 0) {\n setRoutes({});\n return;\n }\n post({\n command: \"reset\",\n nodes,\n edges: avoidEdges,\n options: optsRef.current,\n });\n didResetRef.current = true;\n }, [post, setRoutes, workerLoaded]);\n\n const sendIncrementalChanges = useCallback(\n (nodeIds: string[]) => {\n if (!workerLoaded || !didResetRef.current) return;\n const nodeMap = new Map(nodesRef.current.map((n) => [n.id, n]));\n const changedNodes = nodeIds\n .map((id) => nodeMap.get(id))\n .filter((n): n is Node => n != null);\n if (changedNodes.length === 0) return;\n post({ command: \"updateNodes\", nodes: changedNodes });\n },\n [post, workerLoaded]\n );\n\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const pendingChangeIdsRef = useRef<Set<string>>(new Set());\n\n const resetRouting = useCallback(() => { sendReset(); }, [sendReset]);\n const refreshRouting = useCallback(() => { sendReset(); }, [sendReset]);\n const updateRoutingForNodeIds = useCallback(\n (nodeIds: string[]) => { sendIncrementalChanges(nodeIds); },\n [sendIncrementalChanges]\n );\n\n const updateRoutingOnNodesChange = useCallback(\n (changes: NodeChange<Node>[]) => {\n if (!workerLoaded) return;\n\n let hasPosition = false;\n let hasDimensions = false;\n let hasAddOrRemove = false;\n\n for (const c of changes) {\n if (c.type === \"position\") {\n hasPosition = true;\n pendingChangeIdsRef.current.add(c.id);\n } else if (c.type === \"dimensions\") {\n hasDimensions = true;\n pendingChangeIdsRef.current.add(c.id);\n } else if (c.type === \"add\" || c.type === \"remove\") {\n hasAddOrRemove = true;\n }\n }\n\n if (!hasPosition && !hasDimensions && !hasAddOrRemove) return;\n\n // On first dimensions change (initial measurement) or structural changes,\n // do a full reset so all nodes get correct measured bounds.\n const needsFullReset = hasAddOrRemove || (hasDimensions && !nodesMeasuredRef.current);\n\n if (needsFullReset) {\n if (debounceRef.current) { clearTimeout(debounceRef.current); debounceRef.current = null; }\n pendingChangeIdsRef.current.clear();\n // Use rAF to ensure React has flushed state (nodesRef is up to date).\n debounceRef.current = setTimeout(() => {\n debounceRef.current = null;\n requestAnimationFrame(() => sendReset());\n }, DEBOUNCE_ROUTING_MS);\n return;\n }\n\n if (!didResetRef.current) return;\n\n if (debounceRef.current) clearTimeout(debounceRef.current);\n debounceRef.current = setTimeout(() => {\n debounceRef.current = null;\n // Use rAF to ensure React has flushed state before reading nodesRef.\n requestAnimationFrame(() => {\n const ids = Array.from(pendingChangeIdsRef.current);\n pendingChangeIdsRef.current.clear();\n if (ids.length > 0) {\n sendIncrementalChanges(ids);\n }\n });\n }, DEBOUNCE_ROUTING_MS);\n },\n [workerLoaded, sendReset, sendIncrementalChanges]\n );\n\n useEffect(() => {\n setActions({\n resetRouting,\n updateRoutesForNodeId: (nodeId) => updateRoutingForNodeIds([nodeId]),\n });\n return () => setActions({ resetRouting: () => {}, updateRoutesForNodeId: () => {} });\n }, [resetRouting, updateRoutingForNodeIds, setActions]);\n\n useEffect(() => {\n if (workerLoaded) sendReset();\n }, [\n workerLoaded,\n nodes.length,\n edges.length,\n opts.shapeBufferDistance,\n opts.idealNudgingDistance,\n opts.edgeRounding,\n opts.diagramGridSize,\n opts.shouldSplitEdgesNearHandle,\n sendReset,\n ]);\n\n return {\n updateRoutingOnNodesChange,\n resetRouting,\n refreshRouting,\n updateRoutingForNodeIds,\n };\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { AvoidRouterWorkerCommand } from \"./worker-messages\";\nimport type { AvoidRoute } from \"./router\";\nimport { attachAvoidWorkerListener } from \"./worker-listener\";\n\nexport interface UseAvoidWorkerOptions {\n create?: boolean;\n onRouted?: (routes: Record<string, AvoidRoute>) => void;\n onLoaded?: (success: boolean) => void;\n}\n\nexport interface UseAvoidWorkerResult {\n workerLoaded: boolean;\n post: (cmd: AvoidRouterWorkerCommand) => void;\n close: () => void;\n}\n\n/**\n * Creates the avoid-router Web Worker and waits for it to load WASM.\n * WASM loads exclusively in the worker thread — never on the main thread.\n */\nexport function useAvoidWorker(options?: UseAvoidWorkerOptions): UseAvoidWorkerResult {\n const workerRef = useRef<Worker | null>(null);\n const [workerLoaded, setWorkerLoaded] = useState(false);\n const onRoutedRef = useRef(options?.onRouted);\n const onLoadedRef = useRef(options?.onLoaded);\n onRoutedRef.current = options?.onRouted;\n onLoadedRef.current = options?.onLoaded;\n\n const createWorker = options?.create !== false;\n\n useEffect(() => {\n if (!createWorker) {\n workerRef.current = null;\n setWorkerLoaded(false);\n return;\n }\n let worker: Worker;\n try {\n worker = new Worker(new URL(\"./workers/avoid-router.worker.js\", import.meta.url), { type: \"module\" });\n } catch (e) {\n console.error(\"[avoid-worker] Failed to create worker:\", e);\n return;\n }\n\n workerRef.current = worker;\n\n worker.addEventListener(\"error\", (e) => {\n console.error(\"[avoid-worker] Worker error:\", e.message);\n });\n\n const cleanup = attachAvoidWorkerListener(worker, {\n onRouted: (routes) => onRoutedRef.current?.(routes),\n onLoaded: (success) => {\n setWorkerLoaded(success);\n onLoadedRef.current?.(success);\n },\n });\n\n return () => {\n cleanup();\n worker.postMessage({ command: \"close\" } as AvoidRouterWorkerCommand);\n worker.terminate();\n workerRef.current = null;\n setWorkerLoaded(false);\n };\n }, [createWorker]);\n\n const post = useCallback((cmd: AvoidRouterWorkerCommand) => {\n if (workerRef.current) {\n workerRef.current.postMessage(cmd);\n }\n }, []);\n\n const close = useCallback(() => {\n if (workerRef.current) {\n workerRef.current.postMessage({ command: \"close\" } as AvoidRouterWorkerCommand);\n workerRef.current.terminate();\n workerRef.current = null;\n setWorkerLoaded(false);\n }\n }, []);\n\n return { workerLoaded, post, close };\n}\n","/**\n * Listener for avoid-router Web Worker messages.\n * Syncs \"loaded\" / \"routed\" into the avoid store.\n */\n\nimport type { AvoidRouterWorkerResponse } from \"./worker-messages\";\nimport { useAvoidRoutesStore } from \"./store\";\n\nexport interface AttachAvoidWorkerListenerOptions {\n onRouted?: (routes: Record<string, { path: string; labelX: number; labelY: number }>) => void;\n onLoaded?: (success: boolean) => void;\n}\n\n/**\n * Attach a message listener to an avoid-router worker so the app stays in sync.\n * @returns Cleanup function (removeEventListener).\n */\nexport function attachAvoidWorkerListener(\n worker: Worker,\n options: AttachAvoidWorkerListenerOptions = {}\n): () => void {\n const { onRouted, onLoaded } = options;\n const setLoaded = useAvoidRoutesStore.getState().setLoaded;\n const setRoutes = useAvoidRoutesStore.getState().setRoutes;\n\n const handler = (e: MessageEvent<AvoidRouterWorkerResponse>) => {\n const msg = e.data;\n if (!msg || typeof msg !== \"object\" || !(\"command\" in msg)) return;\n\n switch (msg.command) {\n case \"loaded\":\n setLoaded(msg.success);\n onLoaded?.(msg.success);\n break;\n case \"routed\":\n setRoutes(msg.routes);\n onRouted?.(msg.routes);\n break;\n default:\n break;\n }\n };\n\n worker.addEventListener(\"message\", handler);\n return () => worker.removeEventListener(\"message\", handler);\n}\n"],"mappings":"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","names":["d","useCallback","useEffect","useRef","useRef","useEffect","useCallback","nodes"]}