fix Vue node widgets should be in disabled state if their slots are connected with a link (#5834)

## Summary

Fixes https://github.com/Comfy-Org/ComfyUI_frontend/issues/5692 by
making widget link connection status trigger on change so Vue widgets
with connected links could properly switch to the `disabled` state when
they are implicitly converted to inputs.

## Changes

- **What**: Added `node:slot-links:changed` event tracking and reactive
slot data synchronization for Vue widgets

```mermaid
graph TD
    A[Widget Link Change] --> B[NodeInputSlot.link setter]
    B --> C{Is Widget Input?}
    C -->|Yes| D[Trigger slot-links:changed]
    C -->|No| E[End]
    D --> F[Graph Event Handler]
    F --> G[syncNodeSlotData]
    G --> H[Update Vue Reactive Data]
    H --> I[Widget Re-render]
    
    style A fill:#f9f9f9,stroke:#333,color:#000
    style I fill:#f9f9f9,stroke:#333,color:#000
```

## Review Focus

Widget reactivity performance with frequent link changes and event
handler memory management in graph operations.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-5834-fix-Vue-node-widgets-should-be-in-disabled-state-if-their-slots-are-connected-with-a-link-27c6d73d365081f6a6c3c1ddc3905c5e)
by [Unito](https://www.unito.io)
This commit is contained in:
Christian Byrne
2025-10-09 10:30:12 -07:00
committed by GitHub
parent b222cae56e
commit 06b0eecfe4
12 changed files with 388 additions and 82 deletions

View File

@@ -33,7 +33,7 @@
boundingRect: [0, 0, 0, 0]
}"
:node-id="nodeData?.id != null ? String(nodeData.id) : ''"
:index="getWidgetInputIndex(widget)"
:index="widget.slotMetadata?.index ?? 0"
:dot-only="true"
/>
</div>
@@ -56,12 +56,12 @@ import { computed, onErrorCaptured, ref } from 'vue'
import type {
SafeWidgetData,
VueNodeData
VueNodeData,
WidgetSlotMetadata
} from '@/composables/graph/useGraphNodeManager'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
// Import widget components directly
import WidgetInputText from '@/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue'
import {
getComponent,
@@ -110,6 +110,7 @@ interface ProcessedWidget {
value: WidgetValue
updateHandler: (value: unknown) => void
tooltipConfig: any
slotMetadata?: WidgetSlotMetadata
}
const processedWidgets = computed((): ProcessedWidget[] => {
@@ -126,12 +127,24 @@ const processedWidgets = computed((): ProcessedWidget[] => {
const vueComponent = getComponent(widget.type) || WidgetInputText
const slotMetadata = widget.slotMetadata
let widgetOptions = widget.options
// Core feature: Disable Vue widgets when their input slots are connected
// This prevents conflicting input sources - when a slot is linked to another
// node's output, the widget should be read-only to avoid data conflicts
if (slotMetadata?.linked) {
widgetOptions = widget.options
? { ...widget.options, disabled: true }
: { disabled: true }
}
const simplified: SimplifiedWidget = {
name: widget.name,
type: widget.type,
value: widget.value,
label: widget.label,
options: widget.options,
options: widgetOptions,
callback: widget.callback,
spec: widget.spec
}
@@ -152,25 +165,11 @@ const processedWidgets = computed((): ProcessedWidget[] => {
simplified,
value: widget.value,
updateHandler,
tooltipConfig
tooltipConfig,
slotMetadata
})
}
return result
})
// TODO: Refactor to avoid O(n) lookup - consider storing input index on widget creation
// or restructuring data model to unify widgets and inputs
// Map a widget to its corresponding input slot index
const getWidgetInputIndex = (widget: ProcessedWidget): number => {
const inputs = nodeData?.inputs
if (!inputs) return 0
const idx = inputs.findIndex((input: any) => {
if (!input || typeof input !== 'object') return false
if (!('name' in input && 'type' in input)) return false
return 'widget' in input && input.widget?.name === widget.name
})
return idx >= 0 ? idx : 0
}
</script>

View File

@@ -90,6 +90,7 @@ const buttonTooltip = computed(() => {
:step="stepValue"
:use-grouping="useGrouping"
:class="cn(WidgetInputBaseClass, 'w-full text-xs')"
:aria-label="widget.name"
:pt="{
incrementButton:
'!rounded-r-lg bg-transparent border-none hover:bg-zinc-500/30 active:bg-zinc-500/40',