{#if selectionCount > 0}
{
selectedIds = [];
sortedItems = sortedItems.map((item) => ({
...item,
checked: false,
}));
}}
translateWithId={translateWithIdSelection}
{disabled}
{readonly}
/>
{/if}
{
if (disabled || readonly) return;
open = true;
}}
on:keydown
on:keydown|stopPropagation={(event) => {
if (readonly) return;
if (event.key === "Enter") {
if (highlightedId) {
const highlightedItem = sortedItems.find(
(item) => item.id === highlightedId,
);
if (highlightedItem) selectItem(highlightedItem);
}
} else if (event.key === "Tab") {
open = false;
} else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
const step = event.key === "ArrowDown" ? 1 : -1;
if (event.altKey) {
// APG combobox pattern: Alt+ArrowDown opens a closed menu
// without moving the highlight; Alt+ArrowUp closes an open one.
if (event.key === "ArrowDown" && !open) {
open = true;
} else if (event.key === "ArrowUp" && open) {
close("escape-key");
}
} else {
if (!open) open = true;
change(step);
}
} else if (event.key === "Escape") {
close("escape-key");
} else if (event.key === " ") {
if (!open) open = true;
} else if (event.key === "Backspace" && value === "") {
selectedIds = [];
sortedItems = sortedItems.map((item) => ({
...item,
checked: false,
}));
} else if (event.key === "Delete") {
if (open) {
value = "";
} else {
value = "";
selectedIds = [];
sortedItems = sortedItems.map((item) => ({
...item,
checked: false,
}));
}
}
}}
on:input
on:input={() => {
if (!open) open = true;
}}
on:keyup
on:focus
on:focus={() => {
fieldFocused = true;
}}
on:blur
on:blur={() => {
fieldFocused = false;
}}
on:paste
{disabled}
{readonly}
{placeholder}
{id}
{name}
>
{#if value}
{
value = "";
open = false;
}}
translateWithId={translateWithIdSelection}
{disabled}
{readonly}
{open}
/>
{/if}
{
if (disabled || readonly) return;
event.stopPropagation();
open = !open;
}}
{translateWithId}
{open}
/>
{:else}
{
fieldFocused = true;
}}
on:click={() => {
if (disabled || readonly) return;
open = !open;
}}
on:keydown={(event) => {
if (
event.key === " " ||
event.key === "Enter" ||
event.key === "ArrowUp" ||
event.key === "ArrowDown"
) {
// Prevent the native button from synthesizing a click (which would
// toggle the menu) so these keys are handled solely below.
event.preventDefault();
}
if (readonly) return;
if (event.key === " ") {
open = !open;
} else if (event.key === "Tab") {
open = false;
} else if (event.key === "ArrowDown" || event.key === "ArrowUp") {
const step = event.key === "ArrowDown" ? 1 : -1;
if (event.altKey) {
// APG combobox pattern: Alt+ArrowDown opens a closed menu
// without moving the highlight; Alt+ArrowUp closes an open one.
if (event.key === "ArrowDown" && !open) {
open = true;
} else if (event.key === "ArrowUp" && open) {
close("escape-key");
}
} else {
if (!open) open = true;
change(step);
}
} else if (event.key === "Enter") {
if (highlightedIndex > -1) {
const item = (filterable ? filteredItems : sortedItems)[
highlightedIndex
];
if (item) selectItem(item);
}
} else if (event.key === "Escape") {
close("escape-key");
}
}}
on:blur={(event) => {
fieldFocused = false;
dispatch("blur", event);
}}
{id}
{disabled}
{readonly}
{translateWithId}
>
{#if selectionCount > 0}
{
selectedIds = [];
sortedItems = sortedItems.map((item) => ({
...item,
checked: false,
}));
}}
translateWithId={translateWithIdSelection}
{disabled}
{readonly}
/>
{/if}
{label}
{/if}
{
listScrollTop = event.target.scrollTop;
}}
on:mouseleave={() => {
// Clear the hover highlight when the cursor leaves the menu so the
// highlighted state does not linger on the last hovered item.
highlightedIndex = -1;
}}
bind:ref={listRef}
style={effectivePortalMenu
? `max-height: ${virtualConfig
? `${virtualConfig.containerHeight}px; overflow-y: auto`
: menuMaxHeight};`
: virtualConfig
? `max-height: ${virtualConfig.containerHeight}px; overflow-y: auto;`
: undefined}
>
{#if virtualData?.isVirtualized}
{#each itemsToRender as item, index (item.id)}
{@const actualIndex = virtualData.startIndex + index}
{
if (item.disabled) {
event.stopPropagation();
return;
}
// Label default synthesizes a second click; without this,
// selectItem runs twice and the toggle nets to no change.
event.preventDefault();
selectItem(item);
if (filterable) {
inputRef?.focus();
} else {
fieldRef?.focus();
}
}}
on:mouseenter={() => {
if (item.disabled) return;
highlightedIndex = actualIndex;
}}
>
{itemToString(item)}
{/each}
{:else}
{#each itemsToRender as item, index (item.id)}
{
if (item.disabled) {
event.stopPropagation();
return;
}
// Label default synthesizes a second click; without this,
// selectItem runs twice and the toggle nets to no change.
event.preventDefault();
selectItem(item);
if (filterable) {
inputRef?.focus();
} else {
fieldRef?.focus();
}
}}
on:mouseenter={() => {
if (item.disabled) return;
highlightedIndex = index;
}}
>
{itemToString(item)}
{/each}
{/if}