More forgiving connections in vue (#6565)

The previous link connection code uses
[closest](https://developer.mozilla.org/en-US/docs/Web/API/Element/closest)
to find a slot. Closest only checks parents, not siblings. Since the
sought element has no children, this meant connection to a slot required
the mouse be directly over the slot.

This is changed by finding the closest (parent) widget or slot, and then
querying for the slot. For simplicity, this means introducing an
`lg-node-widget` class. As a result, connections can be made by hovering
anywhere over a valid widget.


![vue-connections_00001](https://github.com/user-attachments/assets/e556ff3f-8cbb-4198-998d-9c2aadf2c73c)


Resolves #6488

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6565-More-forgiving-connections-in-vue-2a16d73d365081e1bf46f5d54ec382d6)
by [Unito](https://www.unito.io)
This commit is contained in:
AustinMroz
2025-11-04 13:45:14 -08:00
committed by GitHub
parent a4fc68a9eb
commit 7c2a768d83
3 changed files with 13 additions and 14 deletions

View File

@@ -20,7 +20,9 @@ export const resolveSlotTargetCandidate = (
const { state: dragState, setCompatibleForKey } = useSlotLinkDragUIState()
if (!(target instanceof HTMLElement)) return null
const elWithKey = target.closest<HTMLElement>('[data-slot-key]')
const elWithKey = target
.closest('.lg-slot, .lg-node-widget')
?.querySelector<HTMLElement>('[data-slot-key]')
const key = elWithKey?.dataset['slotKey']
if (!key) return null

View File

@@ -19,7 +19,7 @@
<div
v-for="(widget, index) in processedWidgets"
:key="`widget-${index}-${widget.name}`"
class="group flex items-stretch has-[.widget-expands]:flex-1"
class="lg-node-widget group flex items-stretch has-[.widget-expands]:flex-1"
>
<!-- Widget Input Slot Dot -->

View File

@@ -301,14 +301,12 @@ export function useSlotLinkInteraction({
hoveredSlotKey = dragContext.lastPointerTargetSlotKey
hoveredNodeId = dragContext.lastPointerTargetNodeId
} else if (target instanceof HTMLElement) {
const elWithSlot = target.closest<HTMLElement>('[data-slot-key]')
const elWithNode = elWithSlot
? null
: target.closest<HTMLElement>('[data-node-id]')
const elWithSlot = target
.closest('.lg-slot, .lg-node-widget')
?.querySelector<HTMLElement>('[data-slot-key]')
const elWithNode = target.closest<HTMLElement>('[data-node-id]')
hoveredSlotKey = elWithSlot?.dataset['slotKey'] ?? null
hoveredNodeId = hoveredSlotKey
? null
: (elWithNode?.dataset['nodeId'] ?? null)
hoveredNodeId = elWithNode?.dataset['nodeId'] ?? null
dragContext.lastPointerEventTarget = target
dragContext.lastPointerTargetSlotKey = hoveredSlotKey
dragContext.lastPointerTargetNodeId = hoveredNodeId
@@ -325,10 +323,8 @@ export function useSlotLinkInteraction({
const graph = app.canvas?.graph ?? null
const context = { adapter, graph, session: dragContext }
const slotCandidate = resolveSlotTargetCandidate(target, context)
const nodeCandidate = slotCandidate
? null
: resolveNodeSurfaceSlotCandidate(target, context)
candidate = slotCandidate ?? nodeCandidate
const nodeCandidate = resolveNodeSurfaceSlotCandidate(target, context)
candidate = slotCandidate?.compatible ? slotCandidate : nodeCandidate
dragContext.lastHoverSlotKey = hoveredSlotKey
dragContext.lastHoverNodeId = hoveredNodeId
@@ -339,7 +335,8 @@ export function useSlotLinkInteraction({
slotCandidate.layout.type === 'input'
)
setCompatibleForKey(key, !!slotCandidate.compatible)
} else if (nodeCandidate) {
}
if (nodeCandidate && !slotCandidate?.compatible) {
const key = getSlotKey(
nodeCandidate.layout.nodeId,
nodeCandidate.layout.index,