mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 02:32:18 +00:00
feat: display label_on/label_off for boolean widgets in vueNodes mode (#7894)
## Summary Add support for displaying custom on/off labels for boolean toggle widgets, matching the behavior in litegraph mode. ## Screenshots before - litegraph <img width="1232" height="600" alt="image" src="https://github.com/user-attachments/assets/aae91acd-4b6b-4a89-aded-c5445e352006" /> before - vueNodes <img width="869" height="584" alt="image" src="https://github.com/user-attachments/assets/a69dc71e-45f7-4941-911f-f037a2b1c5c2" /> after - vueNodes <img width="1156" height="608" alt="image" src="https://github.com/user-attachments/assets/818164a6-826b-4545-bc20-e01625f11d7d" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-7894-feat-display-label_on-label_off-for-boolean-widgets-in-vueNodes-mode-2e26d73d365081a3b938c87dd4cf23aa) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
import { mount } from '@vue/test-utils'
|
import { mount } from '@vue/test-utils'
|
||||||
import PrimeVue from 'primevue/config'
|
import PrimeVue from 'primevue/config'
|
||||||
import ToggleSwitch from 'primevue/toggleswitch'
|
import ToggleSwitch from 'primevue/toggleswitch'
|
||||||
import type { ToggleSwitchProps } from 'primevue/toggleswitch'
|
|
||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import type { IWidgetOptions } from '@/lib/litegraph/src/types/widgets'
|
||||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||||
|
|
||||||
import WidgetToggleSwitch from './WidgetToggleSwitch.vue'
|
import WidgetToggleSwitch from './WidgetToggleSwitch.vue'
|
||||||
@@ -11,9 +11,9 @@ import WidgetToggleSwitch from './WidgetToggleSwitch.vue'
|
|||||||
describe('WidgetToggleSwitch Value Binding', () => {
|
describe('WidgetToggleSwitch Value Binding', () => {
|
||||||
const createMockWidget = (
|
const createMockWidget = (
|
||||||
value: boolean = false,
|
value: boolean = false,
|
||||||
options: Partial<ToggleSwitchProps> = {},
|
options: IWidgetOptions = {},
|
||||||
callback?: (value: boolean) => void
|
callback?: (value: boolean) => void
|
||||||
): SimplifiedWidget<boolean> => ({
|
): SimplifiedWidget<boolean, IWidgetOptions> => ({
|
||||||
name: 'test_toggle',
|
name: 'test_toggle',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
value,
|
value,
|
||||||
@@ -149,4 +149,47 @@ describe('WidgetToggleSwitch Value Binding', () => {
|
|||||||
expect(emitted![3]).toContain(false)
|
expect(emitted![3]).toContain(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Label Display (label_on/label_off)', () => {
|
||||||
|
it('displays label_on when value is true', () => {
|
||||||
|
const widget = createMockWidget(true, { on: 'inside', off: 'outside' })
|
||||||
|
const wrapper = mountComponent(widget, true)
|
||||||
|
|
||||||
|
expect(wrapper.text()).toContain('inside')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('displays label_off when value is false', () => {
|
||||||
|
const widget = createMockWidget(false, { on: 'inside', off: 'outside' })
|
||||||
|
const wrapper = mountComponent(widget, false)
|
||||||
|
|
||||||
|
expect(wrapper.text()).toContain('outside')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not display label when no on/off options provided', () => {
|
||||||
|
const widget = createMockWidget(false, {})
|
||||||
|
const wrapper = mountComponent(widget, false)
|
||||||
|
|
||||||
|
expect(wrapper.find('span').exists()).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates label when value changes', async () => {
|
||||||
|
const widget = createMockWidget(false, { on: 'enabled', off: 'disabled' })
|
||||||
|
const wrapper = mountComponent(widget, false)
|
||||||
|
|
||||||
|
expect(wrapper.text()).toContain('disabled')
|
||||||
|
|
||||||
|
await wrapper.setProps({ modelValue: true })
|
||||||
|
expect(wrapper.text()).toContain('enabled')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('falls back to true/false when only partial options provided', () => {
|
||||||
|
const widgetOnOnly = createMockWidget(true, { on: 'active' })
|
||||||
|
const wrapperOn = mountComponent(widgetOnOnly, true)
|
||||||
|
expect(wrapperOn.text()).toContain('active')
|
||||||
|
|
||||||
|
const widgetOffOnly = createMockWidget(false, { off: 'inactive' })
|
||||||
|
const wrapperOff = mountComponent(widgetOffOnly, false)
|
||||||
|
expect(wrapperOff.text()).toContain('inactive')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,25 @@
|
|||||||
<template>
|
<template>
|
||||||
<WidgetLayoutField :widget>
|
<WidgetLayoutField :widget>
|
||||||
<ToggleSwitch
|
<div class="ml-auto flex w-fit items-center gap-2">
|
||||||
v-model="modelValue"
|
<span
|
||||||
v-bind="filteredProps"
|
v-if="stateLabel"
|
||||||
class="ml-auto block"
|
:class="
|
||||||
:aria-label="widget.name"
|
cn(
|
||||||
/>
|
'text-sm transition-colors',
|
||||||
|
modelValue
|
||||||
|
? 'text-node-component-slot-text'
|
||||||
|
: 'text-node-component-slot-text/50'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ stateLabel }}
|
||||||
|
</span>
|
||||||
|
<ToggleSwitch
|
||||||
|
v-model="modelValue"
|
||||||
|
v-bind="filteredProps"
|
||||||
|
:aria-label="widget.name"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</WidgetLayoutField>
|
</WidgetLayoutField>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -13,7 +27,9 @@
|
|||||||
import ToggleSwitch from 'primevue/toggleswitch'
|
import ToggleSwitch from 'primevue/toggleswitch'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import type { IWidgetOptions } from '@/lib/litegraph/src/types/widgets'
|
||||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||||
|
import { cn } from '@/utils/tailwindUtil'
|
||||||
import {
|
import {
|
||||||
STANDARD_EXCLUDED_PROPS,
|
STANDARD_EXCLUDED_PROPS,
|
||||||
filterWidgetProps
|
filterWidgetProps
|
||||||
@@ -22,7 +38,7 @@ import {
|
|||||||
import WidgetLayoutField from './layout/WidgetLayoutField.vue'
|
import WidgetLayoutField from './layout/WidgetLayoutField.vue'
|
||||||
|
|
||||||
const { widget } = defineProps<{
|
const { widget } = defineProps<{
|
||||||
widget: SimplifiedWidget<boolean>
|
widget: SimplifiedWidget<boolean, IWidgetOptions>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const modelValue = defineModel<boolean>()
|
const modelValue = defineModel<boolean>()
|
||||||
@@ -30,4 +46,10 @@ const modelValue = defineModel<boolean>()
|
|||||||
const filteredProps = computed(() =>
|
const filteredProps = computed(() =>
|
||||||
filterWidgetProps(widget.options, STANDARD_EXCLUDED_PROPS)
|
filterWidgetProps(widget.options, STANDARD_EXCLUDED_PROPS)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const stateLabel = computed(() => {
|
||||||
|
const options = widget.options
|
||||||
|
if (!options?.on && !options?.off) return null
|
||||||
|
return modelValue.value ? (options.on ?? 'true') : (options.off ?? 'false')
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user