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:
pythongosssss
2026-03-13 16:47:27 +00:00
committed by GitHub
parent 9652871aaf
commit 1054503b4e
2 changed files with 125 additions and 10 deletions

View File

@@ -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'
])
})
})
})

View File

@@ -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)