anki/ts/editor/WithAutocomplete.svelte

136 lines
4.1 KiB
Svelte
Raw Normal View History

<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="typescript">
import { createEventDispatcher, onDestroy, tick } from "svelte";
2021-06-28 02:05:32 +02:00
2021-06-25 19:23:35 +02:00
import type Dropdown from "bootstrap/js/dist/dropdown";
import WithDropdownMenu from "components/WithDropdownMenu.svelte";
import DropdownMenu from "components/DropdownMenu.svelte";
import AutocompleteItem from "./AutocompleteItem.svelte";
let className: string = "";
export { className as class };
export let suggestionsPromise: Promise<string[]>;
2021-06-27 17:13:48 +02:00
let target: HTMLElement;
let dropdown: Dropdown;
let autocomplete: any;
let selected: number | null = null;
2021-06-26 03:20:27 +02:00
let active: boolean = false;
2021-06-28 02:05:32 +02:00
const dispatch = createEventDispatcher();
async function selectNext() {
const suggestions = await suggestionsPromise;
if (selected === null) {
selected = 0;
} else if (selected >= suggestions.length - 1) {
selected = null;
} else {
selected++;
}
dispatch("autocomplete", { selected: suggestions[selected ?? -1] });
}
async function selectPrevious() {
const suggestions = await suggestionsPromise;
if (selected === null) {
selected = suggestions.length - 1;
} else if (selected === 0) {
selected = null;
} else {
selected--;
}
dispatch("autocomplete", { selected: suggestions[selected ?? -1] });
}
async function chooseSelected() {
const suggestions = await suggestionsPromise;
active = true;
dispatch("choose", { chosen: suggestions[selected ?? -1] });
}
async function update() {
dropdown = dropdown as Dropdown;
dropdown.update();
dispatch("update");
const [, suggestions] = await Promise.all([tick(), suggestionsPromise]);
if (suggestions.length > 0) {
dropdown.show();
// disabled class will tell Bootstrap not to show menu on clicking
target.classList.remove("disabled");
} else {
dropdown.hide();
target.classList.add("disabled");
}
}
const createAutocomplete =
(createDropdown: (element: HTMLElement) => Dropdown) =>
(element: HTMLElement): any => {
target = element;
dropdown = createDropdown(element);
autocomplete = {
hide: dropdown.hide.bind(dropdown),
show: dropdown.show.bind(dropdown),
toggle: dropdown.toggle.bind(dropdown),
isVisible: (dropdown as any).isVisible,
selectPrevious,
selectNext,
chooseSelected,
update,
};
return autocomplete;
};
onDestroy(() => dropdown?.dispose());
function setSelected(index: number): void {
selected = index;
}
async function chooseIndex(index: number): Promise<void> {
const suggestions = await suggestionsPromise;
dispatch("autocomplete", { name: suggestions[index] });
}
function selectIfMousedown(event: MouseEvent, index: number): void {
if (event.buttons === 1) {
setSelected(index);
}
}
</script>
<WithDropdownMenu toggleOnClick={false} let:menuId let:createDropdown>
<slot createAutocomplete={createAutocomplete(createDropdown)} {autocomplete} />
<DropdownMenu id={menuId} class={className}>
{#await suggestionsPromise}
<AutocompleteItem>...</AutocompleteItem>
{:then suggestions}
{#each suggestions as suggestion, index}
<AutocompleteItem
selected={index === selected}
active={index === selected && active}
on:mousedown={() => setSelected(index)}
on:mouseenter={(event) => selectIfMousedown(event, index)}
on:mouseup={() => chooseIndex(index)}>{suggestion}</AutocompleteItem
>
{/each}
{/await}
</DropdownMenu>
</WithDropdownMenu>