diff --git a/src/uix/langs/components/combobox.ts b/src/uix/langs/components/combobox.ts index 3ba9a319f..8d259e7e0 100644 --- a/src/uix/langs/components/combobox.ts +++ b/src/uix/langs/components/combobox.ts @@ -15,5 +15,9 @@ export const comboboxLangs = { toggle: { es: 'Alternar', en: 'Toggle' + }, + clear: { + es: 'Borrar valor', + en: 'Clear value' } } satisfies LangNode; diff --git a/src/uix/soma/components/combobox/combobox-provider.svelte.ts b/src/uix/soma/components/combobox/combobox-provider.svelte.ts index 1db8255c5..828f9fb35 100644 --- a/src/uix/soma/components/combobox/combobox-provider.svelte.ts +++ b/src/uix/soma/components/combobox/combobox-provider.svelte.ts @@ -1,3 +1,4 @@ +import { untrack } from 'svelte'; import { SvelteMap } from 'svelte/reactivity'; import { createAttrs } from '$uix/morfo'; import { context, type ProviderOpts, type WithRefOpts } from '../../provider'; @@ -134,6 +135,38 @@ export class ComboboxProvider { ref: this.contentRef, onComplete: (open) => opts.onOpenChangeComplete.current(open) }); + + // Single-mode auto-sync: keep `inputValue` mirroring the selected + // value's label so the input reflects external `value` changes + // (initial mount with a pre-selected value, or `bind:value` updates). + // Multi-mode intentionally leaves `inputValue` alone — that input is + // the live filter, and stamping a label onto it would hide the rest + // of the items behind a single-match query. + $effect(() => { + if (this.opts.type.current !== 'single') return; + const v = this.opts.value.current; + if (v.length === 0) return; + const label = this.labelRegistry.get(v[0]); + if (label === undefined) return; + if (this.opts.inputValue.current !== label) { + this.opts.inputValue.current = label; + } + }); + + // Mode switch reset: when `type` changes between 'single' and + // 'multiple', wipe `inputValue` so leftover text from the previous + // mode doesn't accidentally filter the list when the user expects a + // fresh start. + let lastType = untrack(() => this.opts.type.current); + $effect(() => { + const type = this.opts.type.current; + if (type !== lastType) { + lastType = type; + untrack(() => { + this.opts.inputValue.current = ''; + }); + } + }); } isSelected(value: string): boolean { @@ -186,6 +219,17 @@ export class ComboboxProvider { } this.opts.value.current = next; + + // Sync inputValue in SINGLE mode only — show the selected label so the + // input reflects the current value. In MULTI mode the user is likely + // still filtering for more picks (cmdk pattern: filter → pick → filter + // → pick), so we preserve the current input text and leave the + // already-selected items visible as checked items in the list. + if (this.opts.type.current === 'single') { + const label = next.length > 0 ? (this.labelRegistry.get(next[0]) ?? next[0]) : ''; + this.opts.inputValue.current = label; + } + const eventTarget = target ?? this.resolveItemEl(value); if (eventName && eventTarget) { void this.runtime.trigger(eventName, { fallbackTarget: eventTarget });