({ id, forceStyle: true, forceClass: true }))
},
...Object.entries(ccomponents)
- .filter(([key]) => key !== 'quillcomponent')
+ .filter(([key]) => !['quillcomponent', ...Object.keys(DEPRECATED_COMPONENTS)].includes(key))
.map(([type, { name, icon, customCss }]) => ({
type: type as keyof typeof components,
name,
diff --git a/frontend/src/lib/components/apps/editor/componentsPanel/cssUtils.ts b/frontend/src/lib/components/apps/editor/componentsPanel/cssUtils.ts
index 4634959ac0..8545d0627c 100644
--- a/frontend/src/lib/components/apps/editor/componentsPanel/cssUtils.ts
+++ b/frontend/src/lib/components/apps/editor/componentsPanel/cssUtils.ts
@@ -676,8 +676,8 @@ export const customisationByComponent: Customisation[] = [
comment: 'buttons to remove a single or all selected options at once'
},
{
- selector: '.multiselect > input[autocomplete]',
- comment: 'input inside the top-level wrapper div'
+ selector: '.multiselect.dropdown',
+ comment: 'dropdown container'
},
{
selector: '.multiselect > ul.options',
@@ -686,23 +686,6 @@ export const customisationByComponent: Customisation[] = [
{
selector: '.multiselect > ul.options > li',
comment: 'dropdown list items'
- },
- {
- selector: '.multiselect > ul.options > li.selected',
- comment: 'selected options in the dropdown list'
- },
- {
- selector: '.multiselect > ul.options > li:not(.selected):hover',
- comment: 'unselected but hovered options in the dropdown list'
- },
- {
- selector: '.multiselect > ul.options > li.active',
- comment:
- 'active item, navigated to with up/down arrow keys and ready to be selected by pressing enter'
- },
- {
- selector: '.multiselect > ul.options > li.disabled',
- comment: 'options with disabled key set to true'
}
],
variables: [
diff --git a/frontend/src/lib/components/apps/editor/settingsPanel/EventHandlerItem.svelte b/frontend/src/lib/components/apps/editor/settingsPanel/EventHandlerItem.svelte
index 83b05f5fd0..37697dec9d 100644
--- a/frontend/src/lib/components/apps/editor/settingsPanel/EventHandlerItem.svelte
+++ b/frontend/src/lib/components/apps/editor/settingsPanel/EventHandlerItem.svelte
@@ -1,11 +1,16 @@
{#if condition}
-
+
{@render children?.()}
{:else}
-
+ {@render children?.()}
{/if}
diff --git a/frontend/src/lib/components/multiselect/MultiSelect.svelte b/frontend/src/lib/components/multiselect/MultiSelect.svelte
deleted file mode 100644
index 1e4f00c66d..0000000000
--- a/frontend/src/lib/components/multiselect/MultiSelect.svelte
+++ /dev/null
@@ -1,828 +0,0 @@
-
-
-
-
-
-
-
-
= Number(required) ? JSON.stringify(selected) : null}
- tabindex="-1"
- aria-hidden="true"
- aria-label="ignore this, used only to prevent form submission if select is required but empty"
- class="form-control"
- bind:this={form_input}
- on:invalid={() => {
- invalid = true
- let msg
- if (maxSelect && maxSelect > 1 && Number(required) > 1) {
- msg = `Please select between ${required} and ${maxSelect} options`
- } else if (Number(required) > 1) {
- msg = `Please select at least ${required} options`
- } else {
- msg = `Please select an option`
- }
- form_input?.setCustomValidity(msg)
- }}
- />
-
-
-
-
- {#if loading}
-
spinner
- {/if}
- {#if disabled}
-
disable
- {:else if selected.length > 0}
- {#if maxSelect !== 1 && selected.length > 1 && !disableRemoveAll}
-
- {/if}
- {/if}
-
- {#if allowUserOptions || (searchText && noMatchingOptionsMsg) || options?.length > 0}
-
-
-
- {@const option = matchingOptions[index]}
- {@const {
- label,
- disabled = null,
- title = null,
- selectedTitle = null,
- disabledTitle = defaultDisabledTitle
- } = option instanceof Object ? option : { label: option }}
-
-
-
{
- if (!disabled) add(option, event)
- }}
- title={disabled ? disabledTitle : (is_selected(label) && selectedTitle) || title}
- class={twMerge('hover:bg-blue-100 hover:dark:bg-gray-900 cursor-pointer !px-2 py-1')}
- on:mouseover={() => {
- if (!disabled) activeIndex = index
- }}
- on:focus={() => {
- if (!disabled) activeIndex = index
- }}
- on:mouseout={() => (activeIndex = null)}
- on:blur={() => (activeIndex = null)}
- role="option"
- aria-selected="false"
- style={get_style(option, `option`)}
- >
-
-
- {#if parseLabelsAsHtml}
- {@html get_label(option)}
- {:else}
- {get_label(option)}
- {/if}
-
-
-
-
-
-
- {#if searchText}
- {@const text_input_is_duplicate = selected.map(get_label).includes(searchText)}
- {@const is_dupe = !duplicates && text_input_is_duplicate && `dupe`}
- {@const can_create = Boolean(allowUserOptions && createOptionMsg) && `create`}
- {@const no_match =
- Boolean(matchingOptions?.length == 0 && noMatchingOptionsMsg) && `no-match`}
- {@const msgType = is_dupe || can_create || no_match}
- {#if msgType}
- {@const msg = {
- dupe: duplicateOptionMsg,
- create: createOptionMsg,
- 'no-match': noMatchingOptionsMsg
- }[msgType]}
-
-
{
- if (allowUserOptions) add(searchText, event)
- }}
- title={createOptionMsg}
- class:active={option_msg_is_active}
- on:mouseover={() => (option_msg_is_active = true)}
- on:focus={() => (option_msg_is_active = true)}
- on:mouseout={() => (option_msg_is_active = false)}
- on:blur={() => (option_msg_is_active = false)}
- role="option"
- aria-selected="false"
- class="user-msg p-1"
- style:cursor={{
- dupe: `not-allowed`,
- create: `pointer`,
- 'no-match': `default`
- }[msgType]}
- >
-
- {msg}
-
-
- {/if}
- {/if}
-
- {/if}
-
-
-
diff --git a/frontend/src/lib/components/multiselect/MultiSelectWrapper.svelte b/frontend/src/lib/components/multiselect/MultiSelectWrapper.svelte
deleted file mode 100644
index 685c9b0801..0000000000
--- a/frontend/src/lib/components/multiselect/MultiSelectWrapper.svelte
+++ /dev/null
@@ -1,130 +0,0 @@
-
-
-
-
-
- {#if !value || Array.isArray(value)}
-
-
[...(value ?? [])],
- (newVal) => {
- if (!deepEqual(value, newVal)) {
- value = newVal
- }
- }
- }
- {placeholder}
- options={items}
- on:close={() => {
- open = false
- }}
- on:open={() => {
- open = true
- }}
- let:option
- disableRemoveAll
- >
- {
- e.stopPropagation()
- }}
- onpointerdown={(e) => {
- e.stopPropagation()
- let newe = new MouseEvent('mouseup')
- e.target?.['parentElement']?.dispatchEvent(newe)
- }}
- aria-selected={value?.includes(option)}
- >
- {option}
-
-
-
-
-
-
-
-
{
- e.stopPropagation()
- }}
- role="listbox"
- tabindex="0"
- >
-
-
- {:else}
- Value {value} is not an array
- {/if}
-
diff --git a/frontend/src/lib/components/multiselect/types.ts b/frontend/src/lib/components/multiselect/types.ts
deleted file mode 100644
index d3dc4d7cac..0000000000
--- a/frontend/src/lib/components/multiselect/types.ts
+++ /dev/null
@@ -1,47 +0,0 @@
-export type Option = string | number | ObjectOption
-
-// single CSS string or an object with keys 'option' and 'selected', each a string,
-// which only apply to the dropdown list and list of selected options, respectively
-export type OptionStyle = string | { option: string; selected: string }
-
-export type ObjectOption = {
- label: string | number // user-displayed text
- value?: unknown // associated value, can be anything incl. objects (defaults to label if undefined)
- title?: string // on-hover tooltip
- disabled?: boolean // make this option unselectable
- preselected?: boolean // make this option selected on page load (before any user interaction)
- disabledTitle?: string // override the default disabledTitle = 'This option is disabled'
- selectedTitle?: string // tooltip to display when this option is selected and hovered
- style?: OptionStyle
- [key: string]: unknown // allow any other keys users might want
-}
-
-export type DispatchEvents
= {
- add: { option: T }
- create: { option: T }
- remove: { option: T }
- removeAll: { options: T[] }
- change: {
- option?: T
- options?: T[]
- type: 'add' | 'remove' | 'removeAll'
- }
- open: { event: Event }
- close: { event: Event }
-}
-
-export type MultiSelectEvents = {
- [key in keyof DispatchEvents]: CustomEvent
-} & {
- blur: FocusEvent
- click: MouseEvent
- focus: FocusEvent
- keydown: KeyboardEvent
- keyup: KeyboardEvent
- mouseenter: MouseEvent
- mouseleave: MouseEvent
- touchcancel: TouchEvent
- touchend: TouchEvent
- touchmove: TouchEvent
- touchstart: TouchEvent
-}
diff --git a/frontend/src/lib/components/multiselect/utils.ts b/frontend/src/lib/components/multiselect/utils.ts
deleted file mode 100644
index a9bcfcaa69..0000000000
--- a/frontend/src/lib/components/multiselect/utils.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-import type { Option, OptionStyle } from './types'
-
-// get the label key from an option object or the option itself if it's a string or number
-export const get_label = (opt: Option) => {
- if (opt instanceof Object) {
- if (opt.label === undefined) {
- console.error(`MultiSelect option ${JSON.stringify(opt)} is an object but has no label key`)
- }
- return opt.label
- }
- return `${opt}`
-}
-
-// this function is used extract CSS strings from a {selected, option} style object to be used in the style attribute of the option
-// if the style is a string, it will be returned as is
-export function get_style(
- option: { style?: OptionStyle; [key: string]: unknown } | string | number,
- key: 'selected' | 'option' | null = null
-) {
- let css_str = ``
- if (![`selected`, `option`, null].includes(key)) {
- console.error(`MultiSelect: Invalid key=${key} for get_style`)
- }
- if (typeof option == `object` && option.style) {
- if (typeof option.style == `string`) {
- css_str = option.style
- }
- if (typeof option.style == `object`) {
- if (key && key in option.style) return option.style[key] ?? ``
- else {
- console.error(`Invalid style object for option=${JSON.stringify(option)}`)
- }
- }
- }
- // ensure css_str ends with a semicolon
- if (css_str.trim() && !css_str.trim().endsWith(`;`)) css_str += `;`
- return css_str
-}
-
-// Firefox lacks support for scrollIntoViewIfNeeded (https://caniuse.com/scrollintoviewifneeded).
-// See https://github.com/janosh/svelte-multiselect/issues/87
-// Polyfill copied from
-// https://github.com/nuxodin/lazyfill/blob/a8e63/polyfills/Element/prototype/scrollIntoViewIfNeeded.js
-// exported for testing
-export function scroll_into_view_if_needed_polyfill(elem: Element, centerIfNeeded: boolean = true) {
- const observer = new IntersectionObserver(function ([entry]) {
- const ratio = entry.intersectionRatio
- if (ratio < 1) {
- const place = ratio <= 0 && centerIfNeeded ? `center` : `nearest`
- elem.scrollIntoView({
- block: place,
- inline: place
- })
- }
- observer.disconnect()
- }, {
- root: null, // or specify a scrolling parent if needed
- rootMargin: '0px 1000px', // Essentially making horizontal checks irrelevant
- threshold: 0.1 // Adjust threshold to control when observer should trigger
-})
- observer.observe(elem)
-
- return observer // return for testing
-}
diff --git a/frontend/src/lib/components/runs/RunsFilter.svelte b/frontend/src/lib/components/runs/RunsFilter.svelte
index 6fd0b0ed03..77ab068bfe 100644
--- a/frontend/src/lib/components/runs/RunsFilter.svelte
+++ b/frontend/src/lib/components/runs/RunsFilter.svelte
@@ -12,7 +12,8 @@
import { createEventDispatcher, untrack } from 'svelte'
import ToggleButtonMore from '../common/toggleButton-v2/ToggleButtonMore.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
- import Select from '../Select.svelte'
+ import Select from '../select/Select.svelte'
+ import { safeSelectItems } from '../select/utils.svelte'
interface Props {
// Filters
@@ -174,7 +175,7 @@
User
@@ -319,7 +321,7 @@
{#each workspaces as workspace}
@@ -331,7 +333,7 @@
Label (optional)
-
+
{#if !mcpCreationMode}
@@ -339,7 +341,7 @@
Expires In (optional)
-
+
diff --git a/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorConfigSection.svelte b/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorConfigSection.svelte
index 83aa8633ce..60751a4595 100644
--- a/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorConfigSection.svelte
+++ b/frontend/src/lib/components/triggers/gcp/GcpTriggerEditorConfigSection.svelte
@@ -23,7 +23,8 @@
import { RefreshCw } from 'lucide-svelte'
import Alert from '$lib/components/common/alert/Alert.svelte'
import TestingBadge from '../testingBadge.svelte'
- import Select from '$lib/components/Select.svelte'
+ import Select from '$lib/components/select/Select.svelte'
+ import { safeSelectItems } from '$lib/components/select/utils.svelte'
let topic_items: string[] = $state([])
let subscription_items: string[] = $state([])
@@ -176,7 +177,7 @@
loadAllSubscriptionFromGooglePubSubTopic()
}
}
- items={topic_items.map((value) => ({ value }))}
+ items={safeSelectItems(topic_items)}
placeholder="Choose a topic"
/>