feat: add arrow key support to Selector
parent
e5fc315852
commit
8eb4466222
|
@ -29,6 +29,7 @@
|
||||||
on:input={onInput}
|
on:input={onInput}
|
||||||
on:focus={() => (focused = true)}
|
on:focus={() => (focused = true)}
|
||||||
on:blur={() => (focused = false)}
|
on:blur={() => (focused = false)}
|
||||||
|
on:keydown
|
||||||
{disabled}
|
{disabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -182,14 +182,59 @@
|
||||||
visible = false;
|
visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputFocused = false;
|
let listEl: HTMLUListElement;
|
||||||
let hover = false;
|
let optionFocusIndex: number = -1;
|
||||||
$: visible = (inputFocused || hover) && Boolean(options.length);
|
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;
|
let input: Input;
|
||||||
export function focus() {
|
export function focus() {
|
||||||
input.focus();
|
input.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let inputFocused = false;
|
||||||
|
$: visible =
|
||||||
|
(inputFocused || optionFocusIndex > -1) && Boolean(options.length);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="selector">
|
<div class="selector">
|
||||||
|
@ -206,18 +251,28 @@
|
||||||
bind:value={inputValue}
|
bind:value={inputValue}
|
||||||
on:input={onInput}
|
on:input={onInput}
|
||||||
on:focusChange={(ev) => (inputFocused = ev.detail)}
|
on:focusChange={(ev) => (inputFocused = ev.detail)}
|
||||||
|
on:keydown={handleArrowKeys}
|
||||||
{disabled}
|
{disabled}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<ul
|
<ul class="options" class:visible bind:this={listEl}>
|
||||||
class="options"
|
{#each options.slice(0, MAX_OPTIONS) as option, idx}
|
||||||
class:visible
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
on:mouseenter={() => (hover = true)}
|
<li
|
||||||
on:mouseleave={() => (hover = false)}
|
tabindex="0"
|
||||||
>
|
on:click={() => set(option)}
|
||||||
{#each options.slice(0, MAX_OPTIONS) as option}
|
on:mousemove={() => focus()}
|
||||||
<li on:click={() => set(option)}>
|
on:focus={() => (optionFocusIndex = idx)}
|
||||||
|
on:blur={() => (optionFocusIndex = -1)}
|
||||||
|
on:keydown={(ev) => {
|
||||||
|
if (ev.key === "Enter") {
|
||||||
|
set(option);
|
||||||
|
} else {
|
||||||
|
handleArrowKeys(ev);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
{#if option.attribute}
|
{#if option.attribute}
|
||||||
{option.attribute}
|
{option.attribute}
|
||||||
{:else if option.value}
|
{:else if option.value}
|
||||||
|
@ -282,11 +337,16 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.1s;
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--background-lighter);
|
background-color: var(--background-lighter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background-color: var(--background-lighter);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.type,
|
.type,
|
||||||
.content {
|
.content {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
Loading…
Reference in New Issue