mirror of
https://github.com/ankitects/anki.git
synced 2025-09-24 16:56:36 -04:00
Switch WithAutocomplete to WithDropdown and fix SelectedTagBadge
This commit is contained in:
parent
874a315f83
commit
3dff89fda5
5 changed files with 37 additions and 99 deletions
|
@ -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">
|
||||||
|
|
|
@ -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} />
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue