@stuly/anode-react 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.
@@ -0,0 +1,615 @@
1
+ import { AnodeReactContext, useAnode, useSelection, useViewport } from "../context.js";
2
+ import { useEdges, useGroups, useVisibleNodes } from "../hooks.js";
3
+ import { Node } from "./Node.js";
4
+ import { Group } from "./Group.js";
5
+ import { Link as Link$1 } from "./Link.js";
6
+ import React, { useContext, useEffect, useRef, useState } from "react";
7
+ import { Context, LinkKind, Rect, Vec2 } from "@stuly/anode";
8
+ import { jsx, jsxs } from "react/jsx-runtime";
9
+
10
+ //#region src/elements/World.tsx
11
+ const DefaultNode = ({ entity }) => /* @__PURE__ */ jsx("div", {
12
+ style: {
13
+ padding: 10,
14
+ background: "white",
15
+ border: "1px solid #ccc",
16
+ borderRadius: 4
17
+ },
18
+ children: entity.inner?.label || `Node ${entity.id}`
19
+ });
20
+ const getDistance = (t1, t2) => {
21
+ return Math.sqrt(Math.pow(t2.clientX - t1.clientX, 2) + Math.pow(t2.clientY - t1.clientY, 2));
22
+ };
23
+ const getCenter = (t1, t2) => {
24
+ return {
25
+ x: (t1.clientX + t2.clientX) / 2,
26
+ y: (t1.clientY + t2.clientY) / 2
27
+ };
28
+ };
29
+ const World = ({ children, style, nodeTypes = {}, linkTypes = {}, defaultLinkKind = LinkKind.BEZIER, onConnect, isValidConnection, selectionBoxStyle, nodes, links: linksProp, onNodesChange, onLinksChange }) => {
30
+ const ctx = useAnode();
31
+ const { viewport: transform, setViewport: setTransform, screenToWorld } = useViewport();
32
+ const { setScreenToWorld } = useContext(AnodeReactContext);
33
+ const { selection, setSelection } = useSelection();
34
+ const worldRef = useRef(null);
35
+ const [selectionBox, setSelectionBox] = useState(null);
36
+ useEffect(() => {
37
+ setScreenToWorld(() => (clientX, clientY) => {
38
+ if (!worldRef.current) return {
39
+ x: clientX,
40
+ y: clientY
41
+ };
42
+ const rect = worldRef.current.getBoundingClientRect();
43
+ return {
44
+ x: (clientX - rect.left - transformRef.current.x) / transformRef.current.k,
45
+ y: (clientY - rect.top - transformRef.current.y) / transformRef.current.k
46
+ };
47
+ });
48
+ }, [setScreenToWorld]);
49
+ const [containerSize, setContainerSize] = useState({
50
+ width: 0,
51
+ height: 0
52
+ });
53
+ useEffect(() => {
54
+ if (!nodes) return;
55
+ ctx.batch(() => {
56
+ const currentIds = new Set(ctx.entities.keys());
57
+ const incomingIds = new Set(nodes.map((n) => n.id));
58
+ for (const id of currentIds) if (!incomingIds.has(id)) {
59
+ const entity = ctx.entities.get(id);
60
+ if (entity) ctx.dropEntity(entity);
61
+ }
62
+ for (const n of nodes) {
63
+ const entity = ctx.entities.get(n.id);
64
+ const innerData = {
65
+ ...n.data || {},
66
+ type: n.type
67
+ };
68
+ if (!entity) ctx.newEntity(innerData, n.id).move(n.position.x, n.position.y);
69
+ else {
70
+ if (Math.abs(entity.position.x - n.position.x) > .01 || Math.abs(entity.position.y - n.position.y) > .01) entity.move(n.position.x, n.position.y);
71
+ entity.setInner(innerData);
72
+ }
73
+ }
74
+ }, "Sync Nodes from Props");
75
+ }, [ctx, nodes]);
76
+ useEffect(() => {
77
+ if (!linksProp) return;
78
+ const syncLinks = () => {
79
+ ctx.batch(() => {
80
+ const currentIds = new Set(ctx.links.keys());
81
+ const incomingIds = new Set(linksProp.map((l) => l.id));
82
+ for (const id of currentIds) if (!incomingIds.has(id)) {
83
+ const link = ctx.links.get(id);
84
+ if (link) ctx.dropLink(link);
85
+ }
86
+ for (const l of linksProp) {
87
+ const innerData = {
88
+ ...l.data || {},
89
+ type: l.type
90
+ };
91
+ const link = ctx.links.get(l.id);
92
+ if (!link) {
93
+ const fromNode = ctx.entities.get(l.source);
94
+ const toNode = ctx.entities.get(l.target);
95
+ if (fromNode && toNode) {
96
+ const fromSocket = Array.from(fromNode.sockets.values()).find((s) => s.name === l.sourceHandle);
97
+ const toSocket = Array.from(toNode.sockets.values()).find((s) => s.name === l.targetHandle);
98
+ if (fromSocket && toSocket) {
99
+ const newLink = ctx.newLink(fromSocket, toSocket, l.kind || defaultLinkKind, l.id, innerData);
100
+ if (newLink && l.waypoints) newLink.waypoints = l.waypoints.map((p) => new Vec2(p.x, p.y));
101
+ }
102
+ }
103
+ } else {
104
+ link.inner = innerData;
105
+ if (l.waypoints) link.waypoints = l.waypoints.map((p) => new Vec2(p.x, p.y));
106
+ }
107
+ }
108
+ }, "Sync Links from Props");
109
+ };
110
+ syncLinks();
111
+ const h1 = ctx.registerSocketCreateListener(syncLinks);
112
+ const h2 = ctx.registerSocketDropListener(syncLinks);
113
+ return () => {
114
+ ctx.unregisterListener(h1);
115
+ ctx.unregisterListener(h2);
116
+ };
117
+ }, [
118
+ ctx,
119
+ linksProp,
120
+ defaultLinkKind
121
+ ]);
122
+ useEffect(() => {
123
+ if (!onNodesChange && !onLinksChange) return;
124
+ const notify = () => {
125
+ if (onNodesChange && nodes) onNodesChange(Array.from(ctx.entities.values()).map((e) => ({
126
+ id: e.id,
127
+ position: {
128
+ x: e.position.x,
129
+ y: e.position.y
130
+ },
131
+ type: e.inner?.type,
132
+ data: e.inner
133
+ })), ctx);
134
+ if (onLinksChange && linksProp) onLinksChange(Array.from(ctx.links.values()).map((l) => {
135
+ const fromSocket = ctx.sockets.get(l.from);
136
+ const toSocket = ctx.sockets.get(l.to);
137
+ return {
138
+ id: l.id,
139
+ source: fromSocket?.entityId || 0,
140
+ sourceHandle: fromSocket?.name || "",
141
+ target: toSocket?.entityId || 0,
142
+ targetHandle: toSocket?.name || "",
143
+ kind: l.kind,
144
+ type: l.inner?.type,
145
+ data: l.inner,
146
+ waypoints: l.waypoints.map((p) => ({
147
+ x: p.x,
148
+ y: p.y
149
+ }))
150
+ };
151
+ }), ctx);
152
+ };
153
+ const handles = [
154
+ ctx.registerEntityCreateListener(notify),
155
+ ctx.registerEntityDropListener(notify),
156
+ ctx.registerEntityMoveListener(notify),
157
+ ctx.registerLinkCreateListener(notify),
158
+ ctx.registerLinkDropListener(notify),
159
+ ctx.registerLinkUpdateListener(notify),
160
+ ctx.registerBulkChangeListener(notify)
161
+ ];
162
+ return () => handles.forEach((h) => ctx.unregisterListener(h));
163
+ }, [
164
+ ctx,
165
+ onNodesChange,
166
+ onLinksChange,
167
+ nodes,
168
+ linksProp
169
+ ]);
170
+ const transformRef = useRef(transform);
171
+ transformRef.current = transform;
172
+ useEffect(() => {
173
+ if (!worldRef.current) return;
174
+ const observer = new ResizeObserver((entries) => {
175
+ const entry = entries[0];
176
+ if (!entry) return;
177
+ const { width, height } = entry.contentRect;
178
+ setContainerSize({
179
+ width,
180
+ height
181
+ });
182
+ });
183
+ observer.observe(worldRef.current);
184
+ return () => observer.disconnect();
185
+ }, []);
186
+ useEffect(() => {
187
+ const el = worldRef.current;
188
+ if (!el) return;
189
+ const onWheelNative = (e) => {
190
+ e.preventDefault();
191
+ const rect = el.getBoundingClientRect();
192
+ const t = transformRef.current;
193
+ const delta = -e.deltaY;
194
+ const factor = Math.pow(1.1, delta / 100);
195
+ const newK = Math.min(Math.max(t.k * factor, .1), 5);
196
+ const mouseX = e.clientX - rect.left;
197
+ const mouseY = e.clientY - rect.top;
198
+ const beforeKMouseX = (mouseX - t.x) / t.k;
199
+ const beforeKMouseY = (mouseY - t.y) / t.k;
200
+ setTransform({
201
+ x: mouseX - beforeKMouseX * newK,
202
+ y: mouseY - beforeKMouseY * newK,
203
+ k: newK
204
+ });
205
+ };
206
+ el.addEventListener("wheel", onWheelNative, { passive: false });
207
+ return () => el.removeEventListener("wheel", onWheelNative);
208
+ }, [setTransform]);
209
+ const entities = useVisibleNodes(containerSize);
210
+ const links = useEdges();
211
+ const groups = useGroups();
212
+ const [pendingLink, setPendingLink] = useState(null);
213
+ useEffect(() => {
214
+ const onKeyDown = (e) => {
215
+ if (e.key === "Backspace" || e.key === "Delete") {
216
+ if (["INPUT", "TEXTAREA"].includes(e.target.tagName)) return;
217
+ ctx.batch(() => {
218
+ for (const nid of selection.nodes) {
219
+ const entity = ctx.entities.get(nid);
220
+ if (entity) ctx.dropEntity(entity);
221
+ }
222
+ for (const lid of selection.links) {
223
+ const link = ctx.links.get(lid);
224
+ if (link) ctx.dropLink(link);
225
+ }
226
+ }, "Delete Selection");
227
+ setSelection({
228
+ nodes: /* @__PURE__ */ new Set(),
229
+ links: /* @__PURE__ */ new Set()
230
+ });
231
+ }
232
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "z") {
233
+ if (e.shiftKey) ctx.redo();
234
+ else ctx.undo();
235
+ e.preventDefault();
236
+ }
237
+ if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "y") {
238
+ ctx.redo();
239
+ e.preventDefault();
240
+ }
241
+ };
242
+ window.addEventListener("keydown", onKeyDown);
243
+ return () => window.removeEventListener("keydown", onKeyDown);
244
+ }, [
245
+ ctx,
246
+ selection,
247
+ setSelection
248
+ ]);
249
+ useEffect(() => {
250
+ const handleLinkStart = (e) => {
251
+ const { socketId, x, y } = e.detail;
252
+ const rect = worldRef.current?.getBoundingClientRect();
253
+ if (!rect) return;
254
+ const fromSocket = ctx.sockets.get(socketId);
255
+ if (!fromSocket) return;
256
+ const fromWorldPos = ctx.getWorldPosition(fromSocket.entityId);
257
+ setPendingLink({
258
+ fromId: socketId,
259
+ fromPos: new Vec2(fromWorldPos.x + fromSocket.offset.x, fromWorldPos.y + fromSocket.offset.y),
260
+ toPos: new Vec2((x - rect.left - transform.x) / transform.k, (y - rect.top - transform.y) / transform.k),
261
+ isValid: true
262
+ });
263
+ const onMove = (moveEvent) => {
264
+ const touches = moveEvent.touches;
265
+ const clientX = touches ? touches[0]?.clientX ?? 0 : moveEvent.clientX;
266
+ const clientY = touches ? touches[0]?.clientY ?? 0 : moveEvent.clientY;
267
+ const targetSocketEl = (touches ? document.elementFromPoint(clientX, clientY) : moveEvent.target)?.closest(".anode-socket");
268
+ let isValid = true;
269
+ if (targetSocketEl) {
270
+ const toId = parseInt(targetSocketEl.getAttribute("data-socket-id") || "");
271
+ const from = ctx.sockets.get(socketId);
272
+ const to = ctx.sockets.get(toId);
273
+ if (from && to) isValid = ctx.canLink(from, to) && (!isValidConnection || isValidConnection(from, to, ctx));
274
+ }
275
+ setPendingLink((prev) => prev ? {
276
+ ...prev,
277
+ toPos: new Vec2((clientX - rect.left - transform.x) / transform.k, (clientY - rect.top - transform.y) / transform.k),
278
+ isValid
279
+ } : null);
280
+ };
281
+ const onUp = (upEvent) => {
282
+ document.removeEventListener("mousemove", onMove);
283
+ document.removeEventListener("mouseup", onUp);
284
+ document.removeEventListener("touchmove", onMove);
285
+ document.removeEventListener("touchend", onUp);
286
+ let target = null;
287
+ const changedTouches = upEvent.changedTouches;
288
+ if (changedTouches) {
289
+ const touch = changedTouches[0];
290
+ target = touch ? document.elementFromPoint(touch.clientX, touch.clientY) : null;
291
+ } else target = upEvent.target;
292
+ const targetSocket = target?.closest(".anode-socket");
293
+ if (targetSocket) {
294
+ const toId = parseInt(targetSocket.getAttribute("data-socket-id") || "");
295
+ const from = ctx.sockets.get(socketId);
296
+ const to = ctx.sockets.get(toId);
297
+ if (from && to) {
298
+ if (ctx.canLink(from, to) && (!isValidConnection || isValidConnection(from, to, ctx))) if (onConnect) onConnect(socketId, toId, ctx);
299
+ else ctx.newLink(from, to, defaultLinkKind);
300
+ }
301
+ }
302
+ setPendingLink(null);
303
+ };
304
+ document.addEventListener("mousemove", onMove);
305
+ document.addEventListener("mouseup", onUp);
306
+ document.addEventListener("touchmove", onMove, { passive: false });
307
+ document.addEventListener("touchend", onUp);
308
+ };
309
+ const handleReconnect = (e) => {
310
+ const { linkId, type, x, y } = e.detail;
311
+ const rect = worldRef.current?.getBoundingClientRect();
312
+ if (!rect) return;
313
+ const link = ctx.links.get(linkId);
314
+ if (!link) return;
315
+ const otherSocketId = type === "from" ? link.to : link.from;
316
+ const otherSocket = ctx.sockets.get(otherSocketId);
317
+ if (!otherSocket) return;
318
+ const otherNodeWorldPos = ctx.getWorldPosition(otherSocket.entityId);
319
+ setPendingLink({
320
+ fromId: otherSocketId,
321
+ fromPos: new Vec2(otherNodeWorldPos.x + otherSocket.offset.x, otherNodeWorldPos.y + otherSocket.offset.y),
322
+ toPos: new Vec2((x - rect.left - transform.x) / transform.k, (y - rect.top - transform.y) / transform.k),
323
+ isValid: true
324
+ });
325
+ const onMove = (moveEvent) => {
326
+ const touches = moveEvent.touches;
327
+ const clientX = touches ? touches[0]?.clientX ?? 0 : moveEvent.clientX;
328
+ const clientY = touches ? touches[0]?.clientY ?? 0 : moveEvent.clientY;
329
+ const targetSocketEl = (touches ? document.elementFromPoint(clientX, clientY) : moveEvent.target)?.closest(".anode-socket");
330
+ let isValid = true;
331
+ if (targetSocketEl) {
332
+ const toId = parseInt(targetSocketEl.getAttribute("data-socket-id") || "");
333
+ const toSocket = ctx.sockets.get(toId);
334
+ if (toSocket) {
335
+ const newFrom = type === "from" ? toSocket : otherSocket;
336
+ const newTo = type === "from" ? otherSocket : toSocket;
337
+ isValid = ctx.canLink(newFrom, newTo) && (!isValidConnection || isValidConnection(newFrom, newTo, ctx));
338
+ }
339
+ }
340
+ setPendingLink((prev) => prev ? {
341
+ ...prev,
342
+ toPos: new Vec2((clientX - rect.left - transform.x) / transform.k, (clientY - rect.top - transform.y) / transform.k),
343
+ isValid
344
+ } : null);
345
+ };
346
+ const onUp = (upEvent) => {
347
+ document.removeEventListener("mousemove", onMove);
348
+ document.removeEventListener("mouseup", onUp);
349
+ document.removeEventListener("touchmove", onMove);
350
+ document.removeEventListener("touchend", onUp);
351
+ let target = null;
352
+ const changedTouches = upEvent.changedTouches;
353
+ if (changedTouches) {
354
+ const touch = changedTouches[0];
355
+ target = touch ? document.elementFromPoint(touch.clientX, touch.clientY) : null;
356
+ } else target = upEvent.target;
357
+ const targetSocketEl = target?.closest(".anode-socket");
358
+ if (targetSocketEl) {
359
+ const newSocketId = parseInt(targetSocketEl.getAttribute("data-socket-id") || "");
360
+ const newSocket = ctx.sockets.get(newSocketId);
361
+ if (newSocket) {
362
+ const newFrom = type === "from" ? newSocket : otherSocket;
363
+ const newTo = type === "from" ? otherSocket : newSocket;
364
+ if (ctx.canLink(newFrom, newTo) && (!isValidConnection || isValidConnection(newFrom, newTo, ctx))) ctx.updateLink(link, newFrom.id, newTo.id);
365
+ }
366
+ }
367
+ setPendingLink(null);
368
+ };
369
+ document.addEventListener("mousemove", onMove);
370
+ document.addEventListener("mouseup", onUp);
371
+ document.addEventListener("touchmove", onMove, { passive: false });
372
+ document.addEventListener("touchend", onUp);
373
+ };
374
+ const el = worldRef.current;
375
+ el?.addEventListener("anode-link-start", handleLinkStart);
376
+ el?.addEventListener("anode-link-reconnect", handleReconnect);
377
+ return () => {
378
+ el?.removeEventListener("anode-link-start", handleLinkStart);
379
+ el?.removeEventListener("anode-link-reconnect", handleReconnect);
380
+ };
381
+ }, [
382
+ ctx,
383
+ transform,
384
+ defaultLinkKind,
385
+ onConnect,
386
+ isValidConnection
387
+ ]);
388
+ const onMouseDown = (e) => {
389
+ if (e.button !== 0) return;
390
+ if (e.target !== worldRef.current) return;
391
+ if (e.altKey) {
392
+ const rect = worldRef.current.getBoundingClientRect();
393
+ const startX = e.clientX - rect.left;
394
+ const startY = e.clientY - rect.top;
395
+ setSelectionBox({
396
+ startX,
397
+ startY,
398
+ endX: startX,
399
+ endY: startY
400
+ });
401
+ const onMouseMove = (moveEvent) => {
402
+ setSelectionBox((prev) => prev ? {
403
+ ...prev,
404
+ endX: moveEvent.clientX - rect.left,
405
+ endY: moveEvent.clientY - rect.top
406
+ } : null);
407
+ };
408
+ const onMouseUp = () => {
409
+ document.removeEventListener("mousemove", onMouseMove);
410
+ document.removeEventListener("mouseup", onMouseUp);
411
+ setSelectionBox((prev) => {
412
+ if (!prev) return null;
413
+ const rect = worldRef.current?.getBoundingClientRect();
414
+ if (!rect) return null;
415
+ const x1 = Math.min(prev.startX, prev.endX);
416
+ const y1 = Math.min(prev.startY, prev.endY);
417
+ const x2 = Math.max(prev.startX, prev.endX);
418
+ const y2 = Math.max(prev.startY, prev.endY);
419
+ const worldTopLeft = screenToWorld(x1 + rect.left, y1 + rect.top);
420
+ const worldBottomRight = screenToWorld(x2 + rect.left, y2 + rect.top);
421
+ const queryRect = new Rect(worldTopLeft.x, worldTopLeft.y, worldBottomRight.x - worldTopLeft.x, worldBottomRight.y - worldTopLeft.y);
422
+ const selectedIds = ctx.quadTree.query(queryRect);
423
+ setSelection({
424
+ nodes: new Set(selectedIds),
425
+ links: /* @__PURE__ */ new Set()
426
+ });
427
+ return null;
428
+ });
429
+ };
430
+ document.addEventListener("mousemove", onMouseMove);
431
+ document.addEventListener("mouseup", onMouseUp);
432
+ return;
433
+ }
434
+ setSelection({
435
+ nodes: /* @__PURE__ */ new Set(),
436
+ links: /* @__PURE__ */ new Set()
437
+ });
438
+ const startX = e.clientX - transform.x;
439
+ const startY = e.clientY - transform.y;
440
+ const onMouseMove = (moveEvent) => {
441
+ setTransform({
442
+ x: moveEvent.clientX - startX,
443
+ y: moveEvent.clientY - startY,
444
+ k: transformRef.current.k
445
+ });
446
+ };
447
+ const onMouseUp = () => {
448
+ document.removeEventListener("mousemove", onMouseMove);
449
+ document.removeEventListener("mouseup", onMouseUp);
450
+ };
451
+ document.addEventListener("mousemove", onMouseMove);
452
+ document.addEventListener("mouseup", onMouseUp);
453
+ };
454
+ const onTouchStart = (e) => {
455
+ if (e.touches.length === 1) {
456
+ if (e.target !== worldRef.current) return;
457
+ setSelection({
458
+ nodes: /* @__PURE__ */ new Set(),
459
+ links: /* @__PURE__ */ new Set()
460
+ });
461
+ const touch = e.touches[0];
462
+ if (!touch) return;
463
+ const startX = touch.clientX - transform.x;
464
+ const startY = touch.clientY - transform.y;
465
+ const onTouchMove = (moveEvent) => {
466
+ if (moveEvent.touches.length === 1) {
467
+ const touch = moveEvent.touches[0];
468
+ if (!touch) return;
469
+ setTransform({
470
+ x: touch.clientX - startX,
471
+ y: touch.clientY - startY,
472
+ k: transformRef.current.k
473
+ });
474
+ }
475
+ };
476
+ const onTouchEnd = () => {
477
+ document.removeEventListener("touchmove", onTouchMove);
478
+ document.removeEventListener("touchend", onTouchEnd);
479
+ };
480
+ document.addEventListener("touchmove", onTouchMove, { passive: false });
481
+ document.addEventListener("touchend", onTouchEnd);
482
+ } else if (e.touches.length === 2) {
483
+ const t1 = e.touches[0];
484
+ const t2 = e.touches[1];
485
+ if (!t1 || !t2) return;
486
+ const initialDist = getDistance(t1, t2);
487
+ const initialCenter = getCenter(t1, t2);
488
+ const initialTransform = { ...transformRef.current };
489
+ const onTouchMove = (moveEvent) => {
490
+ if (moveEvent.touches.length === 2) {
491
+ const mt1 = moveEvent.touches[0];
492
+ const mt2 = moveEvent.touches[1];
493
+ if (!mt1 || !mt2) return;
494
+ const currentDist = getDistance(mt1, mt2);
495
+ const currentCenter = getCenter(mt1, mt2);
496
+ const factor = currentDist / initialDist;
497
+ const newK = Math.min(Math.max(initialTransform.k * factor, .1), 5);
498
+ const rect = worldRef.current?.getBoundingClientRect();
499
+ if (!rect) return;
500
+ const zoomCenterX = initialCenter.x - rect.left;
501
+ const zoomCenterY = initialCenter.y - rect.top;
502
+ const beforeKCenterX = (zoomCenterX - initialTransform.x) / initialTransform.k;
503
+ const beforeKCenterY = (zoomCenterY - initialTransform.y) / initialTransform.k;
504
+ const dx = currentCenter.x - initialCenter.x;
505
+ const dy = currentCenter.y - initialCenter.y;
506
+ setTransform({
507
+ x: zoomCenterX - beforeKCenterX * newK + dx,
508
+ y: zoomCenterY - beforeKCenterY * newK + dy,
509
+ k: newK
510
+ });
511
+ if (moveEvent.cancelable) moveEvent.preventDefault();
512
+ }
513
+ };
514
+ const onTouchEnd = () => {
515
+ document.removeEventListener("touchmove", onTouchMove);
516
+ document.removeEventListener("touchend", onTouchEnd);
517
+ };
518
+ document.addEventListener("touchmove", onTouchMove, { passive: false });
519
+ document.addEventListener("touchend", onTouchEnd);
520
+ }
521
+ };
522
+ return /* @__PURE__ */ jsxs("div", {
523
+ ref: worldRef,
524
+ className: "anode-world",
525
+ onMouseDown,
526
+ onTouchStart,
527
+ style: {
528
+ position: "relative",
529
+ width: "100%",
530
+ height: "100%",
531
+ overflow: "hidden",
532
+ userSelect: "none",
533
+ background: "#f1f5f9",
534
+ ...style
535
+ },
536
+ children: [
537
+ children,
538
+ selectionBox && /* @__PURE__ */ jsx("div", { style: {
539
+ position: "absolute",
540
+ left: Math.min(selectionBox.startX, selectionBox.endX),
541
+ top: Math.min(selectionBox.startY, selectionBox.endY),
542
+ width: Math.abs(selectionBox.endX - selectionBox.startX),
543
+ height: Math.abs(selectionBox.endY - selectionBox.startY),
544
+ border: "1px solid #3b82f6",
545
+ background: "rgba(59, 130, 246, 0.1)",
546
+ pointerEvents: "none",
547
+ zIndex: 1e3,
548
+ ...selectionBoxStyle
549
+ } }),
550
+ /* @__PURE__ */ jsxs("div", {
551
+ style: {
552
+ position: "absolute",
553
+ top: 0,
554
+ left: 0,
555
+ width: "100%",
556
+ height: "100%",
557
+ transform: `translate(${transform.x}px, ${transform.y}px) scale(${transform.k})`,
558
+ transformOrigin: "0 0",
559
+ pointerEvents: "none"
560
+ },
561
+ children: [/* @__PURE__ */ jsxs("svg", {
562
+ style: {
563
+ position: "absolute",
564
+ top: 0,
565
+ left: 0,
566
+ width: "100000px",
567
+ height: "100000px",
568
+ pointerEvents: "none",
569
+ zIndex: 0,
570
+ overflow: "visible"
571
+ },
572
+ children: [/* @__PURE__ */ jsx("g", {
573
+ style: { pointerEvents: "auto" },
574
+ children: links.map((link) => {
575
+ const Component = linkTypes[link.inner?.type || "default"];
576
+ return /* @__PURE__ */ jsx(Link$1, {
577
+ id: link.id,
578
+ component: Component
579
+ }, link.id);
580
+ })
581
+ }), pendingLink && /* @__PURE__ */ jsx("line", {
582
+ x1: pendingLink.fromPos.x,
583
+ y1: pendingLink.fromPos.y,
584
+ x2: pendingLink.toPos.x,
585
+ y2: pendingLink.toPos.y,
586
+ stroke: pendingLink.isValid ? "#94a3b8" : "#ef4444",
587
+ strokeWidth: 2 / transform.k,
588
+ strokeDasharray: 4 / transform.k
589
+ })]
590
+ }), /* @__PURE__ */ jsxs("div", {
591
+ style: {
592
+ position: "absolute",
593
+ top: 0,
594
+ left: 0,
595
+ zIndex: 1,
596
+ pointerEvents: "none"
597
+ },
598
+ children: [groups.map((group) => /* @__PURE__ */ jsx(Group, { id: group.id }, group.id)), entities.map((entity) => {
599
+ const Component = nodeTypes[entity.inner?.type || "default"] || DefaultNode;
600
+ return /* @__PURE__ */ jsx(Node, {
601
+ id: entity.id,
602
+ children: /* @__PURE__ */ jsx("div", {
603
+ style: { pointerEvents: "auto" },
604
+ children: /* @__PURE__ */ jsx(Component, { entity })
605
+ })
606
+ }, entity.id);
607
+ })]
608
+ })]
609
+ })
610
+ ]
611
+ });
612
+ };
613
+
614
+ //#endregion
615
+ export { World };
@@ -0,0 +1,15 @@
1
+ import * as _stuly_anode0 from "@stuly/anode";
2
+ import { Entity, Link } from "@stuly/anode";
3
+
4
+ //#region src/hooks.d.ts
5
+ declare const useNodes: () => Entity<any>[];
6
+ declare const useVisibleNodes: (containerRect?: {
7
+ width: number;
8
+ height: number;
9
+ }) => Entity<any>[];
10
+ declare const useEdges: () => Link<any>[];
11
+ declare const useEntitySockets: (entityId: number) => any[];
12
+ declare function useSocketValue<T = any>(socketId: number | null): T;
13
+ declare const useGroups: () => _stuly_anode0.Group[];
14
+ //#endregion
15
+ export { useEdges, useEntitySockets, useGroups, useNodes, useSocketValue, useVisibleNodes };