Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions src/renderer/extensions/vueNodes/components/NodeWidgets.vue
Original file line number Diff line number Diff line change
Expand Up @@ -192,19 +192,23 @@ const processedWidgets = computed((): ProcessedWidget[] => {
// Get value from store (falls back to undefined if not registered)
const value = widgetState?.value as WidgetValue

// Build options from store state, with slot-linked override for disabled
// Build options from store state, with slot-linked override for disabled.
// Promoted widgets inside a subgraph are always linked to SubgraphInput,
// but should remain interactive — skip the disabled override for them.
const storeOptions = widgetState?.options ?? {}
const widgetOptions = slotMetadata?.linked
? { ...storeOptions, disabled: true }
: storeOptions
const isPromotedOnOwningNode =
widgetState?.promoted && String(widgetState?.nodeId) === String(nodeId)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be wrong, but IMHO,

nodeId (from nodeData.id) includes the graph prefix in subgraphs (e.g. "3:5"), while widgetState?.nodeId from the store is typically the bare ID (e.g. "5"). This means the check would always be false inside subgraphs, so promoted widgets could still end up disabled.

Since bareWidgetId on line 189 already strips the prefix, would something like this work?

const isPromotedOnOwningNode =
  widgetState?.promoted && String(widgetState?.nodeId) === bareWidgetId

That way both sides are compared as bare IDs regardless of graph depth.

const widgetOptions =
slotMetadata?.linked && !isPromotedOnOwningNode
? { ...storeOptions, disabled: true }
: storeOptions

// Derive border style from store metadata
const borderStyle =
widgetState?.promoted && String(widgetState?.nodeId) === String(nodeId)
? 'ring ring-component-node-widget-promoted'
: widget.options?.advanced
? 'ring ring-component-node-widget-advanced'
: undefined
const borderStyle = isPromotedOnOwningNode
? 'ring ring-component-node-widget-promoted'
: widget.options?.advanced
? 'ring ring-component-node-widget-advanced'
: undefined

const simplified: SimplifiedWidget = {
name: widget.name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,12 @@ function createMockWidget(
function mountComponent(
widget: SimplifiedWidget<string>,
modelValue: string,
readonly = false,
placeholder?: string
) {
return mount(WidgetTextarea, {
props: {
widget,
modelValue,
readonly,
placeholder
}
})
Expand Down Expand Up @@ -179,18 +177,39 @@ describe('WidgetTextarea Value Binding', () => {

it('uses provided placeholder when specified', () => {
const widget = createMockWidget('test')
const wrapper = mountComponent(
widget,
'test',
false,
'Custom placeholder'
)
const wrapper = mountComponent(widget, 'test', 'Custom placeholder')

const textarea = wrapper.find('textarea')
expect(textarea.attributes('placeholder')).toBe('Custom placeholder')
})
})

describe('Read-Only Behavior', () => {
it('is readonly when options.read_only is true', () => {
const widget = createMockWidget('test', { read_only: true })
const wrapper = mountComponent(widget, 'test')
expect(wrapper.find('textarea').attributes('readonly')).toBeDefined()
})

it('is readonly when options.disabled is true', () => {
const widget = createMockWidget('test', { disabled: true })
const wrapper = mountComponent(widget, 'test')
expect(wrapper.find('textarea').attributes('readonly')).toBeDefined()
})

it('is editable when neither read_only nor disabled is set', () => {
const widget = createMockWidget('test', {})
const wrapper = mountComponent(widget, 'test')
expect(wrapper.find('textarea').attributes('readonly')).toBeUndefined()
})

it('is editable when disabled is explicitly false', () => {
const widget = createMockWidget('test', { disabled: false })
const wrapper = mountComponent(widget, 'test')
expect(wrapper.find('textarea').attributes('readonly')).toBeUndefined()
})
})

describe('Edge Cases', () => {
it('handles very long text', async () => {
const widget = createMockWidget('short')
Expand Down
Loading