@tldiagram/core-ui 1.92.0 → 1.94.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/api/client.d.ts +13 -1
- package/dist/components/ElementNode.d.ts +14 -1
- package/dist/components/ZUI/ZUICanvas.d.ts +1 -0
- package/dist/config/runtime-vscode.d.ts +1 -0
- package/dist/config/runtime.d.ts +1 -0
- package/dist/index.js +10875 -9550
- package/dist/pages/InfiniteZoom.d.ts +5 -2
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +10 -3
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.test.d.ts +1 -0
- package/dist/pages/ViewEditor/hooks/useViewData.d.ts +27 -24
- package/dist/pages/ViewsGrid.d.ts +9 -1
- package/dist/shims/empty-node-module.d.ts +2 -0
- package/dist/store/useStore.d.ts +80 -0
- package/dist/store/useStore.test.d.ts +1 -0
- package/package.json +10 -7
- package/src/api/client.ts +39 -1
- package/src/components/ElementNode.tsx +21 -59
- package/src/components/ElementPanel.tsx +2 -3
- package/src/components/LayoutSection.tsx +95 -104
- package/src/components/ViewGridNode.tsx +1 -4
- package/src/components/ZUI/ZUICanvas.tsx +138 -1
- package/src/components/ZUI/renderer.ts +166 -66
- package/src/components/ZUI/useZUIInteraction.ts +235 -81
- package/src/config/runtime-vscode.ts +6 -0
- package/src/config/runtime.ts +4 -0
- package/src/main.tsx +26 -14
- package/src/pages/InfiniteZoom.tsx +14 -5
- package/src/pages/ViewEditor/context.tsx +14 -3
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.test.ts +30 -0
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +294 -146
- package/src/pages/ViewEditor/hooks/useViewData.ts +459 -256
- package/src/pages/ViewEditor/index.tsx +67 -70
- package/src/pages/Views.tsx +552 -83
- package/src/pages/ViewsGrid.tsx +26 -337
- package/src/shims/empty-node-module.ts +1 -0
- package/src/store/useStore.test.ts +285 -0
- package/src/store/useStore.ts +327 -0
|
@@ -34,8 +34,106 @@ import {
|
|
|
34
34
|
ensureVisualHandleId,
|
|
35
35
|
getLogicalHandleId,
|
|
36
36
|
} from '../../../utils/edgeDistribution'
|
|
37
|
+
import { useStore } from '../../../store/useStore'
|
|
37
38
|
|
|
38
39
|
const SNAP_RADIUS = 75
|
|
40
|
+
const CONNECTOR_DRAG_UPDATE_INTERVAL_MS = 25
|
|
41
|
+
|
|
42
|
+
type HandleTarget = {
|
|
43
|
+
nodeId?: string
|
|
44
|
+
handleId: string
|
|
45
|
+
x: number
|
|
46
|
+
y: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function collectHandleTargets(excludeNodeId?: string): HandleTarget[] {
|
|
50
|
+
const handles = document.querySelectorAll('.react-flow__handle')
|
|
51
|
+
const targets: HandleTarget[] = []
|
|
52
|
+
|
|
53
|
+
for (const handle of handles) {
|
|
54
|
+
const nodeId = handle.closest('.react-flow__node')?.getAttribute('data-id') || undefined
|
|
55
|
+
if (excludeNodeId && nodeId === excludeNodeId) continue
|
|
56
|
+
|
|
57
|
+
const rect = handle.getBoundingClientRect()
|
|
58
|
+
targets.push({
|
|
59
|
+
nodeId,
|
|
60
|
+
handleId: handle.getAttribute('data-handleid') || handle.id,
|
|
61
|
+
x: rect.left + rect.width / 2,
|
|
62
|
+
y: rect.top + rect.height / 2,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return targets
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function findNearestHandleTargetInCache(targets: HandleTarget[], clientX: number, clientY: number) {
|
|
70
|
+
let hoveredHandleId: string | undefined
|
|
71
|
+
let hoveredNodeId: string | undefined
|
|
72
|
+
let snapPos = { x: clientX, y: clientY }
|
|
73
|
+
let nearestDistance = Infinity
|
|
74
|
+
|
|
75
|
+
for (const target of targets) {
|
|
76
|
+
const dist = Math.hypot(clientX - target.x, clientY - target.y)
|
|
77
|
+
if (dist < 36 && dist < nearestDistance) {
|
|
78
|
+
nearestDistance = dist
|
|
79
|
+
snapPos = { x: target.x, y: target.y }
|
|
80
|
+
hoveredHandleId = target.handleId
|
|
81
|
+
hoveredNodeId = target.nodeId
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
nearHandle: hoveredHandleId !== undefined,
|
|
87
|
+
snapPos,
|
|
88
|
+
hoveredHandleId,
|
|
89
|
+
hoveredNodeId,
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function applyNodeChangesWithStructuralSharing(changes: NodeChange[], nodes: RFNode[]) {
|
|
94
|
+
if (changes.length === 0) return nodes
|
|
95
|
+
|
|
96
|
+
const canFastPath = changes.every((change) => change.type === 'position' && 'id' in change)
|
|
97
|
+
if (!canFastPath) return applyNodeChanges(changes, nodes)
|
|
98
|
+
|
|
99
|
+
const changesById = new Map(changes.map((change) => [change.id, change]))
|
|
100
|
+
let didChange = false
|
|
101
|
+
|
|
102
|
+
const nextNodes = nodes.map((node) => {
|
|
103
|
+
const change = changesById.get(node.id)
|
|
104
|
+
if (!change || change.type !== 'position') return node
|
|
105
|
+
|
|
106
|
+
const position = change.position ?? node.position
|
|
107
|
+
const positionAbsolute = change.positionAbsolute ?? node.positionAbsolute
|
|
108
|
+
const dragging = change.dragging ?? node.dragging
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
node.position.x === position.x &&
|
|
112
|
+
node.position.y === position.y &&
|
|
113
|
+
node.positionAbsolute?.x === positionAbsolute?.x &&
|
|
114
|
+
node.positionAbsolute?.y === positionAbsolute?.y &&
|
|
115
|
+
node.dragging === dragging
|
|
116
|
+
) {
|
|
117
|
+
return node
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
didChange = true
|
|
121
|
+
return {
|
|
122
|
+
...node,
|
|
123
|
+
position,
|
|
124
|
+
positionAbsolute,
|
|
125
|
+
dragging,
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
return didChange ? nextNodes : nodes
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function getConnectorDeletionTarget(
|
|
133
|
+
selectedConnector: Connector | null,
|
|
134
|
+
) {
|
|
135
|
+
return selectedConnector?.id ?? null
|
|
136
|
+
}
|
|
39
137
|
|
|
40
138
|
interface CanvasInteractionOptions {
|
|
41
139
|
viewId: number | null
|
|
@@ -72,12 +170,11 @@ interface CanvasInteractionOptions {
|
|
|
72
170
|
openConnectorPanel: () => void
|
|
73
171
|
closeConnectorPanel: () => void
|
|
74
172
|
selectedElement: LibraryElement | null
|
|
75
|
-
|
|
173
|
+
selectedConnector: Connector | null
|
|
76
174
|
connectors: Connector[]
|
|
77
175
|
layers: ViewLayer[]
|
|
78
176
|
setSelectedElement: React.Dispatch<React.SetStateAction<LibraryElement | null>>
|
|
79
177
|
setSelectedEdge: (e: Connector | null) => void
|
|
80
|
-
setSelectedEdgeId: (id: number | null) => void
|
|
81
178
|
setSelectedProxyConnectorDetails: React.Dispatch<React.SetStateAction<import('../../../crossBranch/types').ProxyConnectorDetails | null>>
|
|
82
179
|
openProxyConnectorPanel: () => void
|
|
83
180
|
closeProxyConnectorPanel: () => void
|
|
@@ -110,6 +207,12 @@ type HandleReconnectDragState = {
|
|
|
110
207
|
hoveredHandleId?: string
|
|
111
208
|
}
|
|
112
209
|
|
|
210
|
+
type InteractionStartOptions = {
|
|
211
|
+
sourceHandle?: string
|
|
212
|
+
clientX?: number
|
|
213
|
+
clientY?: number
|
|
214
|
+
}
|
|
215
|
+
|
|
113
216
|
export function useCanvasInteractions({
|
|
114
217
|
viewId,
|
|
115
218
|
canEdit,
|
|
@@ -127,8 +230,8 @@ export function useCanvasInteractions({
|
|
|
127
230
|
interactionSourceIdRef,
|
|
128
231
|
hoveredZoomRef,
|
|
129
232
|
hoverPanLockedUntilRef,
|
|
130
|
-
setViewElements,
|
|
131
|
-
setConnectors,
|
|
233
|
+
setViewElements: _setViewElements,
|
|
234
|
+
setConnectors: _setConnectors,
|
|
132
235
|
setRfNodes,
|
|
133
236
|
setRfEdges,
|
|
134
237
|
setLinksMap,
|
|
@@ -145,12 +248,11 @@ export function useCanvasInteractions({
|
|
|
145
248
|
openConnectorPanel: openConnectorPanel,
|
|
146
249
|
closeConnectorPanel: closeConnectorPanel,
|
|
147
250
|
selectedElement,
|
|
148
|
-
|
|
251
|
+
selectedConnector,
|
|
149
252
|
connectors,
|
|
150
253
|
layers,
|
|
151
254
|
setSelectedElement,
|
|
152
255
|
setSelectedEdge,
|
|
153
|
-
setSelectedEdgeId,
|
|
154
256
|
setSelectedProxyConnectorDetails,
|
|
155
257
|
openProxyConnectorPanel,
|
|
156
258
|
closeProxyConnectorPanel,
|
|
@@ -163,6 +265,10 @@ export function useCanvasInteractions({
|
|
|
163
265
|
onMoveStateChange,
|
|
164
266
|
}: CanvasInteractionOptions) {
|
|
165
267
|
const { screenToFlowPosition, setViewport, getViewport, zoomIn, zoomOut } = useReactFlow()
|
|
268
|
+
const updateElementPosition = useStore((state) => state.updateElementPosition)
|
|
269
|
+
const removeElementPlacement = useStore((state) => state.removeElementPlacement)
|
|
270
|
+
const upsertConnector = useStore((state) => state.upsertConnector)
|
|
271
|
+
const replaceConnector = useStore((state) => state.replaceConnector)
|
|
166
272
|
const screenToFlowPositionRef = useRef(screenToFlowPosition)
|
|
167
273
|
screenToFlowPositionRef.current = screenToFlowPosition
|
|
168
274
|
|
|
@@ -176,6 +282,7 @@ export function useCanvasInteractions({
|
|
|
176
282
|
const [reconnectPicking, setReconnectPicking] = useState<{ edgeId: number; endpoint: 'source' | 'target' } | null>(null)
|
|
177
283
|
const [handleReconnectDrag, setHandleReconnectDrag] = useState<HandleReconnectDragState | null>(null)
|
|
178
284
|
const [connectorLongPressMenu, setConnectorLongPressMenu] = useState<{ edgeId: number; x: number; y: number } | null>(null)
|
|
285
|
+
const isMovingRef = useRef(false)
|
|
179
286
|
|
|
180
287
|
interactionSourceIdRef.current = interactionSourceId
|
|
181
288
|
|
|
@@ -185,11 +292,13 @@ export function useCanvasInteractions({
|
|
|
185
292
|
const connectingSourceRef = useRef<string | null>(null)
|
|
186
293
|
const connectWasValidRef = useRef(false)
|
|
187
294
|
const connectGhostListenerRef = useRef<((e: MouseEvent) => void) | null>(null)
|
|
295
|
+
const connectorDragLastUpdateRef = useRef(0)
|
|
188
296
|
const isReconnectingRef = useRef(false)
|
|
189
297
|
const suppressNextConnectorClickRef = useRef(false)
|
|
190
298
|
const suppressNextPaneClickRef = useRef(false)
|
|
191
299
|
const longPressCanvasRef = useRef<{ timer: ReturnType<typeof setTimeout>; clientX: number; clientY: number } | null>(null)
|
|
192
300
|
const pendingConnectionSourceRef = useRef(pendingConnectionSource)
|
|
301
|
+
const pendingConnectionSourceHandleRef = useRef<string | null>(null)
|
|
193
302
|
pendingConnectionSourceRef.current = pendingConnectionSource
|
|
194
303
|
const clickConnectModeRef = useRef(clickConnectMode)
|
|
195
304
|
clickConnectModeRef.current = clickConnectMode
|
|
@@ -225,35 +334,11 @@ export function useCanvasInteractions({
|
|
|
225
334
|
isReconnectingRef.current = false
|
|
226
335
|
}, [clearHandleReconnectListeners])
|
|
227
336
|
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
let nearestDistance = Infinity
|
|
234
|
-
|
|
235
|
-
for (const handle of handles) {
|
|
236
|
-
const nodeId = handle.closest('.react-flow__node')?.getAttribute('data-id') || undefined
|
|
237
|
-
if (excludeNodeId && nodeId === excludeNodeId) continue
|
|
238
|
-
const rect = handle.getBoundingClientRect()
|
|
239
|
-
const cx = rect.left + rect.width / 2
|
|
240
|
-
const cy = rect.top + rect.height / 2
|
|
241
|
-
const dist = Math.hypot(clientX - cx, clientY - cy)
|
|
242
|
-
if (dist < 36 && dist < nearestDistance) {
|
|
243
|
-
nearestDistance = dist
|
|
244
|
-
snapPos = { x: cx, y: cy }
|
|
245
|
-
hoveredHandleId = handle.getAttribute('data-handleid') || handle.id
|
|
246
|
-
hoveredNodeId = nodeId
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return {
|
|
251
|
-
nearHandle: hoveredHandleId !== undefined,
|
|
252
|
-
snapPos,
|
|
253
|
-
hoveredHandleId,
|
|
254
|
-
hoveredNodeId,
|
|
255
|
-
}
|
|
256
|
-
}, [])
|
|
337
|
+
const finalizeConnectorCreate = useCallback(async (connector: Connector) => {
|
|
338
|
+
upsertConnectorGraphSnapshot(connector)
|
|
339
|
+
upsertConnector(connector)
|
|
340
|
+
await refreshElements()
|
|
341
|
+
}, [refreshElements, upsertConnector])
|
|
257
342
|
|
|
258
343
|
// ── Ref-forwarded callbacks ────────────────────────────────────────────────
|
|
259
344
|
const openConnectorPanelRef = useRef(openConnectorPanel)
|
|
@@ -304,8 +389,10 @@ export function useCanvasInteractions({
|
|
|
304
389
|
if (!canEdit || viewId === null || !addingElementAt || addingElementAt.mode !== 'add') return
|
|
305
390
|
const { flowX, flowY } = addingElementAt
|
|
306
391
|
const sourceId = pendingConnectionSourceRef.current
|
|
392
|
+
const pendingSourceHandle = pendingConnectionSourceHandleRef.current
|
|
307
393
|
setAddingElementAt(null)
|
|
308
394
|
setPendingConnectionSource(null)
|
|
395
|
+
pendingConnectionSourceHandleRef.current = null
|
|
309
396
|
try {
|
|
310
397
|
const obj = await api.elements.create({ name, kind: '' })
|
|
311
398
|
await api.workspace.views.placements.add(viewId, obj.id, flowX - 100, flowY - 40)
|
|
@@ -319,20 +406,22 @@ export function useCanvasInteractions({
|
|
|
319
406
|
: { sourceHandle: 'right', targetHandle: 'left' }
|
|
320
407
|
const newConnector = await api.workspace.connectors.create(viewId, {
|
|
321
408
|
source_element_id: sourceId, target_element_id: obj.id,
|
|
322
|
-
source_handle: sourceHandle, target_handle: targetHandle, direction: 'forward',
|
|
409
|
+
source_handle: pendingSourceHandle ?? sourceHandle, target_handle: targetHandle, direction: 'forward',
|
|
323
410
|
})
|
|
324
|
-
|
|
325
|
-
|
|
411
|
+
const connector = connectorToConnector(newConnector)
|
|
412
|
+
await finalizeConnectorCreate(connector)
|
|
326
413
|
}
|
|
327
414
|
} catch { /* intentionally empty */ }
|
|
328
|
-
}, [
|
|
415
|
+
}, [addingElementAt, canEdit, finalizeConnectorCreate, refreshElements, rfNodesRef, viewId, viewElementsRef])
|
|
329
416
|
|
|
330
417
|
const handleConfirmExistingElement = useCallback(async (obj: LibraryElement) => {
|
|
331
418
|
if (!canEdit || viewId === null || !addingElementAt || addingElementAt.mode !== 'add') return
|
|
332
419
|
const { flowX, flowY } = addingElementAt
|
|
333
420
|
const sourceId = pendingConnectionSourceRef.current
|
|
421
|
+
const pendingSourceHandle = pendingConnectionSourceHandleRef.current
|
|
334
422
|
setAddingElementAt(null)
|
|
335
423
|
setPendingConnectionSource(null)
|
|
424
|
+
pendingConnectionSourceHandleRef.current = null
|
|
336
425
|
try {
|
|
337
426
|
if (!existingElementIds.has(obj.id)) {
|
|
338
427
|
await api.workspace.views.placements.add(viewId, obj.id, flowX - 100, flowY - 40)
|
|
@@ -350,20 +439,22 @@ export function useCanvasInteractions({
|
|
|
350
439
|
: { sourceHandle: 'right', targetHandle: 'left' }
|
|
351
440
|
const newConnector = await api.workspace.connectors.create(viewId, {
|
|
352
441
|
source_element_id: sourceId, target_element_id: obj.id,
|
|
353
|
-
source_handle: sourceHandle, target_handle: targetHandle, direction: 'forward',
|
|
442
|
+
source_handle: pendingSourceHandle ?? sourceHandle, target_handle: targetHandle, direction: 'forward',
|
|
354
443
|
})
|
|
355
|
-
|
|
356
|
-
|
|
444
|
+
const connector = connectorToConnector(newConnector)
|
|
445
|
+
await finalizeConnectorCreate(connector)
|
|
357
446
|
}
|
|
358
447
|
} catch { /* intentionally empty */ }
|
|
359
|
-
}, [
|
|
448
|
+
}, [addingElementAt, canEdit, existingElementIds, finalizeConnectorCreate, refreshElements, rfNodesRef, viewId, viewElementsRef])
|
|
360
449
|
|
|
361
450
|
const handleConfirmConnectExistingElement = useCallback(async (obj: LibraryElement) => {
|
|
362
451
|
if (!canEdit || viewId === null || !addingElementAt || addingElementAt.mode !== 'connect') return
|
|
363
452
|
const { flowX, flowY } = addingElementAt
|
|
364
453
|
const sourceId = pendingConnectionSourceRef.current
|
|
454
|
+
const pendingSourceHandle = pendingConnectionSourceHandleRef.current
|
|
365
455
|
setAddingElementAt(null)
|
|
366
456
|
setPendingConnectionSource(null)
|
|
457
|
+
pendingConnectionSourceHandleRef.current = null
|
|
367
458
|
if (sourceId == null || sourceId === obj.id) return
|
|
368
459
|
try {
|
|
369
460
|
const sourceNode = rfNodesRef.current.find((n) => n.id === String(sourceId))
|
|
@@ -373,14 +464,14 @@ export function useCanvasInteractions({
|
|
|
373
464
|
const newConnector = await api.workspace.connectors.create(viewId, {
|
|
374
465
|
source_element_id: sourceId,
|
|
375
466
|
target_element_id: obj.id,
|
|
376
|
-
source_handle: sourceHandle,
|
|
467
|
+
source_handle: pendingSourceHandle ?? sourceHandle,
|
|
377
468
|
target_handle: targetHandle,
|
|
378
469
|
direction: 'forward',
|
|
379
470
|
})
|
|
380
|
-
|
|
381
|
-
|
|
471
|
+
const connector = connectorToConnector(newConnector)
|
|
472
|
+
await finalizeConnectorCreate(connector)
|
|
382
473
|
} catch { /* intentionally empty */ }
|
|
383
|
-
}, [addingElementAt, canEdit,
|
|
474
|
+
}, [addingElementAt, canEdit, finalizeConnectorCreate, rfNodesRef, viewId])
|
|
384
475
|
|
|
385
476
|
// ── Zoom-in / zoom-out stable callbacks ───────────────────────────────────
|
|
386
477
|
const stableOnZoomIn = useCallback(async (elementId: number) => {
|
|
@@ -467,20 +558,98 @@ export function useCanvasInteractions({
|
|
|
467
558
|
try {
|
|
468
559
|
await api.workspace.views.placements.remove(viewId, elementId)
|
|
469
560
|
removePlacementGraphSnapshot(viewId, elementId)
|
|
561
|
+
removeElementPlacement(elementId)
|
|
470
562
|
handleElementDeleted(elementId)
|
|
471
563
|
setInteractionSourceId(null)
|
|
564
|
+
pendingConnectionSourceHandleRef.current = null
|
|
472
565
|
} catch { /* intentionally empty */ }
|
|
473
|
-
}, [canEdit, viewId, handleElementDeleted])
|
|
566
|
+
}, [canEdit, viewId, removeElementPlacement, handleElementDeleted])
|
|
567
|
+
|
|
568
|
+
const connectClickModeToHandle = useCallback(async (targetElementId: number, targetHandle: string) => {
|
|
569
|
+
if (!canEdit) return
|
|
570
|
+
const cid = viewIdRef.current
|
|
571
|
+
const sourceElementId = interactionSourceIdRef.current
|
|
572
|
+
if (cid === null || sourceElementId === null || sourceElementId === targetElementId) return
|
|
573
|
+
|
|
574
|
+
const sourceHandle = pendingConnectionSourceHandleRef.current ??
|
|
575
|
+
(clickConnectModeRef.current?.sourceHandle
|
|
576
|
+
? getLogicalHandleId(clickConnectModeRef.current.sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE) ?? DEFAULT_SOURCE_HANDLE_SIDE
|
|
577
|
+
: DEFAULT_SOURCE_HANDLE_SIDE)
|
|
578
|
+
const logicalTargetHandle = getLogicalHandleId(targetHandle, DEFAULT_TARGET_HANDLE_SIDE) ?? DEFAULT_TARGET_HANDLE_SIDE
|
|
579
|
+
|
|
580
|
+
setInteractionSourceId(null)
|
|
581
|
+
setPendingConnectionSource(null)
|
|
582
|
+
pendingConnectionSourceHandleRef.current = null
|
|
583
|
+
setClickConnectMode(null)
|
|
584
|
+
setClickConnectCursorPos(null)
|
|
585
|
+
setConnectGhostPos(null)
|
|
586
|
+
|
|
587
|
+
try {
|
|
588
|
+
const newConnector = await api.workspace.connectors.create(cid, {
|
|
589
|
+
source_element_id: sourceElementId,
|
|
590
|
+
target_element_id: targetElementId,
|
|
591
|
+
source_handle: sourceHandle,
|
|
592
|
+
target_handle: logicalTargetHandle,
|
|
593
|
+
direction: 'forward',
|
|
594
|
+
})
|
|
595
|
+
const connector = connectorToConnector(newConnector)
|
|
596
|
+
await finalizeConnectorCreate(connector)
|
|
597
|
+
} catch { /* intentionally empty */ }
|
|
598
|
+
}, [canEdit, finalizeConnectorCreate, interactionSourceIdRef, viewIdRef])
|
|
599
|
+
|
|
600
|
+
const stableOnInteractionStart = useCallback((elementId: number, options?: InteractionStartOptions) => {
|
|
601
|
+
if (!canEdit) return
|
|
602
|
+
const sourceHandle = options?.sourceHandle
|
|
603
|
+
|
|
604
|
+
if (sourceHandle) {
|
|
605
|
+
const cursorPos = options?.clientX !== undefined && options.clientY !== undefined
|
|
606
|
+
? { x: options.clientX, y: options.clientY }
|
|
607
|
+
: lastMousePosRef.current
|
|
608
|
+
? { x: lastMousePosRef.current.clientX, y: lastMousePosRef.current.clientY }
|
|
609
|
+
: null
|
|
610
|
+
if (!cursorPos) return
|
|
611
|
+
|
|
612
|
+
const activeSourceId = interactionSourceIdRef.current
|
|
613
|
+
if (activeSourceId !== null && activeSourceId !== elementId) {
|
|
614
|
+
void connectClickModeToHandle(elementId, sourceHandle)
|
|
615
|
+
return
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
pendingConnectionSourceHandleRef.current = getLogicalHandleId(sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE) ?? DEFAULT_SOURCE_HANDLE_SIDE
|
|
619
|
+
setInteractionSourceId(elementId)
|
|
620
|
+
setPendingConnectionSource(null)
|
|
621
|
+
setAddingElementAt(null)
|
|
622
|
+
setClickConnectMode({
|
|
623
|
+
sourceNodeId: String(elementId),
|
|
624
|
+
sourceHandle: ensureVisualHandleId(sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE) ?? sourceHandle,
|
|
625
|
+
})
|
|
626
|
+
setClickConnectCursorPos(cursorPos)
|
|
627
|
+
setConnectGhostPos(cursorPos)
|
|
628
|
+
return
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const isCancelling = interactionSourceIdRef.current === elementId
|
|
632
|
+
const nextSourceId = isCancelling ? null : elementId
|
|
633
|
+
|
|
634
|
+
if (isCancelling) pendingConnectionSourceHandleRef.current = null
|
|
635
|
+
setInteractionSourceId(nextSourceId)
|
|
636
|
+
|
|
637
|
+
if (isCancelling) {
|
|
638
|
+
setClickConnectMode(null)
|
|
639
|
+
setClickConnectCursorPos(null)
|
|
640
|
+
setConnectGhostPos(null)
|
|
641
|
+
}
|
|
642
|
+
}, [canEdit, connectClickModeToHandle, interactionSourceIdRef])
|
|
474
643
|
|
|
475
644
|
// ── Node/connector changes ─────────────────────────────────────────────────────
|
|
476
645
|
const onNodesChange = useCallback((changes: NodeChange[]) => {
|
|
477
646
|
if (!canEdit) {
|
|
478
647
|
const nonMutating = changes.filter((c) => c.type !== 'position')
|
|
479
648
|
if (nonMutating.length === 0) return
|
|
480
|
-
setRfNodes((nds) =>
|
|
649
|
+
setRfNodes((nds) => applyNodeChangesWithStructuralSharing(nonMutating, nds))
|
|
481
650
|
return
|
|
482
651
|
}
|
|
483
|
-
setRfNodes((nds) =>
|
|
652
|
+
setRfNodes((nds) => applyNodeChangesWithStructuralSharing(changes, nds))
|
|
484
653
|
}, [canEdit, setRfNodes])
|
|
485
654
|
|
|
486
655
|
const onEdgesChange = useCallback((changes: EdgeChange[]) => {
|
|
@@ -494,18 +663,11 @@ export function useCanvasInteractions({
|
|
|
494
663
|
dragStartPositionsRef.current[node.id] = { x: node.position.x, y: node.position.y }
|
|
495
664
|
}, [canEdit, viewId])
|
|
496
665
|
|
|
497
|
-
const onNodeDrag: NodeDragHandler = useCallback((
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
prev.map((element) =>
|
|
503
|
-
element.element_id === elementId
|
|
504
|
-
? { ...element, position_x: node.position.x, position_y: node.position.y }
|
|
505
|
-
: element,
|
|
506
|
-
),
|
|
507
|
-
)
|
|
508
|
-
}, [canEdit, setViewElements, viewId])
|
|
666
|
+
const onNodeDrag: NodeDragHandler = useCallback(() => {
|
|
667
|
+
// React Flow already updates rfNodes via onNodesChange while dragging.
|
|
668
|
+
// Mirroring into viewElements here forces every derived edge/node to rebuild
|
|
669
|
+
// on each pointer frame, so persist to app state only on drag stop.
|
|
670
|
+
}, [])
|
|
509
671
|
|
|
510
672
|
const positionTimers = useRef<Record<string, ReturnType<typeof setTimeout>>>({})
|
|
511
673
|
const dragStartPositionsRef = useRef<Record<string, { x: number; y: number }>>({})
|
|
@@ -522,13 +684,7 @@ export function useCanvasInteractions({
|
|
|
522
684
|
return
|
|
523
685
|
}
|
|
524
686
|
|
|
525
|
-
|
|
526
|
-
prev.map((element) =>
|
|
527
|
-
element.element_id === elementId
|
|
528
|
-
? { ...element, position_x: node.position.x, position_y: node.position.y }
|
|
529
|
-
: element,
|
|
530
|
-
),
|
|
531
|
-
)
|
|
687
|
+
updateElementPosition(elementId, node.position.x, node.position.y)
|
|
532
688
|
clearTimeout(positionTimers.current[node.id])
|
|
533
689
|
positionTimers.current[node.id] = setTimeout(() => {
|
|
534
690
|
api.workspace.views.placements
|
|
@@ -536,7 +692,7 @@ export function useCanvasInteractions({
|
|
|
536
692
|
.catch(() => { /* intentionally empty */ })
|
|
537
693
|
}, 400)
|
|
538
694
|
delete dragStartPositionsRef.current[node.id]
|
|
539
|
-
}, [canEdit,
|
|
695
|
+
}, [canEdit, updateElementPosition, viewId, viewElementsRef])
|
|
540
696
|
|
|
541
697
|
// ── Connections ────────────────────────────────────────────────────────────
|
|
542
698
|
const onConnect: OnConnect = useCallback(async (params: Connection) => {
|
|
@@ -554,25 +710,23 @@ export function useCanvasInteractions({
|
|
|
554
710
|
source_handle: sourceHandle, target_handle: targetHandle,
|
|
555
711
|
direction: 'forward', style: 'bezier',
|
|
556
712
|
})
|
|
557
|
-
|
|
558
|
-
|
|
713
|
+
const connector = connectorToConnector(newConnector)
|
|
714
|
+
await finalizeConnectorCreate(connector)
|
|
559
715
|
} catch { /* intentionally empty */ }
|
|
560
|
-
}, [canEdit,
|
|
716
|
+
}, [canEdit, finalizeConnectorCreate, viewId])
|
|
561
717
|
|
|
562
718
|
const onConnectStart = useCallback((_: React.MouseEvent | React.TouchEvent, { nodeId }: OnConnectStartParams) => {
|
|
563
719
|
if (!canEdit || isReconnectingRef.current) return
|
|
564
720
|
connectingSourceRef.current = nodeId
|
|
565
721
|
connectWasValidRef.current = false
|
|
722
|
+
const handleTargets = collectHandleTargets(nodeId ?? undefined)
|
|
723
|
+
connectorDragLastUpdateRef.current = 0
|
|
566
724
|
const listener = (e: MouseEvent) => {
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
nearHandle = true; break
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
setConnectGhostPos(nearHandle ? null : { x: e.clientX, y: e.clientY })
|
|
725
|
+
const now = performance.now()
|
|
726
|
+
if (now - connectorDragLastUpdateRef.current < CONNECTOR_DRAG_UPDATE_INTERVAL_MS) return
|
|
727
|
+
connectorDragLastUpdateRef.current = now
|
|
728
|
+
const hit = findNearestHandleTargetInCache(handleTargets, e.clientX, e.clientY)
|
|
729
|
+
setConnectGhostPos(hit.nearHandle ? null : { x: e.clientX, y: e.clientY })
|
|
576
730
|
}
|
|
577
731
|
connectGhostListenerRef.current = listener
|
|
578
732
|
document.addEventListener('mousemove', listener)
|
|
@@ -618,15 +772,15 @@ export function useCanvasInteractions({
|
|
|
618
772
|
source_element_id: sourceElementId, target_element_id: targetElementId,
|
|
619
773
|
source_handle: sourceHandle, target_handle: targetHandle, direction: 'forward',
|
|
620
774
|
}).then((connector) => {
|
|
621
|
-
|
|
622
|
-
|
|
775
|
+
const next = connectorToConnector(connector)
|
|
776
|
+
void finalizeConnectorCreate(next)
|
|
623
777
|
}).catch(() => { /* intentionally empty */ })
|
|
624
778
|
} else {
|
|
625
779
|
setPendingConnectionSource(sourceElementId)
|
|
626
780
|
suppressNextPaneClickRef.current = true
|
|
627
781
|
showAddingElementAt(clientX, clientY, true, 'connect', 'shiftKey' in event && event.shiftKey)
|
|
628
782
|
}
|
|
629
|
-
}, [canEdit,
|
|
783
|
+
}, [canEdit, finalizeConnectorCreate, showAddingElementAt, rfNodesRef, viewIdRef])
|
|
630
784
|
|
|
631
785
|
// ── Reconnect ──────────────────────────────────────────────────────────────
|
|
632
786
|
const performReconnect = useCallback(async (oldConnector: RFEdge, newConnection: Connection) => {
|
|
@@ -636,7 +790,6 @@ export function useCanvasInteractions({
|
|
|
636
790
|
const targetId = parseNumericId(newConnection.target)
|
|
637
791
|
if (edgeId === null || sourceId === null || targetId === null) return
|
|
638
792
|
setRfEdges((eds) => reconnectEdge(oldConnector, newConnection, eds))
|
|
639
|
-
setSelectedEdgeId(null)
|
|
640
793
|
try {
|
|
641
794
|
const existingData = oldConnector.data as Connector
|
|
642
795
|
const sourceHandle = getLogicalHandleId(newConnection.sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE)
|
|
@@ -650,12 +803,11 @@ export function useCanvasInteractions({
|
|
|
650
803
|
style: existingData?.style === 'default' ? 'bezier' : (existingData?.style ?? 'bezier'),
|
|
651
804
|
url: existingData?.url ?? undefined, relationship: existingData?.relationship ?? undefined,
|
|
652
805
|
})
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
)
|
|
806
|
+
const connector = connectorToConnector(updated)
|
|
807
|
+
upsertConnectorGraphSnapshot(connector)
|
|
808
|
+
replaceConnector(connector)
|
|
657
809
|
} catch { /* intentionally empty */ }
|
|
658
|
-
}, [canEdit,
|
|
810
|
+
}, [canEdit, replaceConnector, viewId, setRfEdges])
|
|
659
811
|
const onReconnect = useCallback(async (oldConnector: RFEdge, newConnection: Connection) => {
|
|
660
812
|
await performReconnect(oldConnector, newConnection)
|
|
661
813
|
}, [performReconnect])
|
|
@@ -685,6 +837,8 @@ export function useCanvasInteractions({
|
|
|
685
837
|
setConnectGhostPos(null)
|
|
686
838
|
clearHandleReconnectListeners()
|
|
687
839
|
isReconnectingRef.current = true
|
|
840
|
+
const handleTargets = collectHandleTargets(fixedNodeId)
|
|
841
|
+
connectorDragLastUpdateRef.current = 0
|
|
688
842
|
syncHandleReconnectDrag({
|
|
689
843
|
edgeId: args.edgeId,
|
|
690
844
|
endpoint: args.endpoint,
|
|
@@ -695,7 +849,10 @@ export function useCanvasInteractions({
|
|
|
695
849
|
})
|
|
696
850
|
|
|
697
851
|
const move = (event: PointerEvent) => {
|
|
698
|
-
const
|
|
852
|
+
const now = performance.now()
|
|
853
|
+
if (now - connectorDragLastUpdateRef.current < CONNECTOR_DRAG_UPDATE_INTERVAL_MS) return
|
|
854
|
+
connectorDragLastUpdateRef.current = now
|
|
855
|
+
const hit = findNearestHandleTargetInCache(handleTargets, event.clientX, event.clientY)
|
|
699
856
|
const current = handleReconnectDragRef.current
|
|
700
857
|
if (!current) return
|
|
701
858
|
syncHandleReconnectDrag({
|
|
@@ -776,7 +933,7 @@ export function useCanvasInteractions({
|
|
|
776
933
|
document.addEventListener('pointermove', move)
|
|
777
934
|
document.addEventListener('pointerup', up)
|
|
778
935
|
document.addEventListener('pointercancel', up)
|
|
779
|
-
}, [canEdit, clearHandleReconnectListeners,
|
|
936
|
+
}, [canEdit, clearHandleReconnectListeners, performReconnect, rfNodesRef, _rfEdgesRef, syncHandleReconnectDrag])
|
|
780
937
|
|
|
781
938
|
// ── Click-connect ghost cursor tracking ────────────────────────────────────
|
|
782
939
|
useEffect(() => {
|
|
@@ -785,43 +942,29 @@ export function useCanvasInteractions({
|
|
|
785
942
|
setConnectGhostPos(null)
|
|
786
943
|
return
|
|
787
944
|
}
|
|
945
|
+
const handleTargets = collectHandleTargets(clickConnectMode.sourceNodeId)
|
|
946
|
+
const sourceHandleTargets = collectHandleTargets().filter((target) => target.nodeId === clickConnectMode.sourceNodeId)
|
|
947
|
+
connectorDragLastUpdateRef.current = 0
|
|
788
948
|
const listener = (e: MouseEvent) => {
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
if (handle.closest(`[data-id="${clickConnectMode.sourceNodeId}"]`)) continue
|
|
796
|
-
const rect = handle.getBoundingClientRect()
|
|
797
|
-
const cx = rect.left + rect.width / 2
|
|
798
|
-
const cy = rect.top + rect.height / 2
|
|
799
|
-
const dist = Math.hypot(e.clientX - cx, e.clientY - cy)
|
|
800
|
-
if (dist < 36 && dist < nearestTargetHandleDistance) {
|
|
801
|
-
nearestTargetHandleDistance = dist
|
|
802
|
-
nearHandle = true
|
|
803
|
-
snapPos = { x: cx, y: cy }
|
|
804
|
-
hoveredTargetHandleId = handle.getAttribute('data-handleid') || handle.id
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
setClickConnectCursorPos(snapPos)
|
|
808
|
-
setConnectGhostPos(nearHandle ? null : { x: e.clientX, y: e.clientY })
|
|
949
|
+
const now = performance.now()
|
|
950
|
+
if (now - connectorDragLastUpdateRef.current < CONNECTOR_DRAG_UPDATE_INTERVAL_MS) return
|
|
951
|
+
connectorDragLastUpdateRef.current = now
|
|
952
|
+
const hit = findNearestHandleTargetInCache(handleTargets, e.clientX, e.clientY)
|
|
953
|
+
setClickConnectCursorPos(hit.snapPos)
|
|
954
|
+
setConnectGhostPos(hit.nearHandle ? null : { x: e.clientX, y: e.clientY })
|
|
809
955
|
setClickConnectMode((prev) => {
|
|
810
956
|
if (!prev) return null
|
|
811
957
|
let bestHandle = prev.sourceHandle
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
const
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
const cx = rect.left + rect.width / 2; const cy = rect.top + rect.height / 2
|
|
819
|
-
const dist = Math.hypot(e.clientX - cx, e.clientY - cy)
|
|
820
|
-
if (dist < minDist) { minDist = dist; bestHandle = h.getAttribute('data-handleid') || h.id }
|
|
958
|
+
let minDist = Infinity
|
|
959
|
+
for (const target of sourceHandleTargets) {
|
|
960
|
+
const dist = Math.hypot(e.clientX - target.x, e.clientY - target.y)
|
|
961
|
+
if (dist < minDist) {
|
|
962
|
+
minDist = dist
|
|
963
|
+
bestHandle = target.handleId
|
|
821
964
|
}
|
|
822
965
|
}
|
|
823
|
-
if (prev.sourceHandle !== bestHandle || prev.targetHandle !==
|
|
824
|
-
return { ...prev, sourceHandle: bestHandle, targetHandle:
|
|
966
|
+
if (prev.sourceHandle !== bestHandle || prev.targetHandle !== hit.hoveredHandleId) {
|
|
967
|
+
return { ...prev, sourceHandle: bestHandle, targetHandle: hit.hoveredHandleId }
|
|
825
968
|
}
|
|
826
969
|
return prev
|
|
827
970
|
})
|
|
@@ -855,7 +998,6 @@ export function useCanvasInteractions({
|
|
|
855
998
|
setSelectedElement(null)
|
|
856
999
|
closeElementPanel()
|
|
857
1000
|
setSelectedEdge(null)
|
|
858
|
-
setSelectedEdgeId(null)
|
|
859
1001
|
closeConnectorPanel()
|
|
860
1002
|
setSelectedProxyConnectorDetails((rfConnector.data as { details?: import('../../../crossBranch/types').ProxyConnectorDetails }).details ?? null)
|
|
861
1003
|
openProxyConnectorPanel()
|
|
@@ -863,16 +1005,14 @@ export function useCanvasInteractions({
|
|
|
863
1005
|
}
|
|
864
1006
|
const clickedId = parseNumericId(rfConnector.id)
|
|
865
1007
|
if (clickedId === null) return
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
}
|
|
875
|
-
}, [closeConnectorPanel, closeElementPanel, connectors, openProxyConnectorPanel, selectedEdgeId, setSelectedEdge, setSelectedEdgeId, setSelectedElement, setSelectedProxyConnectorDetails])
|
|
1008
|
+
const connector = connectors.find((e) => e.id === clickedId)
|
|
1009
|
+
if (!connector) return
|
|
1010
|
+
setSelectedElement(null)
|
|
1011
|
+
closeElementPanel()
|
|
1012
|
+
setSelectedProxyConnectorDetails(null)
|
|
1013
|
+
setSelectedEdge(connector)
|
|
1014
|
+
openConnectorPanelRef.current()
|
|
1015
|
+
}, [closeElementPanel, connectors, openProxyConnectorPanel, setSelectedEdge, setSelectedElement, setSelectedProxyConnectorDetails])
|
|
876
1016
|
|
|
877
1017
|
// ── Pane interactions ─────────────────────────────────────────────────────
|
|
878
1018
|
const onPaneClick = useCallback((e: React.MouseEvent) => {
|
|
@@ -881,7 +1021,6 @@ export function useCanvasInteractions({
|
|
|
881
1021
|
setReconnectPicking(null)
|
|
882
1022
|
setSelectedElement(null)
|
|
883
1023
|
setSelectedEdge(null)
|
|
884
|
-
setSelectedEdgeId(null)
|
|
885
1024
|
setSelectedProxyConnectorDetails(null)
|
|
886
1025
|
setConnectorLongPressMenu(null)
|
|
887
1026
|
setCanvasMenu(null)
|
|
@@ -904,14 +1043,18 @@ export function useCanvasInteractions({
|
|
|
904
1043
|
} else {
|
|
905
1044
|
setInteractionSourceId(null)
|
|
906
1045
|
setPendingConnectionSource(sourceId)
|
|
1046
|
+
pendingConnectionSourceHandleRef.current = clickConnectModeRef.current?.sourceHandle
|
|
1047
|
+
? getLogicalHandleId(clickConnectModeRef.current.sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE) ?? DEFAULT_SOURCE_HANDLE_SIDE
|
|
1048
|
+
: pendingConnectionSourceHandleRef.current
|
|
907
1049
|
showAddingElementAt(e.clientX, e.clientY, true, 'connect', e.shiftKey)
|
|
908
1050
|
}
|
|
909
1051
|
return
|
|
910
1052
|
}
|
|
911
1053
|
setInteractionSourceId(null)
|
|
912
1054
|
setPendingConnectionSource(null)
|
|
1055
|
+
pendingConnectionSourceHandleRef.current = null
|
|
913
1056
|
setAddingElementAt(null)
|
|
914
|
-
}, [stableOnConnectTo, showAddingElementAt, closeElementPanel, closeConnectorPanel, closeProxyConnectorPanel, rfNodesRef, interactionSourceIdRef, setSelectedElement, setSelectedEdge,
|
|
1057
|
+
}, [stableOnConnectTo, showAddingElementAt, closeElementPanel, closeConnectorPanel, closeProxyConnectorPanel, rfNodesRef, interactionSourceIdRef, setSelectedElement, setSelectedEdge, setSelectedProxyConnectorDetails])
|
|
915
1058
|
|
|
916
1059
|
const onPaneContextMenu = useCallback((e: React.MouseEvent) => {
|
|
917
1060
|
e.preventDefault()
|
|
@@ -930,21 +1073,23 @@ export function useCanvasInteractions({
|
|
|
930
1073
|
}, [])
|
|
931
1074
|
|
|
932
1075
|
const onMoveStart = useCallback(() => {
|
|
1076
|
+
if (isMovingRef.current) return
|
|
1077
|
+
isMovingRef.current = true
|
|
933
1078
|
setCanvasMenu(null)
|
|
934
1079
|
setConnectorLongPressMenu(null)
|
|
935
1080
|
setAddingElementAt(null)
|
|
936
|
-
setRfNodes((nds) => nds.map((n) => ({ ...n, data: { ...n.data, isCanvasMoving: true } })))
|
|
937
1081
|
onMoveStateChange?.(true)
|
|
938
|
-
}, [
|
|
1082
|
+
}, [onMoveStateChange])
|
|
939
1083
|
|
|
940
1084
|
const onMove = useCallback((_: unknown, viewport: { x: number; y: number; zoom: number }) => {
|
|
941
1085
|
drawingCanvasRef.current?.notifyViewportChange(viewport)
|
|
942
1086
|
}, [drawingCanvasRef])
|
|
943
1087
|
|
|
944
1088
|
const onMoveEnd = useCallback(() => {
|
|
945
|
-
|
|
1089
|
+
if (!isMovingRef.current) return
|
|
1090
|
+
isMovingRef.current = false
|
|
946
1091
|
onMoveStateChange?.(false)
|
|
947
|
-
}, [
|
|
1092
|
+
}, [onMoveStateChange])
|
|
948
1093
|
|
|
949
1094
|
// ── Touch & long-press ────────────────────────────────────────────────────
|
|
950
1095
|
function getTouchDistance(touches: Map<number, { x: number; y: number }>): number {
|
|
@@ -1073,10 +1218,12 @@ export function useCanvasInteractions({
|
|
|
1073
1218
|
setSelectedElement(null)
|
|
1074
1219
|
closeElementPanel()
|
|
1075
1220
|
}
|
|
1076
|
-
} else
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1221
|
+
} else {
|
|
1222
|
+
const connectorId = getConnectorDeletionTarget(selectedConnector)
|
|
1223
|
+
if (connectorId === null) return
|
|
1224
|
+
api.workspace.connectors.delete('', connectorId).then(() => {
|
|
1225
|
+
handleConnectorDeleted(connectorId)
|
|
1226
|
+
setSelectedEdge(null)
|
|
1080
1227
|
closeConnectorPanel()
|
|
1081
1228
|
}).catch(() => { /* intentionally empty */ })
|
|
1082
1229
|
}
|
|
@@ -1185,7 +1332,7 @@ export function useCanvasInteractions({
|
|
|
1185
1332
|
}
|
|
1186
1333
|
window.addEventListener('keydown', handler)
|
|
1187
1334
|
return () => window.removeEventListener('keydown', handler)
|
|
1188
|
-
}, [canEdit, refreshGrid, selectedElement,
|
|
1335
|
+
}, [canEdit, refreshGrid, selectedElement, selectedConnector, viewId, stableOnRemoveElement, handleConnectorDeleted, handleElementPermanentlyDeleted, closeElementPanel, closeConnectorPanel, viewIdRef, incomingLinksRef, treeDataRef, navigateRef, rfNodesRef, viewElementsRef, setLinksMap, showAddingElementAt, setSelectedElement, setSelectedEdge, containerRef, linksMapRef])
|
|
1189
1336
|
|
|
1190
1337
|
// ── DnD handlers ──────────────────────────────────────────────────────────
|
|
1191
1338
|
const onDragOver = useCallback((e: React.DragEvent) => {
|
|
@@ -1300,6 +1447,7 @@ export function useCanvasInteractions({
|
|
|
1300
1447
|
stableOnHoverZoom,
|
|
1301
1448
|
stableOnRemoveElement,
|
|
1302
1449
|
stableOnConnectTo,
|
|
1450
|
+
stableOnInteractionStart,
|
|
1303
1451
|
stableOnStartHandleReconnect,
|
|
1304
1452
|
showAddingElementAt,
|
|
1305
1453
|
// RF event handlers
|