Refactor WithDropdown to be more versatile and cause less bugs

This commit is contained in:
Henrik Giesel 2021-06-30 14:01:33 +02:00
parent 90fdcebcc8
commit 6493adf7ca
8 changed files with 87 additions and 92 deletions

View File

@ -8,7 +8,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import ButtonToolbar from "./ButtonToolbar.svelte"; import ButtonToolbar from "./ButtonToolbar.svelte";
export let id: string; export let id: string | undefined = undefined;
let className = ""; let className = "";
export { className as class }; export { className as class };

View File

@ -6,7 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { setContext } from "svelte"; import { setContext } from "svelte";
import { dropdownKey } from "./contextKeys"; import { dropdownKey } from "./contextKeys";
export let id: string; export let id: string | undefined = undefined;
setContext(dropdownKey, null); setContext(dropdownKey, null);
</script> </script>

View File

@ -0,0 +1,38 @@
<!--
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, onDestroy } from "svelte";
import { dropdownKey } from "./contextKeys";
setContext(dropdownKey, {
dropdown: true,
"data-bs-toggle": "dropdown",
});
let dropdown: Dropdown;
const noop = () => {};
function createDropdown(toggle: HTMLElement): Dropdown {
/* avoid focusing element toggle on menu activation */
toggle.focus = noop;
dropdown = new Dropdown(toggle, {} as any);
return dropdown;
}
onDestroy(() => dropdown?.dispose());
</script>
<div class="dropdown">
<slot {createDropdown} />
</div>
<style lang="scss">
div {
display: contents;
}
</style>

View File

@ -1,49 +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;
setContext(dropdownKey, {
dropdown: true,
"data-bs-toggle": "dropdown",
"aria-expanded": "false",
});
const menuId = Math.random().toString(36).substring(2);
let dropdown: Dropdown;
function activateDropdown(): void {
if (!disabled) {
dropdown.toggle();
}
}
/* Normally dropdown and trigger are associated with a
/* common ancestor with .dropdown class */
function createDropdown(element: HTMLElement): void {
/* Prevent focus on menu activation */
const noop = () => {};
Object.defineProperty(element, "focus", { value: noop });
const menu = (element.getRootNode() as Document) /* or shadow root */
.getElementById(menuId);
if (!menu) {
console.log(`Could not find menu "${menuId}" for dropdown menu.`);
} else {
dropdown = new Dropdown(element);
/* Set custom menu without using common element with .dropdown */
(dropdown as any)._menu = menu;
}
}
</script>
<slot {createDropdown} {activateDropdown} {menuId} />

View File

@ -4,7 +4,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
import WithDropdownMenu from "components/WithDropdownMenu.svelte"; import type Dropdown from "bootstrap/js/dist/dropdown";
import WithDropdown from "components/WithDropdown.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";
import Badge from "./Badge.svelte"; import Badge from "./Badge.svelte";
@ -32,43 +33,41 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
let modified: boolean; let modified: boolean;
$: modified = !isEqual(value, defaultValue); $: modified = !isEqual(value, defaultValue);
$: className = !modified ? "opacity-0" : "";
let dropdown: Dropdown;
const isTouchDevice = getContext<boolean>(touchDeviceKey); const isTouchDevice = getContext<boolean>(touchDeviceKey);
function revert(): void { function revert(): void {
value = cloneDeep(defaultValue); value = cloneDeep(defaultValue);
dropdown.hide();
} }
</script> </script>
<WithDropdownMenu <WithDropdown let:createDropdown>
disabled={!modified} <div class:hide={!modified}>
let:createDropdown <Badge
let:activateDropdown class="p-1"
let:menuId on:mount={(event) => (dropdown = createDropdown(event.detail.span))}
>
<Badge
class={`p-1 ${className}`}
on:mount={(event) => createDropdown(event.detail.span)}
on:click={activateDropdown}
>
{@html revertIcon}
</Badge>
<DropdownMenu id={menuId}>
<DropdownItem
class={`spinner ${isTouchDevice ? "spin-always" : ""}`}
on:click={() => { on:click={() => {
revert(); if (modified) {
// Otherwise the menu won't close when the item is clicked dropdown.toggle();
// TODO: investigate why this is necessary }
activateDropdown();
}} }}
> >
{tr.deckConfigRevertButtonTooltip()}<Badge>{@html revertIcon}</Badge> {@html revertIcon}
</DropdownItem> </Badge>
</DropdownMenu>
</WithDropdownMenu> <DropdownMenu>
<DropdownItem
class={`spinner ${isTouchDevice ? "spin-always" : ""}`}
on:click={() => revert()}
>
{tr.deckConfigRevertButtonTooltip()}<Badge>{@html revertIcon}</Badge>
</DropdownItem>
</DropdownMenu>
</div>
</WithDropdown>
<style lang="scss"> <style lang="scss">
:global(.spinner:hover .badge, .spinner.spin-always .badge) { :global(.spinner:hover .badge, .spinner.spin-always .badge) {
@ -84,4 +83,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
transform: rotate(0deg); transform: rotate(0deg);
} }
} }
.hide :global(.badge) {
opacity: 0;
}
</style> </style>

View File

@ -6,6 +6,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from "svelte";
import type { DeckOptionsState } from "./lib"; import type { DeckOptionsState } from "./lib";
import type Dropdown from "bootstrap/js/dist/dropdown";
import ButtonGroup from "components/ButtonGroup.svelte"; import ButtonGroup from "components/ButtonGroup.svelte";
import ButtonGroupItem from "components/ButtonGroupItem.svelte"; import ButtonGroupItem from "components/ButtonGroupItem.svelte";
@ -14,7 +15,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import DropdownMenu from "components/DropdownMenu.svelte"; import DropdownMenu from "components/DropdownMenu.svelte";
import DropdownItem from "components/DropdownItem.svelte"; import DropdownItem from "components/DropdownItem.svelte";
import DropdownDivider from "components/DropdownDivider.svelte"; import DropdownDivider from "components/DropdownDivider.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";
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
@ -53,6 +54,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
commitEditing(); commitEditing();
state.save(applyToChildDecks); state.save(applyToChildDecks);
} }
let dropdown: Dropdown;
</script> </script>
<ButtonGroup> <ButtonGroup>
@ -68,12 +71,12 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</ButtonGroupItem> </ButtonGroupItem>
<ButtonGroupItem> <ButtonGroupItem>
<WithDropdownMenu let:createDropdown let:activateDropdown let:menuId> <WithDropdown let:createDropdown>
<LabelButton <LabelButton
on:mount={(event) => createDropdown(event.detail.button)} on:mount={(event) => (dropdown = createDropdown(event.detail.button))}
on:click={activateDropdown} on:click={() => dropdown.toggle()}
/> />
<DropdownMenu id={menuId}> <DropdownMenu>
<DropdownItem on:click={() => dispatch("add")} <DropdownItem on:click={() => dispatch("add")}
>{tr.deckConfigAddGroup()}</DropdownItem >{tr.deckConfigAddGroup()}</DropdownItem
> >
@ -91,6 +94,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{tr.deckConfigSaveToAllSubdecks()} {tr.deckConfigSaveToAllSubdecks()}
</DropdownItem> </DropdownItem>
</DropdownMenu> </DropdownMenu>
</WithDropdownMenu> </WithDropdown>
</ButtonGroupItem> </ButtonGroupItem>
</ButtonGroup> </ButtonGroup>

View File

@ -11,7 +11,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import IconButton from "components/IconButton.svelte"; import IconButton from "components/IconButton.svelte";
import ButtonDropdown from "components/ButtonDropdown.svelte"; import ButtonDropdown from "components/ButtonDropdown.svelte";
import Item from "components/Item.svelte"; import Item from "components/Item.svelte";
import WithDropdownMenu from "components/WithDropdownMenu.svelte"; import WithDropdown from "components/WithDropdown.svelte";
import OnlyEditable from "./OnlyEditable.svelte"; import OnlyEditable from "./OnlyEditable.svelte";
import CommandIconButton from "./CommandIconButton.svelte"; import CommandIconButton from "./CommandIconButton.svelte";
@ -63,7 +63,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</ButtonGroupItem> </ButtonGroupItem>
<ButtonGroupItem> <ButtonGroupItem>
<WithDropdownMenu let:createDropdown let:menuId> <WithDropdown let:createDropdown>
<OnlyEditable let:disabled> <OnlyEditable let:disabled>
<IconButton <IconButton
{disabled} {disabled}
@ -73,7 +73,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</IconButton> </IconButton>
</OnlyEditable> </OnlyEditable>
<ButtonDropdown id={menuId}> <ButtonDropdown>
<Item id="justify"> <Item id="justify">
<ButtonGroup> <ButtonGroup>
<ButtonGroupItem> <ButtonGroupItem>
@ -142,6 +142,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</ButtonGroup> </ButtonGroup>
</Item> </Item>
</ButtonDropdown> </ButtonDropdown>
</WithDropdownMenu> </WithDropdown>
</ButtonGroupItem> </ButtonGroupItem>
</ButtonGroup> </ButtonGroup>

View File

@ -13,7 +13,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import IconButton from "components/IconButton.svelte"; import IconButton from "components/IconButton.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";
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 WithContext from "components/WithContext.svelte"; import WithContext from "components/WithContext.svelte";
import OnlyEditable from "./OnlyEditable.svelte"; import OnlyEditable from "./OnlyEditable.svelte";
@ -88,7 +88,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</ButtonGroupItem> </ButtonGroupItem>
<ButtonGroupItem> <ButtonGroupItem>
<WithDropdownMenu let:createDropdown let:menuId> <WithDropdown let:createDropdown>
<WithContext key={disabledKey} let:context={disabled}> <WithContext key={disabledKey} let:context={disabled}>
<OnlyEditable let:disabled={inCodable}> <OnlyEditable let:disabled={inCodable}>
<IconButton <IconButton
@ -100,7 +100,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</OnlyEditable> </OnlyEditable>
</WithContext> </WithContext>
<DropdownMenu id={menuId}> <DropdownMenu>
<WithShortcut <WithShortcut
shortcut={"Control+M, M"} shortcut={"Control+M, M"}
let:createShortcut let:createShortcut
@ -185,7 +185,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
</DropdownItem> </DropdownItem>
</WithShortcut> </WithShortcut>
</DropdownMenu> </DropdownMenu>
</WithDropdownMenu> </WithDropdown>
</ButtonGroupItem> </ButtonGroupItem>
<ButtonGroupItem> <ButtonGroupItem>