combobox: default icon support, smart icon width
This commit is contained in:
@@ -24,7 +24,7 @@
|
|||||||
import { Portal } from '@jsrob/svelte-portal';
|
import { Portal } from '@jsrob/svelte-portal';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { scale } from 'svelte/transition';
|
import { scale } from 'svelte/transition';
|
||||||
import { generateIdentifier } from './util';
|
import { generateIdentifier, type IconDef } from './util';
|
||||||
import type { ClassValue } from 'svelte/elements';
|
import type { ClassValue } from 'svelte/elements';
|
||||||
import { matchSorter } from 'match-sorter';
|
import { matchSorter } from 'match-sorter';
|
||||||
import Spinner from './Spinner.svelte';
|
import Spinner from './Spinner.svelte';
|
||||||
@@ -40,6 +40,8 @@
|
|||||||
invalidMessage?: string | null;
|
invalidMessage?: string | null;
|
||||||
label?: string;
|
label?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
|
/** displayed by default if no item is selected that has its own icon */
|
||||||
|
icon?: IconDef;
|
||||||
/**
|
/**
|
||||||
* enables loading spinner and fallback text if no items are found. may be changed
|
* enables loading spinner and fallback text if no items are found. may be changed
|
||||||
* internally if lazy is enabled. loading is, however, not bindable, so don't expect
|
* internally if lazy is enabled. loading is, however, not bindable, so don't expect
|
||||||
@@ -76,6 +78,7 @@
|
|||||||
invalidMessage = 'Please select an option',
|
invalidMessage = 'Please select an option',
|
||||||
label,
|
label,
|
||||||
placeholder,
|
placeholder,
|
||||||
|
icon,
|
||||||
loading = false,
|
loading = false,
|
||||||
lazy = false,
|
lazy = false,
|
||||||
compact = false,
|
compact = false,
|
||||||
@@ -98,6 +101,7 @@
|
|||||||
let searchValue = $state('');
|
let searchValue = $state('');
|
||||||
let pickerPosition = $state<'top' | 'bottom'>('bottom');
|
let pickerPosition = $state<'top' | 'bottom'>('bottom');
|
||||||
let searching = $state(false);
|
let searching = $state(false);
|
||||||
|
let iconWidth = $state<number | undefined>(undefined);
|
||||||
|
|
||||||
let searchInput = $state<HTMLInputElement | null>(null);
|
let searchInput = $state<HTMLInputElement | null>(null);
|
||||||
let searchContainer = $state<HTMLDivElement | null>(null);
|
let searchContainer = $state<HTMLDivElement | null>(null);
|
||||||
@@ -121,13 +125,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
/** controls whether an icon should be displayed */
|
/** controls whether an icon should be displayed */
|
||||||
let iconVisible = $derived.by(() => {
|
let iconVisible = $derived(
|
||||||
return (
|
(open && highlighted && highlighted.icon) ||
|
||||||
(open && highlighted && highlighted.icon) ||
|
|
||||||
(value && value.icon && searchValue === '') ||
|
(value && value.icon && searchValue === '') ||
|
||||||
loading
|
loading ||
|
||||||
);
|
icon !== undefined
|
||||||
});
|
);
|
||||||
|
|
||||||
/** controls whether the highlighted option should be used in the selection preview */
|
/** controls whether the highlighted option should be used in the selection preview */
|
||||||
let useHighlighted = $derived.by(() => {
|
let useHighlighted = $derived.by(() => {
|
||||||
@@ -494,9 +497,11 @@
|
|||||||
{#if iconVisible}
|
{#if iconVisible}
|
||||||
<div
|
<div
|
||||||
class={[
|
class={[
|
||||||
|
(iconWidth === undefined || iconWidth === 0) && 'opacity-0',
|
||||||
'pointer-events-none absolute top-1/2 left-3.5 -translate-y-1/2 transform select-none'
|
'pointer-events-none absolute top-1/2 left-3.5 -translate-y-1/2 transform select-none'
|
||||||
]}
|
]}
|
||||||
transition:scale
|
transition:scale
|
||||||
|
bind:clientWidth={iconWidth}
|
||||||
>
|
>
|
||||||
{#if loading}
|
{#if loading}
|
||||||
<Spinner class="stroke-sui-accent! -mt-0.5" size="1em" />
|
<Spinner class="stroke-sui-accent! -mt-0.5" size="1em" />
|
||||||
@@ -504,6 +509,8 @@
|
|||||||
{@render highlighted.icon(highlighted)}
|
{@render highlighted.icon(highlighted)}
|
||||||
{:else if value?.icon}
|
{:else if value?.icon}
|
||||||
{@render value.icon(value)}
|
{@render value.icon(value)}
|
||||||
|
{:else if icon}
|
||||||
|
<icon.component {...icon.props} />
|
||||||
{:else}
|
{:else}
|
||||||
❌
|
❌
|
||||||
{/if}
|
{/if}
|
||||||
@@ -511,7 +518,8 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<StyledRawInput
|
<StyledRawInput
|
||||||
class={[iconVisible && 'pl-10', caret && 'pr-9', !valid && 'border-red-500!']}
|
style={iconWidth && iconVisible ? `padding-left: ${iconWidth + 14 + 10}px` : undefined}
|
||||||
|
class={[caret && 'pr-9', !valid && 'border-red-500!']}
|
||||||
{compact}
|
{compact}
|
||||||
type="text"
|
type="text"
|
||||||
name={name + '_search'}
|
name={name + '_search'}
|
||||||
|
|||||||
@@ -151,6 +151,12 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Combobox
|
<Combobox
|
||||||
|
icon={{
|
||||||
|
component: Plus,
|
||||||
|
props: {
|
||||||
|
size: '1.2em'
|
||||||
|
}
|
||||||
|
}}
|
||||||
label="Lazy combobox"
|
label="Lazy combobox"
|
||||||
placeholder="Choose..."
|
placeholder="Choose..."
|
||||||
options={lazyOptions}
|
options={lazyOptions}
|
||||||
|
|||||||
Reference in New Issue
Block a user