Refactor WithDropdown to be more versatile and cause less bugs
This commit is contained in:
parent
90fdcebcc8
commit
6493adf7ca
@ -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 };
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
38
ts/components/WithDropdown.svelte
Normal file
38
ts/components/WithDropdown.svelte
Normal 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>
|
@ -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} />
|
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user