@@ -12,8 +12,16 @@ import type {
12
12
INodeOutputSlot
13
13
} from '@/lib/litegraph/src/interfaces'
14
14
import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
15
+ import {
16
+ clearCanvasPointerHistory ,
17
+ toCanvasPointerEvent
18
+ } from '@/renderer/core/canvas/interaction/canvasPointerEvent'
15
19
import { createLinkConnectorAdapter } from '@/renderer/core/canvas/links/linkConnectorAdapter'
16
20
import type { LinkConnectorAdapter } from '@/renderer/core/canvas/links/linkConnectorAdapter'
21
+ import {
22
+ resolveNodeSurfaceCandidate ,
23
+ resolveSlotTargetCandidate
24
+ } from '@/renderer/core/canvas/links/linkDropOrchestrator'
17
25
import {
18
26
type SlotDropCandidate ,
19
27
useSlotLinkDragState
@@ -98,102 +106,38 @@ export function useSlotLinkInteraction({
98
106
// Per-drag drag-state cache
99
107
const dragSession = createSlotLinkDragSession ( )
100
108
101
- function candidateFromTarget (
102
- target : EventTarget | null
103
- ) : SlotDropCandidate | null {
104
- if ( ! ( target instanceof HTMLElement ) ) return null
105
- const elWithKey = target . closest < HTMLElement > ( '[data-slot-key]' )
106
- const key = elWithKey ?. dataset [ 'slotKey' ]
107
- if ( ! key ) return null
108
-
109
- const layout = layoutStore . getSlotLayout ( key )
110
- if ( ! layout ) return null
111
-
112
- const candidate : SlotDropCandidate = { layout, compatible : false }
113
-
114
- const graph = app . canvas ?. graph
115
- const adapter = ensureActiveAdapter ( )
116
- if ( graph && adapter ) {
117
- const cached = dragSession . compatCache . get ( key )
118
- if ( cached != null ) {
119
- candidate . compatible = cached
120
- } else {
121
- const compatible =
122
- layout . type === 'input'
123
- ? adapter . isInputValidDrop ( layout . nodeId , layout . index )
124
- : adapter . isOutputValidDrop ( layout . nodeId , layout . index )
125
- dragSession . compatCache . set ( key , compatible )
126
- candidate . compatible = compatible
127
- }
109
+ const resolveRenderLinkSource = ( link : RenderLink ) : Point | null => {
110
+ if ( link . fromReroute ) {
111
+ const rerouteLayout = layoutStore . getRerouteLayout ( link . fromReroute . id )
112
+ if ( rerouteLayout ) return rerouteLayout . position
113
+ const [ x , y ] = link . fromReroute . pos
114
+ return toPoint ( x , y )
128
115
}
129
116
130
- return candidate
131
- }
132
-
133
- function candidateFromNodeTarget (
134
- target : EventTarget | null
135
- ) : SlotDropCandidate | null {
136
- if ( ! ( target instanceof HTMLElement ) ) return null
137
- const elWithNode = target . closest < HTMLElement > ( '[data-node-id]' )
138
- const nodeIdStr = elWithNode ?. dataset [ 'nodeId' ]
139
- if ( ! nodeIdStr ) return null
140
-
141
- const adapter = ensureActiveAdapter ( )
142
- const graph = app . canvas ?. graph
143
- if ( ! adapter || ! graph ) return null
144
-
145
- const nodeId = Number ( nodeIdStr )
146
-
147
- // Cached preferred slot for this node within this drag
148
- const cachedPreferred = dragSession . nodePreferred . get ( nodeId )
149
- if ( cachedPreferred !== undefined ) {
150
- return cachedPreferred
151
- ? { layout : cachedPreferred . layout , compatible : true }
152
- : null
117
+ const nodeId = link . node . id
118
+ if ( nodeId != null ) {
119
+ const isInputFrom = link . toType === 'output'
120
+ const key = getSlotKey ( String ( nodeId ) , link . fromSlotIndex , isInputFrom )
121
+ const layout = layoutStore . getSlotLayout ( key )
122
+ if ( layout ) return layout . position
153
123
}
154
124
155
- const node = graph . getNodeById ( nodeId )
156
- if ( ! node ) return null
157
-
158
- const firstLink = adapter . renderLinks [ 0 ]
159
- if ( ! firstLink ) return null
160
- const connectingTo = adapter . linkConnector . state . connectingTo
161
-
162
- if ( connectingTo !== 'input' && connectingTo !== 'output' ) return null
163
-
164
- const isInput = connectingTo === 'input'
165
- const slotType = firstLink . fromSlot . type
166
-
167
- const res = isInput
168
- ? node . findInputByType ( slotType )
169
- : node . findOutputByType ( slotType )
170
-
171
- const index = res ?. index
172
- if ( index == null ) return null
173
-
174
- const key = getSlotKey ( String ( nodeId ) , index , isInput )
175
- const layout = layoutStore . getSlotLayout ( key )
176
- if ( ! layout ) return null
177
-
178
- const compatible = isInput
179
- ? adapter . isInputValidDrop ( nodeId , index )
180
- : adapter . isOutputValidDrop ( nodeId , index )
181
-
182
- if ( compatible ) {
183
- dragSession . compatCache . set ( key , true )
184
- const preferred = { index, key, layout }
185
- dragSession . nodePreferred . set ( nodeId , preferred )
186
- return { layout, compatible : true }
187
- } else {
188
- dragSession . compatCache . set ( key , false )
189
- dragSession . nodePreferred . set ( nodeId , null )
190
- return null
191
- }
125
+ const pos = link . fromPos
126
+ return toPoint ( pos [ 0 ] , pos [ 1 ] )
192
127
}
193
128
194
- const ensureActiveAdapter = ( ) : LinkConnectorAdapter | null => {
195
- if ( ! activeAdapter ) activeAdapter = createLinkConnectorAdapter ( )
196
- return activeAdapter
129
+ const syncRenderLinkOrigins = ( ) => {
130
+ if ( ! activeAdapter ) return
131
+ for ( const link of activeAdapter . renderLinks ) {
132
+ const origin = resolveRenderLinkSource ( link )
133
+ if ( ! origin ) continue
134
+ const x = origin . x
135
+ const y = origin . y
136
+ if ( link . fromPos [ 0 ] !== x || link . fromPos [ 1 ] !== y ) {
137
+ link . fromPos [ 0 ] = x
138
+ link . fromPos [ 1 ] = y
139
+ }
140
+ }
197
141
}
198
142
199
143
function hasCanConnectToReroute (
@@ -317,6 +261,9 @@ export function useSlotLinkInteraction({
317
261
}
318
262
319
263
const cleanupInteraction = ( ) => {
264
+ if ( state . pointerId != null ) {
265
+ clearCanvasPointerHistory ( state . pointerId )
266
+ }
320
267
activeAdapter ?. reset ( )
321
268
pointerSession . clear ( )
322
269
endDrag ( )
@@ -347,6 +294,8 @@ export function useSlotLinkInteraction({
347
294
] )
348
295
updatePointerPosition ( data . clientX , data . clientY , canvasX , canvasY )
349
296
297
+ syncRenderLinkOrigins ( )
298
+
350
299
let hoveredSlotKey : string | null = null
351
300
let hoveredNodeId : number | null = null
352
301
const target = data . target
@@ -368,10 +317,13 @@ export function useSlotLinkInteraction({
368
317
let candidate : SlotDropCandidate | null = state . candidate
369
318
370
319
if ( hoverChanged ) {
371
- const slotCandidate = candidateFromTarget ( target )
320
+ const adapter = activeAdapter
321
+ const graph = app . canvas ?. graph ?? null
322
+ const context = { adapter, graph, session : dragSession }
323
+ const slotCandidate = resolveSlotTargetCandidate ( target , context )
372
324
const nodeCandidate = slotCandidate
373
325
? null
374
- : candidateFromNodeTarget ( target )
326
+ : resolveNodeSurfaceCandidate ( target , context )
375
327
candidate = slotCandidate ?? nodeCandidate
376
328
dragSession . lastHoverSlotKey = hoveredSlotKey
377
329
dragSession . lastHoverNodeId = hoveredNodeId
@@ -391,7 +343,17 @@ export function useSlotLinkInteraction({
391
343
dragSession . lastCandidateKey = newCandidateKey
392
344
}
393
345
394
- app . canvas ?. setDirty ( true )
346
+ if ( activeAdapter ) {
347
+ const snapX = newCandidate
348
+ ? newCandidate . layout . position . x
349
+ : state . pointer . canvas . x
350
+ const snapY = newCandidate
351
+ ? newCandidate . layout . position . y
352
+ : state . pointer . canvas . y
353
+ activeAdapter . linkConnector . state . snapLinksPos = [ snapX , snapY ]
354
+ }
355
+
356
+ app . canvas ?. setDirty ( true , true )
395
357
}
396
358
const raf = createRafBatch ( processPointerMoveFrame )
397
359
@@ -411,7 +373,7 @@ export function useSlotLinkInteraction({
411
373
) : boolean => {
412
374
if ( ! candidate ?. compatible ) return false
413
375
const graph = app . canvas ?. graph
414
- const adapter = ensureActiveAdapter ( )
376
+ const adapter = activeAdapter
415
377
if ( ! graph || ! adapter ) return false
416
378
417
379
const nodeId = Number ( candidate . layout . nodeId )
@@ -444,7 +406,7 @@ export function useSlotLinkInteraction({
444
406
y : state . pointer . canvas . y
445
407
} )
446
408
const graph = app . canvas ?. graph
447
- const adapter = ensureActiveAdapter ( )
409
+ const adapter = activeAdapter
448
410
if ( ! rerouteLayout || ! graph || ! adapter ) return false
449
411
450
412
const reroute = graph . getReroute ( rerouteLayout . id )
@@ -492,13 +454,14 @@ export function useSlotLinkInteraction({
492
454
493
455
const finishInteraction = ( event : PointerEvent ) => {
494
456
if ( ! pointerSession . matches ( event ) ) return
495
- event . preventDefault ( )
457
+ const canvasEvent = toCanvasPointerEvent ( event )
458
+ canvasEvent . preventDefault ( )
496
459
497
460
raf . flush ( )
498
461
499
462
if ( ! state . source ) {
500
463
cleanupInteraction ( )
501
- app . canvas ?. setDirty ( true )
464
+ app . canvas ?. setDirty ( true , true )
502
465
return
503
466
}
504
467
@@ -511,24 +474,32 @@ export function useSlotLinkInteraction({
511
474
512
475
// Fallback to DOM slot under pointer (if any), then node fallback, then reroute
513
476
if ( ! connected ) {
514
- const domCandidate = candidateFromTarget ( event . target )
477
+ const adapter = activeAdapter
478
+ const graph = app . canvas ?. graph ?? null
479
+ const context = { adapter, graph, session : dragSession }
480
+ const domCandidate = resolveSlotTargetCandidate (
481
+ canvasEvent . target ,
482
+ context
483
+ )
515
484
connected = tryConnectToCandidate ( domCandidate )
516
- }
517
485
518
- if ( ! connected ) {
519
- const nodeCandidate = candidateFromNodeTarget ( event . target )
520
- connected = tryConnectToCandidate ( nodeCandidate )
486
+ if ( ! connected ) {
487
+ const nodeCandidate = resolveNodeSurfaceCandidate (
488
+ canvasEvent . target ,
489
+ context
490
+ )
491
+ connected = tryConnectToCandidate ( nodeCandidate )
492
+ }
521
493
}
522
494
523
495
if ( ! connected ) connected = tryConnectViaRerouteAtPointer ( ) || connected
524
496
525
- // Drop on canvas: disconnect moving input link(s)
526
- if ( ! connected && ! snappedCandidate && state . source . type === 'input' ) {
527
- ensureActiveAdapter ( ) ?. disconnectMovingLinks ( )
497
+ if ( ! connected ) {
498
+ if ( activeAdapter ) activeAdapter . dropOnCanvas ( canvasEvent )
528
499
}
529
500
530
501
cleanupInteraction ( )
531
- app . canvas ?. setDirty ( true )
502
+ app . canvas ?. setDirty ( true , true )
532
503
}
533
504
534
505
const handlePointerUp = ( event : PointerEvent ) => {
@@ -539,8 +510,9 @@ export function useSlotLinkInteraction({
539
510
if ( ! pointerSession . matches ( event ) ) return
540
511
541
512
raf . flush ( )
513
+ toCanvasPointerEvent ( event )
542
514
cleanupInteraction ( )
543
- app . canvas ?. setDirty ( true )
515
+ app . canvas ?. setDirty ( true , true )
544
516
}
545
517
546
518
const onPointerDown = ( event : PointerEvent ) => {
@@ -552,7 +524,8 @@ export function useSlotLinkInteraction({
552
524
const graph = canvas ?. graph
553
525
if ( ! canvas || ! graph ) return
554
526
555
- ensureActiveAdapter ( )
527
+ activeAdapter = createLinkConnectorAdapter ( )
528
+ if ( ! activeAdapter ) return
556
529
raf . cancel ( )
557
530
dragSession . reset ( )
558
531
@@ -610,19 +583,24 @@ export function useSlotLinkInteraction({
610
583
const shouldMoveExistingInput =
611
584
isInputSlot && ! shouldBreakExistingInputLink && hasExistingInputLink
612
585
613
- const adapter = ensureActiveAdapter ( )
614
- if ( adapter ) {
586
+ if ( activeAdapter ) {
615
587
if ( isOutputSlot ) {
616
- adapter . beginFromOutput ( numericNodeId , index , {
588
+ activeAdapter . beginFromOutput ( numericNodeId , index , {
617
589
moveExisting : shouldMoveExistingOutput
618
590
} )
619
591
} else {
620
- adapter . beginFromInput ( numericNodeId , index , {
592
+ activeAdapter . beginFromInput ( numericNodeId , index , {
621
593
moveExisting : shouldMoveExistingInput
622
594
} )
623
595
}
596
+
597
+ if ( shouldMoveExistingInput && existingInputLink ) {
598
+ existingInputLink . _dragging = true
599
+ }
624
600
}
625
601
602
+ syncRenderLinkOrigins ( )
603
+
626
604
const direction = existingAnchor ?. direction ?? baseDirection
627
605
const startPosition = existingAnchor ?. position ?? {
628
606
x : layout . position . x ,
@@ -646,8 +624,16 @@ export function useSlotLinkInteraction({
646
624
647
625
pointerSession . begin ( event . pointerId )
648
626
627
+ toCanvasPointerEvent ( event )
649
628
updatePointerState ( event )
650
629
630
+ if ( activeAdapter ) {
631
+ activeAdapter . linkConnector . state . snapLinksPos = [
632
+ state . pointer . canvas . x ,
633
+ state . pointer . canvas . y
634
+ ]
635
+ }
636
+
651
637
pointerSession . register (
652
638
useEventListener ( window , 'pointermove' , handlePointerMove , {
653
639
capture : true
@@ -659,7 +645,7 @@ export function useSlotLinkInteraction({
659
645
capture : true
660
646
} )
661
647
)
662
- app . canvas ?. setDirty ( true )
648
+ app . canvas ?. setDirty ( true , true )
663
649
event . preventDefault ( )
664
650
event . stopPropagation ( )
665
651
}
0 commit comments