Comfy-Org / Comfy-Org/ComfyUI_frontend
No shared 'connected' state exposed on widgets converted to input (inconsistent DOM per widget shape)
- Dominant language
- TypeScript
- Stars
- 2k
- Forks
- 699
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 490
Description
### What problem are you experiencing?
Summary
When a widget is "converted to input" and wired to another node, the Nodes 2.0 (Vue) frontend gives no dedicated, shape-independent signal that the widget is now driven by a link. The only observable state change is a bare native disabled HTML attribute set directly on whatever control happens to render that widget — and that control's markup differs completely between widget types, with at least one confirmed false-positive trap. This makes it effectively impossible for a custom-node extension (or a user-authored stylesheet) to reliably detect and style "this widget is connected" without reverse-engineering per-widget-shape DOM internals.
Environment
ComfyUI Desktop 1.49.6
comfyui_frontend_package 1.45.21
Comfy.VueNodes.Enabled: true (Nodes 2.0 / Vue rendering)
Steps to reproduce
Add a CLIPTextEncode node (or any node with a multiline STRING widget) and a KSampler node.
On CLIPTextEncode, right-click the text widget → Convert to input, then wire a Primitive (string) node into it.
On KSampler, do the same for seed (numeric) and sampler_name (combo).
Inspect the DOM of each connected widget's row ([data-testid="node-widget"]).
Observed
text (multiline / customtext): rendered by WidgetTextarea.vue. A bare goes disabled; there is no wrapping "value slot" element shared with other widget types, and the widget's own <label> is a separate, absolutely-positioned sibling (position: absolute; top: 1.5; left: 3, floating inside the textarea's own box, with the textarea getting a compensating padding-top) rather than the side-by-side layout used by line/combo widgets. A selector or lookup written for one widget shape does not transfer to this one.
seed (number / string-line): the <input> goes disabled, wrapped in div.relative.min-w-0.flex-1.
sampler_name (combo): button[data-testid="widget-select-default-trigger"] goes disabled, also wrapped in div.relative.min-w-0.flex-1.
None of the three carries a shared class, attribute, or data-testid that specifically means "this widget is fed by a link" — plain disabled is reused wholesale for other, unrelated reasons already:
A numeric widget's own increment/decrement stepper button (data-testid="increment" / "decrement") goes natively disabled whenever the value sits at the widget's configured min/max — completely unrelated to any connection. Confirmed on KSampler.denoise at its default value of 1.0 (its max). A selector like :has(button:disabled) produces a false positive here.
widget.options.read_only (used e.g. by read-only preview fields) is surfaced as readonly, not disabled — a second, differently-named state doing something conceptually similar ("don't let the user type here right now") for a completely different reason.
Why this matters
Any custom node or extension that wants to give the user real visual feedback when a widget has been converted to an input and wired — e.g. dim/hide the now-redundant inline editor, shrink the node's reserved height, or simply confirm "this value now comes from upstream" — currently has to:
Know, per widget type, which specific sub-element carries the native disabled attribute (an internal implementation detail with three different answers today, none documented).
Explicitly exclude known false-positive cases (the stepper-button-at-range-limit case above) with per-shape logic.
For multiline widgets specifically, resolve the widget name to its live DOM node via label[for] text-matching, because widget.element is not reliably the mounted element and there is no shared wrapper to query from.
This is fragile against any future refactor of WidgetTextarea.vue or the line-input/combo components (class names, wrapper structure), for something the frontend already knows the answer to internally — it's the code that sets disabled in the first place.
Suggested fix
Expose the "widget is converted-to-input and currently linked" state as one shape-independent, stable signal — e.g. a data-widget-connected="true" attribute or a .widget-connected class — applied at whatever shared level already computes it, on the [data-testid="node-widget"] row itself rather than only on the innermost control. That would let extensions (and any future core feature) hook a single selector regardless of widget shape, instead of depending on the native disabled attribute, which is already overloaded for an unrelated case (stepper range limits) and shaped differently again for readonly-driven states.
Happy to share the small reference implementation (a CSS stylesheet + a framework-independent JS extension) built as a workaround for a custom node pack, if useful as a starting point.
### When does this problem occur?
When designing custom node ui.
This could also fix a general issue on your side related to display state of connected input nodes.
### How often do you encounter this problem?
Multiple times per day
### How much does this problem affect your workflow?
Blocks me from completing tasks
### Current workarounds
_No response_
### Ideas for solutions (Optional)
_No response_
### Additional context
_No response_
Contributor guide
Research direction
Start by tracing how connected state is determined for converted-to-input widgets and how it reaches the [data-testid="node-widget"] row; compare the widget render paths, including WidgetTextarea.vue, line inputs, and combo controls. Done means the row exposes one stable connected-state signal across these shapes without relying on the controls' overloaded disabled state; check or add coverage for the different widget shapes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 70/100