feat: add arrow key support to Selector
parent
e5fc315852
commit
8eb4466222
|
@ -29,6 +29,7 @@
|
|||
on:input={onInput}
|
||||
on:focus={() => (focused = true)}
|
||||
on:blur={() => (focused = false)}
|
||||
on:keydown
|
||||
{disabled}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -182,14 +182,59 @@
|
|||
visible = false;
|
||||
}
|
||||
|
||||
let inputFocused = false;
|
||||
let hover = false;
|
||||
$: visible = (inputFocused || hover) && Boolean(options.length);
|
||||
let listEl: HTMLUListElement;
|
||||
let optionFocusIndex: number = -1;
|
||||
function handleArrowKeys(ev: KeyboardEvent) {
|
||||
if (!options.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const optionEls = Array.from(listEl.children) as HTMLLIElement[];
|
||||
const currentIndex = optionEls.findIndex(
|
||||
(el) => document.activeElement === el
|
||||
);
|
||||
|
||||
let targetIndex = currentIndex;
|
||||
switch (ev.key) {
|
||||
case "ArrowDown":
|
||||
targetIndex += 1;
|
||||
|
||||
// pressed down on last
|
||||
if (targetIndex >= optionEls.length) {
|
||||
targetIndex = 0;
|
||||
}
|
||||
break;
|
||||
case "ArrowUp":
|
||||
targetIndex -= 1;
|
||||
|
||||
// pressed up on input
|
||||
if (targetIndex == -2) {
|
||||
targetIndex = optionEls.length - 1;
|
||||
}
|
||||
|
||||
// pressed up on first
|
||||
if (targetIndex == -1) {
|
||||
focus();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return; // early return, stop processing
|
||||
}
|
||||
|
||||
if (optionEls[targetIndex]) {
|
||||
optionEls[targetIndex].focus();
|
||||
}
|
||||
}
|
||||
|
||||
let input: Input;
|
||||
export function focus() {
|
||||
input.focus();
|
||||
}
|
||||
|
||||
let inputFocused = false;
|
||||
$: visible =
|
||||
(inputFocused || optionFocusIndex > -1) && Boolean(options.length);
|
||||
</script>
|
||||
|
||||
<div class="selector">
|
||||
|
@ -206,18 +251,28 @@
|
|||
bind:value={inputValue}
|
||||
on:input={onInput}
|
||||
on:focusChange={(ev) => (inputFocused = ev.detail)}
|
||||
on:keydown={handleArrowKeys}
|
||||
{disabled}
|
||||
{placeholder}
|
||||
/>
|
||||
{/if}
|
||||
<ul
|
||||
class="options"
|
||||
class:visible
|
||||
on:mouseenter={() => (hover = true)}
|
||||
on:mouseleave={() => (hover = false)}
|
||||
>
|
||||
{#each options.slice(0, MAX_OPTIONS) as option}
|
||||
<li on:click={() => set(option)}>
|
||||
<ul class="options" class:visible bind:this={listEl}>
|
||||
{#each options.slice(0, MAX_OPTIONS) as option, idx}
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<li
|
||||
tabindex="0"
|
||||
on:click={() => set(option)}
|
||||
on:mousemove={() => focus()}
|
||||
on:focus={() => (optionFocusIndex = idx)}
|
||||
on:blur={() => (optionFocusIndex = -1)}
|
||||
on:keydown={(ev) => {
|
||||
if (ev.key === "Enter") {
|
||||
set(option);
|
||||
} else {
|
||||
handleArrowKeys(ev);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if option.attribute}
|
||||
{option.attribute}
|
||||
{:else if option.value}
|
||||
|
@ -282,11 +337,16 @@
|
|||
cursor: pointer;
|
||||
padding: 0.5em;
|
||||
|
||||
transition: background-color 0.2s;
|
||||
transition: background-color 0.1s;
|
||||
&:hover {
|
||||
background-color: var(--background-lighter);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: var(--background-lighter);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.type,
|
||||
.content {
|
||||
display: inline-block;
|
||||
|
|
Loading…
Reference in New Issue