@ -5,6 +5,8 @@
type SearchFieldSize,
type SearchFieldVariant
} from '$uix/eidos/components/search-field';
import { Popover } from '$uix/eidos/components/popover';
import { PickerShell } from '$uix/eidos/components/picker-shell';
import { Search , X } from '$uix/eidos/components/icon';
import { compileMorfo } from '$uix/morfo';
import { searchFieldMorfo } from '@/uix/morfo/components/search-field';
@ -33,6 +35,8 @@
let lastValueChange = $state('—');
let trace = $state< TraceEntry [ ] > ([]);
let stageRef = $state< HTMLElement | null > (null);
let resultsOpen = $state(false);
let inputRef = $state< HTMLInputElement | null > (null);
const compiled = compileMorfo(searchFieldMorfo);
const partsList = $derived([...compiled.parts.byKebab.values()]);
@ -186,62 +190,97 @@
< div data-uix-stage >
< div data-uix-stage-area bind:this = { stageRef } >
< div style = "inline-size: min(26rem, 100%);" >
< SearchField
bind:value
{ placeholder }
{ clearOnEscape }
{ disabled }
{ readonly }
{ required }
{ invalid }
{ loading }
{ debounceMs }
{ size }
{ variant }
{ color }
onValueChange={( next ) => {
lastValueChange = next === '' ? 'empty' : next;
lastValueChangeAt = Date.now();
}}
onSubmit={( next ) => ( lastSubmit = next || 'empty' )}
onClear={() => ( lastSubmit = 'cleared' )}
>
< SearchField.Icon >
< Search size = "1em" decorative / >
< / SearchField.Icon >
< SearchField.Input / >
< SearchField.LoadingIndicator / >
< SearchField.ClearTrigger
aria-label={ uix . langs . ts ( '#?components.search-field.clear|Clear' )}
< Popover bind:open = { resultsOpen } >
< Popover.Anchor
onfocusin={() => {
if (!disabled & & !readonly) resultsOpen = true;
}}
>
< X size = "1em" decorative / >
< / SearchField.ClearTrigger >
< / SearchField >
<!-- Live result panel — filters the demo dataset by name/group. -->
< ul
class="search-field-results"
role="listbox"
aria-label="Filtered components"
aria-live="polite"
>
{ #if results . length === 0 }
< li class = "search-field-results-empty" >
No matches for < strong > "{ value } "</ strong >
< / li >
{ : else }
{ #each results as item ( item . name )}
< li class = "search-field-result" role = "option" aria-selected = "false" >
< span class = "search-field-result-name" > { item . name } </ span >
< span class = "search-field-result-group" > { item . group } </ span >
< / li >
{ /each }
{ /if }
< / ul >
< div class = "search-field-results-meta" >
< span data-uix-stage-trace-key > results< / span >
{ results . length } / { components . length }
< / div >
< SearchField
bind:value
{ placeholder }
{ clearOnEscape }
{ disabled }
{ readonly }
{ required }
{ invalid }
{ loading }
{ debounceMs }
{ size }
{ variant }
{ color }
onValueChange={( next ) => {
lastValueChange = next === '' ? 'empty' : next;
lastValueChangeAt = Date.now();
if (!disabled & & !readonly) resultsOpen = true;
}}
onSubmit={( next ) => {
lastSubmit = next || 'empty';
resultsOpen = false;
}}
onClear={() => {
lastSubmit = 'cleared';
resultsOpen = false;
}}
>
< SearchField.Icon >
< Search size = "1em" decorative / >
< / SearchField.Icon >
< SearchField.Input bind:ref = { inputRef } / >
< SearchField.LoadingIndicator / >
< SearchField.ClearTrigger
aria-label={ uix . langs . ts ( '#?components.search-field.clear|Clear' )}
>
< X size = "1em" decorative / >
< / SearchField.ClearTrigger >
< / SearchField >
< / Popover.Anchor >
< Popover.Portal >
< Popover.Content
align="start"
sideOffset={ 6 }
onOpenAutoFocus={( e ) => e . preventDefault ()}
onCloseAutoFocus={( e ) => e . preventDefault ()}
>
< PickerShell.Body >
<!-- Live result list — filters the demo dataset by name/group. -->
< ul
class="search-field-results"
role="listbox"
aria-label="Filtered components"
aria-live="polite"
>
{ #if results . length === 0 }
< li class = "search-field-results-empty" >
No matches for < strong > "{ value } "</ strong >
< / li >
{ : else }
{ #each results as item ( item . name )}
< li role = "option" aria-selected = { value === item . name } >
< button
type="button"
class="search-field-result"
onclick={() => {
value = item.name;
resultsOpen = false;
inputRef?.focus();
}}
>
< span class = "search-field-result-name" > { item . name } </ span >
< span class = "search-field-result-group" > { item . group } </ span >
< / button >
< / li >
{ /each }
{ /if }
< / ul >
< div class = "search-field-results-meta" >
< span data-uix-stage-trace-key > results< / span >
{ results . length } / { components . length }
< / div >
< / PickerShell.Body >
< / Popover.Content >
< / Popover.Portal >
< / Popover >
< / div >
< / div >
< div data-uix-stage-trace >
@ -636,48 +675,60 @@
< style >
.search-field-results {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
gap: var(--uix-space-1);
margin-block-start: var(--uix-space-3);
display: flex;
flex-direction: column;
gap: var(--space-1);
padding: 0;
margin: 0;
list-style: none;
max-block-size: 14rem;
min-inline-size: 14rem;
max-block-size: 16rem;
overflow-y: auto;
}
.search-field-result {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: var(--uix-space-2);
padding: var(--uix-space-1-5) var(--uix-space-2);
border: 1px solid var(--color-border-subtle);
gap: var(--space-3);
inline-size: 100%;
padding: var(--space-1-5) var(--space-2);
border: var(--border-width) solid transparent;
border-radius: var(--radius-md);
background: var(--color-surface-default);
font-size: var(--uix-text-sm);
background: transparent;
color: inherit;
font: inherit;
text-align: start;
cursor: pointer;
transition: background var(--duration-fast) var(--ease-default);
}
.search-field-result:hover {
background: var(--color-surface-overlay);
}
.search-field-result:focus-visible {
outline: var(--focus-ring-width) solid var(--color-primary-border);
outline-offset: var(--focus-ring-offset);
}
.search-field-result-name {
color: var(--color-content-primary);
font-weight: 500;
font-weight: var(--font-weight-medium);
font-size: var(--font-size-sm);
}
.search-field-result-group {
color: var(--color-content-muted);
font-size: var(--uix-text-xs);
font-size: var(--font-size -xs);
}
.search-field-results-empty {
grid-column: 1 / -1;
padding: var(--uix-space-3);
padding: var(--space-3);
color: var(--color-content-muted);
text-align: center;
font-size: var(--uix-text -sm);
font-size: var(--font-size -sm);
}
.search-field-results-meta {
display: flex;
align-items: center;
gap: var(--uix-space-2);
margin-block-start: var(--uix-space-2);
color: var(--uix-text-muted);
font-family: var(--uix-font-mono);
font-size: var(--uix-text-xs);
gap: var(--space-2);
color: var(--color-content-muted);
font-family: var(--font-family-mono);
font-size: var(--font-size-xs);
}
< / style >