combobox: improve icon padding behaviour

Tween the value and make it more consistent with the underlying
StyledRawInput type.
This commit is contained in:
Elijah Duffy
2026-03-10 14:55:44 -07:00
parent 7576f32e86
commit 3d240048c7

View File

@@ -50,6 +50,8 @@
import { matchSorter } from 'match-sorter'; import { matchSorter } from 'match-sorter';
import Spinner from './Spinner.svelte'; import Spinner from './Spinner.svelte';
import type { KeyOption } from 'match-sorter'; import type { KeyOption } from 'match-sorter';
import { Tween } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
interface Props { interface Props {
/** /**
@@ -147,7 +149,6 @@
*/ */
searchKeys?: ('value' | 'label' | 'preview' | 'infotext')[]; searchKeys?: ('value' | 'label' | 'preview' | 'infotext')[];
// TODO: implement searchKeys above.
/** Additional classes applied to the persistent div container */ /** Additional classes applied to the persistent div container */
class?: ClassValue | null | undefined; class?: ClassValue | null | undefined;
/** Optional action applied to the main input */ /** Optional action applied to the main input */
@@ -216,7 +217,6 @@
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);
@@ -264,6 +264,29 @@
/** validation options build from props */ /** validation options build from props */
const validateOpts: ValidatorOptions = $derived({ required }); const validateOpts: ValidatorOptions = $derived({ required });
/** calculates padding for the input based on icon visibility and size */
const calculatePadding = () => {
const gap = 5; // gap between icon and text
// base padding when no icon is visible, see StyledRawInput padding
const basePadding = compact ? 12 : 18;
// if icon is visible, padding is icon width + gap + base padding,
// otherwise it's just base padding
return iconVisible ? iconWidth + gap + basePadding : basePadding;
};
let iconWidth = $state<number>(0);
/** tweens main input padding */
const inputPadding = new Tween(calculatePadding(), {
duration: 150,
easing: cubicOut
});
$effect(() => {
if (iconWidth >= 0) {
untrack(() => {
inputPadding.target = calculatePadding();
});
}
});
/*** HELPER FUNCTIONS ***/ /*** HELPER FUNCTIONS ***/
/** returns the index of the highlighted item in filteredItems */ /** returns the index of the highlighted item in filteredItems */
@@ -637,8 +660,8 @@
{#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' iconWidth === 0 && 'opacity-0'
]} ]}
transition:scale transition:scale
bind:clientWidth={iconWidth} bind:clientWidth={iconWidth}
@@ -663,7 +686,7 @@
<!-- Combobox input box --> <!-- Combobox input box -->
<StyledRawInput <StyledRawInput
style={iconWidth && iconVisible ? `padding-left: ${iconWidth + 14 + 10}px` : undefined} style={`padding-left: ${inputPadding.current}px`}
class={[caret && 'pr-9', !valid && 'border-red-500!']} class={[caret && 'pr-9', !valid && 'border-red-500!']}
{compact} {compact}
type="text" type="text"