diff --git a/frontend/src/lib/components/apps/components/inputs/AppMultiSelectV2.svelte b/frontend/src/lib/components/apps/components/inputs/AppMultiSelectV2.svelte
index ec0c1d00fe..ca7bcb0dc5 100644
--- a/frontend/src/lib/components/apps/components/inputs/AppMultiSelectV2.svelte
+++ b/frontend/src/lib/components/apps/components/inputs/AppMultiSelectV2.svelte
@@ -44,24 +44,28 @@
)
const outputs = initOutput($worldStore, id, {
- result: [] as Options
+ result: isObjectOptionArray(items)
+ ? ([] as ObjectOption[])
+ : isStringArray(items)
+ ? ([] as string[])
+ : ([] as unknown[])
})
- let value: Options | undefined = isLabeledArray(outputs?.result.peak())
+ let value: Options | undefined = isObjectOptionArray(outputs?.result.peak())
? ([...new Set(outputs?.result.peak())] as ObjectOption[])
: ([...new Set(outputs?.result.peak())] as string[])
$componentControl[id] = {
setValue(nvalue: Options) {
- if (isLabeledArray(nvalue)) {
+ if (isObjectOptionArray(nvalue)) {
value = [...new Set(nvalue)] as ObjectOption[]
outputs?.result.set([...(value ?? [])])
}
}
}
- function isObjectOption(item: Options[number]): item is ObjectOption {
- if (typeof item === 'string') {
+ function isObjectOption(item: any): item is ObjectOption {
+ if (!item || item !== Object(item)) {
return false
}
if (item.label === undefined || item.label === null) {
@@ -73,11 +77,17 @@
return true
}
- function isLabeledArray(arr: Options): arr is ObjectOption[] {
+ function isObjectOptionArray(arr: any | undefined): arr is ObjectOption[] {
+ if (!arr || !Array.isArray(arr)) {
+ return false
+ }
return arr.every((item) => isObjectOption(item))
}
- function isStringArray(arr: Options): arr is string[] {
+ function isStringArray(arr: any | undefined): arr is string[] {
+ if (!arr || !Array.isArray(arr)) {
+ return false
+ }
return arr.every((item) => typeof item === 'string')
}
@@ -121,7 +131,7 @@
}
function handleItems() {
- if (isLabeledArray(resolvedConfig.items)) {
+ if (isObjectOptionArray(resolvedConfig.items)) {
handleLabeledItems()
} else if (isStringArray(resolvedConfig.items)) {
handleStringItems()
@@ -138,9 +148,11 @@
outputs?.result.set([])
return
}
- let rawNvalue = new Set(resolvedConfig.defaultItems?.filter((v) => typeof v === 'string'))
- if (isLabeledArray(items)) {
- nvalue = items?.filter((item) => rawNvalue.has(item.value)) as ObjectOption[]
+ let rawNvalue = new Set(
+ resolvedConfig.defaultItems?.filter((v) => typeof v === 'string' || typeof v === 'number')
+ )
+ if (isObjectOptionArray(items)) {
+ nvalue = items?.filter((item) => rawNvalue.has(item.label)) as ObjectOption[]
value = [...new Set(nvalue)]
outputs?.result.set([...(value ?? [])])
} else if (isStringArray(items)) {
@@ -184,6 +196,23 @@
}
let w = 0
let open: boolean = false
+
+ const multiSelectProps = {
+ outerDivClass: resolvedConfig.allowOverflow ? '' : 'h-full',
+ ulSelectedClass: resolvedConfig.allowOverflow ? '' : 'overflow-auto max-h-full',
+ '--sms-border': 'none',
+ '--sms-min-height': '32px',
+ '--sms-focus-border': 'none',
+ placeholder: resolvedConfig.placeholder,
+ allowUserOptions: resolvedConfig.create,
+ onOpen: () => {
+ $selectedComponent = [id]
+ open = true
+ },
+ onClose: () => {
+ open = false
+ }
+ }
{#each Object.keys(components['multiselectcomponent'].initialData.configuration) as key (key)}
@@ -222,46 +251,67 @@
bind:clientWidth={w}
>
{#if !value || Array.isArray(value)}
- {
- if (event?.detail?.type === 'removeAll') {
- outputs?.result.set([])
- } else {
- outputs?.result.set([...(value ?? [])])
- }
- }}
- on:open={() => {
- $selectedComponent = [id]
- open = true
- }}
- on:close={() => {
- open = false
- }}
- let:option
- >
-
-
- {
- let newe = new MouseEvent('mouseup')
- e.target?.['parentElement']?.dispatchEvent(newe)
+ {#if isObjectOptionArray(value) && isObjectOptionArray(items)}
+ {
+ if (event?.detail?.type === 'removeAll') {
+ outputs?.result.set([])
+ } else {
+ outputs?.result.set([...(value ?? [])])
+ }
}}
+ {...multiSelectProps}
+ bind:outerDiv
+ on:open={multiSelectProps.onOpen}
+ on:close={multiSelectProps.onClose}
+ let:option
>
- {option.label}
-
-
+
+
+
{
+ let newe = new MouseEvent('mouseup')
+ e.target?.['parentElement']?.dispatchEvent(newe)
+ }}
+ >
+ {option.label}
+
+
+ {:else if isStringArray(value) && isStringArray(items)}
+ {
+ if (event?.detail?.type === 'removeAll') {
+ outputs?.result.set([])
+ } else {
+ outputs?.result.set([...(value ?? [])])
+ }
+ }}
+ {...multiSelectProps}
+ bind:outerDiv
+ on:open={multiSelectProps.onOpen}
+ on:close={multiSelectProps.onClose}
+ let:option
+ >
+
+
+ {
+ let newe = new MouseEvent('mouseup')
+ e.target?.['parentElement']?.dispatchEvent(newe)
+ }}
+ >
+ {option.label}
+
+
+ {/if}
diff --git a/frontend/src/lib/components/apps/editor/component/components.ts b/frontend/src/lib/components/apps/editor/component/components.ts
index ca1b45d7d5..6ce8fb9f05 100644
--- a/frontend/src/lib/components/apps/editor/component/components.ts
+++ b/frontend/src/lib/components/apps/editor/component/components.ts
@@ -2199,11 +2199,8 @@ This is a paragraph.
items: {
type: 'static',
fieldType: 'array',
- subFieldType: 'labeledselect',
- value: [
- { value: 'foo', label: 'Foo' },
- { value: 'bar', label: 'Bar' }
- ],
+ subFieldType: 'simplestringselect',
+ value: ['Foo', 'Bar'],
hasLabeledMode: true
} as StaticAppInput,
defaultItems: {
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/ArrayStaticInputEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/ArrayStaticInputEditor.svelte
index bea3cf4deb..9fbc63b6f0 100644
--- a/frontend/src/lib/components/apps/editor/settingsPanel/ArrayStaticInputEditor.svelte
+++ b/frontend/src/lib/components/apps/editor/settingsPanel/ArrayStaticInputEditor.svelte
@@ -2,15 +2,16 @@
import { Button } from '$lib/components/common'
import { GripVertical, Loader2, Plus, X } from 'lucide-svelte'
import { createEventDispatcher } from 'svelte'
- import type { InputType, LabeledOption, StaticInput, StaticOptions } from '../../inputType'
+ import type { InputType, StaticInput, StaticOptions } from '../../inputType'
import SubTypeEditor from './SubTypeEditor.svelte'
import { dragHandle, dragHandleZone } from '@windmill-labs/svelte-dnd-action'
import { generateRandomString, pluralize } from '$lib/utils'
import Toggle from '$lib/components/Toggle.svelte'
import QuickAddColumn from './QuickAddColumn.svelte'
import RefreshDatabaseStudioTable from './RefreshDatabaseStudioTable.svelte'
+ import type { ObjectOption } from '$lib/components/multiselect/types'
- export let componentInput: StaticInput
& { loading?: boolean; isLabeled?: boolean }
+ export let componentInput: StaticInput & { loading?: boolean }
export let subFieldType: InputType | undefined = undefined
export let selectOptions: StaticOptions['selectOptions'] | undefined = undefined
export let id: string | undefined
@@ -19,17 +20,6 @@
const dispatch = createEventDispatcher()
const flipDurationMs = 200
- console.log(componentInput.value)
-
- // transform string[] to LabeledOption[], or the opposite way
- function handleLabeledChange() {
- if (labeled) {
- componentInput.value = componentInput.value?.map((option: LabeledOption) => option?.label)
- } else if (!labeled) {
- componentInput.value = componentInput.value?.map((label) => ({ label, value: label }))
- }
- }
-
function addElementByType() {
if (!Array.isArray(componentInput.value)) {
componentInput.value = []
@@ -224,16 +214,62 @@
$: subFieldType === 'db-explorer' && clearTableOnComponentReset(componentInput?.value)
- $: items != undefined && handleItemsChange()
+ let raw: boolean = false
+ let labeled: boolean = subFieldType === 'labeledselect'
function handleItemsChange() {
- componentInput.value = items.map((item) => item.value).filter((item) => item != undefined)
+ const newComponentInput = { ...componentInput }
+ newComponentInput.value = items.map((item) => item.value).filter((item) => item != undefined)
+
+ const newSubFieldType = labeled ? 'labeledselect' : 'simplestringselect'
+
+ dispatch('componentInputChange', { newComponentInput, newSubFieldType })
}
- let raw: boolean = false
- let labeled: boolean = false
+ function isObjectOption(item: any): item is ObjectOption {
+ if (!item || item !== Object(item)) {
+ return false
+ }
+ if (item.label === undefined || item.label === null) {
+ return false
+ }
+ if (typeof item.label !== 'string' && typeof item.label !== 'number') {
+ return false
+ }
+ return true
+ }
- $: labeled
+ function isObjectOptionArray(arr: any | undefined): arr is ObjectOption[] {
+ if (!arr || !Array.isArray(arr)) {
+ return false
+ }
+ return arr.every((item) => isObjectOption(item))
+ }
+
+ function isStringArray(arr: any | undefined): arr is string[] {
+ if (!arr || !Array.isArray(arr)) {
+ return false
+ }
+ return arr.every((item) => typeof item === 'string')
+ }
+
+ // transform string[] to ObjectOption[], or the opposite way
+ function handleLabeledChange() {
+ if (labeled && isStringArray(items.map((item) => item.value))) {
+ for (let i = 0; i < items.length; i++) {
+ const itemValue = items[i].value
+ items[i].value = { label: itemValue, value: itemValue }
+ }
+ } else if (!labeled && isObjectOptionArray(items.map((item) => item.value))) {
+ for (let i = 0; i < items.length; i++) {
+ const itemValue = items[i].value
+ items[i].value = itemValue.label
+ }
+ }
+ }
+
+ $: labeled !== undefined && handleLabeledChange()
+ $: items !== undefined && handleItemsChange()
// let mounted = false
@@ -257,16 +293,22 @@
+
{#if Array.isArray(items) && componentInput.value}
-
{pluralize(items.length, 'item')}
-
{#if subFieldType === 'ag-grid' || subFieldType === 'table-column'}
deleteElementByType(index)}
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/SubTypeEditor.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/SubTypeEditor.svelte
index e1171c56bd..406059ea40 100644
--- a/frontend/src/lib/components/apps/editor/settingsPanel/SubTypeEditor.svelte
+++ b/frontend/src/lib/components/apps/editor/settingsPanel/SubTypeEditor.svelte
@@ -16,6 +16,10 @@
$: fakeComponentInput && (value = fakeComponentInput.value)
+
('AppViewerContext')
+ // Workaround for MultiSelect: changing fieldType needs parent rehydration
+ function handleComponentInputChange(event: {
+ newComponentInput: StaticInput
+ newSubFieldType: InputType
+ }) {
+ if (!event) {
+ console.error('fired a componentInputChange with no data?')
+ return
+ }
+ const { newComponentInput, newSubFieldType } = event
+
+ componentInput = { ...componentInput, ...newComponentInput }
+ subFieldType = newSubFieldType
+ }
+
$: componentInput && onchange?.()
@@ -168,6 +183,7 @@
{subFieldType}
bind:componentInput
on:deleteArrayItem
+ on:componentInputChange={(e) => handleComponentInputChange(e.detail)}
{hasLabeledMode}
/>
{:else if fieldType === 'schema'}
diff --git a/frontend/src/lib/components/apps/inputType.ts b/frontend/src/lib/components/apps/inputType.ts
index 5e913a0c1d..af9f169318 100644
--- a/frontend/src/lib/components/apps/inputType.ts
+++ b/frontend/src/lib/components/apps/inputType.ts
@@ -209,7 +209,7 @@ export type AppInput =
| (AppInputSpec<'array', string[], 'select'> & StaticOptions)
| AppInputSpec<'array', object[], 'labeledresource'>
| AppInputSpec<'array', object[], 'labeledselect'>
- | AppInputSpec<'array', object[], 'simplestringselect'>
+ | AppInputSpec<'array', string[], 'simplestringselect'>
| AppInputSpec<'labeledselect', object>
| AppInputSpec<'labeledresource', object>
| AppInputSpec<'array', object[], 'tab-select'>