Switch WithAutocomplete to WithDropdown and fix SelectedTagBadge

This commit is contained in:
Henrik Giesel 2021-07-01 20:26:41 +02:00
parent 874a315f83
commit 3dff89fda5
5 changed files with 37 additions and 99 deletions

View file

@ -19,12 +19,19 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}); });
let dropdown: Dropdown; let dropdown: Dropdown;
let dropdownObject: Dropdown; let api: Dropdown & { isVisible: () => boolean };
function isVisible() {
return (dropdown as any)._menu
? (dropdown as any)._menu.classList.contains("show")
: false;
}
const noop = () => {}; const noop = () => {};
function createDropdown(toggle: HTMLElement): Dropdown { function createDropdown(toggle: HTMLElement): Dropdown {
/* avoid focusing element toggle on menu activation */ /* avoid focusing element toggle on menu activation */
toggle.focus = noop; toggle.focus = noop;
dropdown = new Dropdown(toggle, { dropdown = new Dropdown(toggle, {
autoClose, autoClose,
popperConfig: (defaultConfig: Record<string, any>) => ({ popperConfig: (defaultConfig: Record<string, any>) => ({
@ -37,22 +44,23 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
dropdown.show(); dropdown.show();
} }
dropdownObject = { let api = {
show: dropdown.show.bind(dropdown), show: dropdown.show.bind(dropdown),
toggle: dropdown.toggle.bind(dropdown), toggle: dropdown.toggle.bind(dropdown),
hide: dropdown.hide.bind(dropdown), hide: dropdown.hide.bind(dropdown),
update: dropdown.update.bind(dropdown), update: dropdown.update.bind(dropdown),
dispose: dropdown.dispose.bind(dropdown), dispose: dropdown.dispose.bind(dropdown),
isVisible,
}; };
return dropdownObject; return api;
} }
onDestroy(() => dropdown?.dispose()); onDestroy(() => dropdown?.dispose());
</script> </script>
<div class="dropdown"> <div class="dropdown">
<slot {createDropdown} {dropdownObject} /> <slot {createDropdown} dropdownObject={api} />
</div> </div>
<style lang="scss"> <style lang="scss">

View file

@ -1,60 +0,0 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="typescript">
import Dropdown from "bootstrap/js/dist/dropdown";
import { setContext } from "svelte";
import { dropdownKey } from "./contextKeys";
export let disabled = false;
export let toggleOnClick = true;
setContext(dropdownKey, {
dropdown: true,
"data-bs-toggle": "dropdown",
"aria-expanded": "false",
});
const menuId = Math.random().toString(36).substring(2);
let dropdown: Dropdown | undefined;
function activateDropdown(): void {
if (dropdown && !disabled) {
dropdown.toggle();
}
}
function isVisible(): boolean {
return (dropdown as any)._menu.classList.contains("show");
}
/* Normally dropdown and trigger are associated with a
/* common ancestor with .dropdown class */
function createDropdown(element: HTMLElement): Dropdown {
/* Prevent focus on menu activation */
const noop = () => {};
Object.defineProperty(element, "focus", { value: noop, configurable: true });
const menu = (element.getRootNode() as Document | ShadowRoot).getElementById(
menuId
);
if (!menu) {
throw Error(`Could not find menu "${menuId}" for dropdown menu.`);
}
if (!toggleOnClick) {
(Dropdown.prototype as any)._addEventListeners = noop;
}
dropdown = new Dropdown(element);
/* Set custom menu without using common element with .dropdown */
(dropdown as any)._menu = menu;
Object.defineProperty(dropdown, "isVisible", { value: isVisible });
return dropdown as Dropdown;
}
</script>
<slot {createDropdown} {activateDropdown} {menuId} />

View file

@ -6,7 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import Badge from "components/Badge.svelte"; import Badge from "components/Badge.svelte";
import WithDropdownMenu from "components/WithDropdownMenu.svelte"; import WithDropdown from "components/WithDropdown.svelte";
import WithShortcut from "components/WithShortcut.svelte"; import WithShortcut from "components/WithShortcut.svelte";
import DropdownMenu from "components/DropdownMenu.svelte"; import DropdownMenu from "components/DropdownMenu.svelte";
import DropdownItem from "components/DropdownItem.svelte"; import DropdownItem from "components/DropdownItem.svelte";
@ -20,15 +20,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const removeLabel = "Remove tags"; const removeLabel = "Remove tags";
</script> </script>
<WithDropdownMenu let:menuId let:createDropdown> <WithDropdown let:createDropdown>
<div class="dropdown"> <div class="more-icon">
<div class="more-icon"> <Badge class="me-1" on:mount={withSpan(createDropdown)}>{@html dotsIcon}</Badge>
<Badge class="me-1" on:mount={withSpan(createDropdown)}
>{@html dotsIcon}</Badge
>
</div>
<DropdownMenu id={menuId}> <DropdownMenu>
<WithShortcut shortcut="C" let:createShortcut let:shortcutLabel> <WithShortcut shortcut="C" let:createShortcut let:shortcutLabel>
<DropdownItem <DropdownItem
on:click={() => dispatch("tagcopy")} on:click={() => dispatch("tagcopy")}
@ -45,7 +41,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</WithShortcut> </WithShortcut>
</DropdownMenu> </DropdownMenu>
</div> </div>
</WithDropdownMenu> </WithDropdown>
<style lang="scss"> <style lang="scss">
.more-icon { .more-icon {

View file

@ -33,6 +33,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let activeName = ""; let activeName = "";
let activeInput: HTMLInputElement; let activeInput: HTMLInputElement;
let autocomplete: any;
let suggestionsPromise: Promise<string[]> = Promise.resolve([]); let suggestionsPromise: Promise<string[]> = Promise.resolve([]);
function updateSuggestions(): void { function updateSuggestions(): void {
@ -104,8 +105,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
async function enterBehavior( async function enterBehavior(
index: number, index: number,
start: number, start: number,
end: number, end: number
autocomplete: any
): Promise<void> { ): Promise<void> {
if (autocomplete.isVisible()) { if (autocomplete.isVisible()) {
autocomplete.chooseSelected(); autocomplete.chooseSelected();
@ -227,7 +227,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
return event.code === "Backspace" || event.code === "Delete"; return event.code === "Backspace" || event.code === "Delete";
} }
function onKeydown(event: KeyboardEvent, autocomplete: any): void { function onKeydown(event: KeyboardEvent): void {
const visible = autocomplete.isVisible(); const visible = autocomplete.isVisible();
const printable = isPrintableKey(event); const printable = isPrintableKey(event);
const deletion = isDeletionKey(event); const deletion = isDeletionKey(event);
@ -274,7 +274,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
} }
function onKeyup(_event: KeyboardEvent, autocomplete: any): void { function onKeyup(_event: KeyboardEvent): void {
if (activeName.length === 0) { if (activeName.length === 0) {
autocomplete.hide(); autocomplete.hide();
} }
@ -384,7 +384,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
onAutocomplete(detail.selected)} onAutocomplete(detail.selected)}
on:choose={({ detail }) => onChosen(detail.chosen)} on:choose={({ detail }) => onChosen(detail.chosen)}
let:createAutocomplete let:createAutocomplete
let:autocomplete
> >
<TagInput <TagInput
id={tag.id} id={tag.id}
@ -392,18 +391,13 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
bind:input={activeInput} bind:input={activeInput}
on:focus={() => { on:focus={() => {
activeName = tag.name; activeName = tag.name;
createAutocomplete(activeInput); autocomplete = createAutocomplete(activeInput);
}} }}
on:keydown={(event) => onKeydown(event, autocomplete)} on:keydown={onKeydown}
on:keyup={(event) => onKeyup(event, autocomplete)} on:keyup={onKeyup}
on:input={() => updateTagName(tag)} on:input={() => updateTagName(tag)}
on:tagsplit={({ detail }) => on:tagsplit={({ detail }) =>
enterBehavior( enterBehavior(index, detail.start, detail.end)}
index,
detail.start,
detail.end,
autocomplete
)}
on:tagadd={() => insertTag(index)} on:tagadd={() => insertTag(index)}
on:tagdelete={() => deleteTagAt(index)} on:tagdelete={() => deleteTagAt(index)}
on:tagjoinprevious={() => joinWithPreviousTag(index)} on:tagjoinprevious={() => joinWithPreviousTag(index)}

View file

@ -7,7 +7,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import type Dropdown from "bootstrap/js/dist/dropdown"; import type Dropdown from "bootstrap/js/dist/dropdown";
import WithDropdownMenu from "components/WithDropdownMenu.svelte"; import WithDropdown from "components/WithDropdown.svelte";
import DropdownMenu from "components/DropdownMenu.svelte"; import DropdownMenu from "components/DropdownMenu.svelte";
import AutocompleteItem from "./AutocompleteItem.svelte"; import AutocompleteItem from "./AutocompleteItem.svelte";
@ -18,7 +18,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let target: HTMLElement; let target: HTMLElement;
let dropdown: Dropdown; let dropdown: Dropdown;
let autocomplete: any;
let selected: number | null = null; let selected: number | null = null;
let active: boolean = false; let active: boolean = false;
@ -82,10 +81,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
(element: HTMLElement): any => { (element: HTMLElement): any => {
target = element; target = element;
dropdown = createDropdown(element); dropdown = createDropdown(element);
autocomplete = {
hide: dropdown.hide.bind(dropdown), const api = {
show: dropdown.show.bind(dropdown), hide: dropdown.hide,
toggle: dropdown.toggle.bind(dropdown), show: dropdown.show,
toggle: dropdown.toggle,
isVisible: (dropdown as any).isVisible, isVisible: (dropdown as any).isVisible,
selectPrevious, selectPrevious,
selectNext, selectNext,
@ -93,7 +93,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
update, update,
}; };
return autocomplete; return api;
}; };
onDestroy(() => dropdown?.dispose()); onDestroy(() => dropdown?.dispose());
@ -114,10 +114,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
</script> </script>
<WithDropdownMenu toggleOnClick={false} let:menuId let:createDropdown> <WithDropdown let:createDropdown>
<slot createAutocomplete={createAutocomplete(createDropdown)} {autocomplete} /> <slot createAutocomplete={createAutocomplete(createDropdown)} />
<DropdownMenu id={menuId} class={className}> <DropdownMenu class={className}>
{#await suggestionsPromise} {#await suggestionsPromise}
<AutocompleteItem>...</AutocompleteItem> <AutocompleteItem>...</AutocompleteItem>
{:then suggestions} {:then suggestions}
@ -132,4 +132,4 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{/each} {/each}
{/await} {/await}
</DropdownMenu> </DropdownMenu>
</WithDropdownMenu> </WithDropdown>