2021-06-24 00:12:20 +02:00
|
|
|
<!--
|
|
|
|
Copyright: Ankitects Pty Ltd and contributors
|
|
|
|
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
-->
|
|
|
|
<script lang="typescript">
|
2021-06-28 17:40:04 +02:00
|
|
|
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";
|
|
|
|
|
2021-06-25 18:09:58 +02:00
|
|
|
import WithDropdownMenu from "components/WithDropdownMenu.svelte";
|
2021-06-24 20:41:50 +02:00
|
|
|
import DropdownMenu from "components/DropdownMenu.svelte";
|
2021-06-29 01:37:23 +02:00
|
|
|
import AutocompleteItem from "./AutocompleteItem.svelte";
|
2021-06-24 00:12:20 +02:00
|
|
|
|
2021-06-25 15:44:44 +02:00
|
|
|
let className: string = "";
|
|
|
|
export { className as class };
|
2021-06-24 00:12:20 +02:00
|
|
|
|
2021-06-29 00:14:31 +02:00
|
|
|
export let suggestionsPromise: Promise<string[]>;
|
2021-06-27 17:13:48 +02:00
|
|
|
|
2021-06-28 23:39:46 +02:00
|
|
|
let target: HTMLElement;
|
|
|
|
let dropdown: Dropdown;
|
|
|
|
let autocomplete: any;
|
2021-06-27 18:57:32 +02:00
|
|
|
|
2021-06-29 02:10:32 +02:00
|
|
|
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();
|
|
|
|
|
2021-06-29 01:37:23 +02:00
|
|
|
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] });
|
2021-06-28 19:21:01 +02:00
|
|
|
}
|
|
|
|
|
2021-06-29 01:37:23 +02:00
|
|
|
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] });
|
2021-06-28 19:21:01 +02:00
|
|
|
}
|
|
|
|
|
2021-06-29 02:10:32 +02:00
|
|
|
async function chooseSelected() {
|
|
|
|
const suggestions = await suggestionsPromise;
|
|
|
|
|
2021-06-28 19:21:01 +02:00
|
|
|
active = true;
|
2021-06-29 02:10:32 +02:00
|
|
|
dispatch("choose", { chosen: suggestions[selected ?? -1] });
|
2021-06-28 19:21:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async function update() {
|
|
|
|
dropdown = dropdown as Dropdown;
|
|
|
|
dropdown.update();
|
|
|
|
dispatch("update");
|
|
|
|
|
2021-06-29 01:37:23 +02:00
|
|
|
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");
|
|
|
|
}
|
2021-06-28 19:21:01 +02:00
|
|
|
}
|
|
|
|
|
2021-06-28 23:39:46 +02:00
|
|
|
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;
|
|
|
|
};
|
2021-06-28 19:21:01 +02:00
|
|
|
|
|
|
|
onDestroy(() => dropdown?.dispose());
|
2021-06-29 01:37:23 +02:00
|
|
|
|
|
|
|
function setSelected(index: number): void {
|
|
|
|
selected = index;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function chooseIndex(index: number): Promise<void> {
|
|
|
|
const suggestions = await suggestionsPromise;
|
2021-06-29 02:10:32 +02:00
|
|
|
dispatch("autocomplete", { name: suggestions[index] });
|
2021-06-29 01:37:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function selectIfMousedown(event: MouseEvent, index: number): void {
|
|
|
|
if (event.buttons === 1) {
|
|
|
|
setSelected(index);
|
|
|
|
}
|
|
|
|
}
|
2021-06-24 00:12:20 +02:00
|
|
|
</script>
|
|
|
|
|
2021-06-29 04:32:54 +02:00
|
|
|
<WithDropdownMenu toggleOnClick={false} let:menuId let:createDropdown>
|
2021-06-28 19:21:01 +02:00
|
|
|
<slot createAutocomplete={createAutocomplete(createDropdown)} {autocomplete} />
|
2021-06-25 18:09:58 +02:00
|
|
|
|
2021-06-25 18:14:32 +02:00
|
|
|
<DropdownMenu id={menuId} class={className}>
|
2021-06-29 00:14:31 +02:00
|
|
|
{#await suggestionsPromise}
|
2021-06-29 01:37:23 +02:00
|
|
|
<AutocompleteItem>...</AutocompleteItem>
|
2021-06-29 00:14:31 +02:00
|
|
|
{:then suggestions}
|
2021-06-29 01:37:23 +02:00
|
|
|
{#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
|
|
|
|
>
|
2021-06-29 00:14:31 +02:00
|
|
|
{/each}
|
|
|
|
{/await}
|
2021-06-25 18:14:32 +02:00
|
|
|
</DropdownMenu>
|
|
|
|
</WithDropdownMenu>
|