Feat: Vue Node Slot Improvements (#6359)

## Summary

Several fixes and improvements to the slot behavior on Vue nodes.

## Changes

- **What**: Restore the pseudo-slots, if there are slots being hidden by
collapse
- **What**: Connections while collapsed
- **What**: Display the links in a more reasonable location
- **What**: Fixes styling of linked widgets
- **What**: [~Fix reconnecting logic to prioritize newly disconnected
and now empty
slots~](https://github.com/Comfy-Org/ComfyUI_frontend/pull/6370)

## Review Focus

<!-- Critical design decisions or edge cases that need attention -->

<!-- If this PR fixes an issue, uncomment and update the line below -->
<!-- Fixes #ISSUE_NUMBER -->

## Screenshots (if applicable)


https://github.com/user-attachments/assets/913cfb8f-acdd-4f3d-b619-c280cc11cce5


<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-6359-WIP-Collapsed-nodes-multislots-29b6d73d3650817289d5f0a8efdade84)
by [Unito](https://www.unito.io)

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Alexander Brown
2025-10-29 20:32:05 -07:00
committed by GitHub
parent 5e9a9923e4
commit e606ff34ec
17 changed files with 263 additions and 72 deletions

View File

@@ -0,0 +1,127 @@
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import type {
INodeInputSlot,
IWidgetLocator
} from '@/lib/litegraph/src/interfaces'
import type { LinkId } from '@/renderer/core/layout/types'
import {
linkedWidgetedInputs,
nonWidgetedInputs
} from '@/renderer/extensions/vueNodes/utils/nodeDataUtils'
import { describe, it } from 'vitest'
function makeFakeInputSlot(
name: string,
withWidget = false,
link: LinkId | null = null
): INodeInputSlot {
const widget: IWidgetLocator | undefined = withWidget ? { name } : undefined
return {
name,
widget,
link,
boundingRect: [0, 0, 0, 0],
type: 'FAKE'
}
}
function makeFakeNodeData(inputs: INodeInputSlot[]): VueNodeData {
const nodeData: Partial<VueNodeData> = { inputs }
return nodeData as VueNodeData
}
describe('nodeDataUtils', () => {
describe('nonWidgetedInputs', () => {
it('should handle an empty inputs list', () => {
const inputs: INodeInputSlot[] = []
const nodeData = makeFakeNodeData(inputs)
const actual = nonWidgetedInputs(nodeData)
expect(actual.length).toBe(0)
})
it('should handle a list of only widgeted inputs', () => {
const inputs: INodeInputSlot[] = [
makeFakeInputSlot('first', true),
makeFakeInputSlot('second', true)
]
const nodeData = makeFakeNodeData(inputs)
const actual = nonWidgetedInputs(nodeData)
expect(actual.length).toBe(0)
})
it('should handle a list of only slot inputs', () => {
const inputs: INodeInputSlot[] = [
makeFakeInputSlot('first'),
makeFakeInputSlot('second')
]
const nodeData = makeFakeNodeData(inputs)
const actual = nonWidgetedInputs(nodeData)
expect(actual.length).toBe(2)
})
it('should handle a list of mixed inputs', () => {
const inputs: INodeInputSlot[] = [
makeFakeInputSlot('first'),
makeFakeInputSlot('second'),
makeFakeInputSlot('third', true),
makeFakeInputSlot('fourth', true)
]
const nodeData = makeFakeNodeData(inputs)
const actual = nonWidgetedInputs(nodeData)
expect(actual.length).toBe(2)
})
})
describe('linkedWidgetedInputs', () => {
it('should return input slots that are bound to widgets and are linked: none present', () => {
const inputs: INodeInputSlot[] = [
makeFakeInputSlot('first'),
makeFakeInputSlot('second'),
makeFakeInputSlot('third', true),
makeFakeInputSlot('fourth', true)
]
const nodeData = makeFakeNodeData(inputs)
const actual = linkedWidgetedInputs(nodeData)
expect(actual.length).toBe(0)
})
it('should return input slots that are bound to widgets and are linked: one present', () => {
const inputs: INodeInputSlot[] = [
makeFakeInputSlot('first'),
makeFakeInputSlot('second'),
makeFakeInputSlot('third', true),
makeFakeInputSlot('fourth', true, 1)
]
const nodeData = makeFakeNodeData(inputs)
const actual = linkedWidgetedInputs(nodeData)
expect(actual.length).toBe(1)
})
it('should return input slots that are bound to widgets and are linked: multiple present', () => {
const inputs: INodeInputSlot[] = [
makeFakeInputSlot('first'),
makeFakeInputSlot('second'),
makeFakeInputSlot('third', true),
makeFakeInputSlot('fourth', true, 1),
makeFakeInputSlot('fifth', true, 2)
]
const nodeData = makeFakeNodeData(inputs)
const actual = linkedWidgetedInputs(nodeData)
expect(actual.length).toBe(2)
})
})
})

View File

@@ -0,0 +1,36 @@
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import type { INodeInputSlot, INodeSlot } from '@/lib/litegraph/src/interfaces'
import { isSlotObject } from '@/utils/typeGuardUtil'
function coerceINodeSlot(input: INodeInputSlot): INodeSlot {
return isSlotObject(input)
? input
: {
name: typeof input === 'string' ? input : '',
type: 'any',
boundingRect: [0, 0, 0, 0]
}
}
function inputHasWidget(input: INodeInputSlot) {
return isSlotObject(input) && 'widget' in input && input.widget
}
export function nonWidgetedInputs(
nodeData: VueNodeData | undefined
): INodeSlot[] {
if (!nodeData?.inputs) return []
return nodeData.inputs
.filter((input) => !inputHasWidget(input))
.map(coerceINodeSlot)
}
export function linkedWidgetedInputs(
nodeData: VueNodeData | undefined
): INodeSlot[] {
if (!nodeData?.inputs) return []
return nodeData.inputs
.filter((input) => inputHasWidget(input) && !!input.link)
.map(coerceINodeSlot)
}