mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-14 01:20:03 +00:00
Add support for values factory function in widget select combo (#8775)
## Summary
Adds support for values factory functions, e.g.
```
this.addWidget(
"combo",
"Dynamic",
"",
(e) => { },
{
values: () => {
return getSomeValuesHere()
}
}
)
```
Specifically for fixing KJNodes get/set
## Changes
- **What**: Check if object is a function, if so, calls it.
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8775-Add-support-for-values-factory-function-in-widget-select-combo-3036d73d3650819bb4e4f9181445cb1d)
by [Unito](https://www.unito.io)
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { nextTick } from 'vue'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import SelectPlus from '@/components/primevueOverride/SelectPlus.vue'
|
||||
import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
||||
|
||||
import WidgetSelectDefault from './WidgetSelectDefault.vue'
|
||||
|
||||
describe('WidgetSelectDefault', () => {
|
||||
const createWidget = (
|
||||
values: unknown
|
||||
): SimplifiedWidget<string | undefined> => ({
|
||||
name: 'test_combo',
|
||||
type: 'combo',
|
||||
value: undefined,
|
||||
options: { values } as SimplifiedWidget['options']
|
||||
})
|
||||
|
||||
const mountComponent = (widget: SimplifiedWidget<string | undefined>) =>
|
||||
mount(WidgetSelectDefault, {
|
||||
props: { widget },
|
||||
global: {
|
||||
plugins: [PrimeVue],
|
||||
components: { SelectPlus }
|
||||
}
|
||||
})
|
||||
|
||||
describe('array-valued options', () => {
|
||||
it('resolves options from a plain array', () => {
|
||||
const widget = createWidget(['a', 'b', 'c'])
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
expect(wrapper.findComponent(SelectPlus).props('options')).toEqual([
|
||||
'a',
|
||||
'b',
|
||||
'c'
|
||||
])
|
||||
})
|
||||
|
||||
it('reactively updates when widget prop changes', async () => {
|
||||
const widget = createWidget(['x', 'y'])
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
await wrapper.setProps({ widget: createWidget(['x', 'y', 'z']) })
|
||||
|
||||
expect(wrapper.findComponent(SelectPlus).props('options')).toEqual([
|
||||
'x',
|
||||
'y',
|
||||
'z'
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('undefined/empty options', () => {
|
||||
it('returns empty array when values is undefined', () => {
|
||||
const widget = createWidget(undefined)
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
expect(wrapper.findComponent(SelectPlus).props('options')).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('function-valued options', () => {
|
||||
it('resolves options from a function', () => {
|
||||
const widget = createWidget(() => ['a', 'b', 'c'])
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
expect(wrapper.findComponent(SelectPlus).props('options')).toEqual([
|
||||
'a',
|
||||
'b',
|
||||
'c'
|
||||
])
|
||||
})
|
||||
|
||||
it('re-evaluates function on show event', async () => {
|
||||
let items = ['x', 'y']
|
||||
const widget = createWidget(() => items)
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
items = ['x', 'y', 'z']
|
||||
wrapper.findComponent(SelectPlus).vm.$emit('show')
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.findComponent(SelectPlus).props('options')).toEqual([
|
||||
'x',
|
||||
'y',
|
||||
'z'
|
||||
])
|
||||
})
|
||||
|
||||
it('re-evaluates function on filter event', async () => {
|
||||
let items = ['a']
|
||||
const widget = createWidget(() => items)
|
||||
const wrapper = mountComponent(widget)
|
||||
|
||||
items = ['a', 'b']
|
||||
wrapper.findComponent(SelectPlus).vm.$emit('filter')
|
||||
await nextTick()
|
||||
|
||||
expect(wrapper.findComponent(SelectPlus).props('options')).toEqual([
|
||||
'a',
|
||||
'b'
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -17,6 +17,8 @@
|
||||
overlay: 'w-fit min-w-full'
|
||||
}"
|
||||
data-capture-wheel="true"
|
||||
@show="refreshOptions"
|
||||
@filter="refreshOptions"
|
||||
>
|
||||
<template #dropdownicon>
|
||||
<i
|
||||
@@ -31,7 +33,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import SelectPlus from '@/components/primevueOverride/SelectPlus.vue'
|
||||
import { useTransformCompatOverlayProps } from '@/composables/useTransformCompatOverlayProps'
|
||||
@@ -51,25 +53,30 @@ interface Props {
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
function resolveValues(values: unknown): string[] {
|
||||
if (typeof values === 'function') return values()
|
||||
if (Array.isArray(values)) return values
|
||||
return []
|
||||
}
|
||||
|
||||
const modelValue = defineModel<string | undefined>({
|
||||
default(props: Props) {
|
||||
const values = props.widget.options?.values
|
||||
return (Array.isArray(values) ? values[0] : undefined) ?? ''
|
||||
const resolved = typeof values === 'function' ? values() : values
|
||||
return Array.isArray(resolved) ? (resolved[0] ?? '') : ''
|
||||
}
|
||||
})
|
||||
|
||||
// Transform compatibility props for overlay positioning
|
||||
const transformCompatProps = useTransformCompatOverlayProps()
|
||||
|
||||
// Extract select options from widget options
|
||||
const refreshTrigger = ref(0)
|
||||
function refreshOptions() {
|
||||
refreshTrigger.value++
|
||||
}
|
||||
const selectOptions = computed(() => {
|
||||
const options = props.widget.options
|
||||
|
||||
if (options?.values && Array.isArray(options.values)) {
|
||||
return options.values
|
||||
}
|
||||
|
||||
return []
|
||||
void refreshTrigger.value
|
||||
return resolveValues(props.widget.options?.values)
|
||||
})
|
||||
const invalid = computed(
|
||||
() => !!modelValue.value && !selectOptions.value.includes(modelValue.value)
|
||||
|
||||
Reference in New Issue
Block a user