Add hiding functionality in ButtonGroup

This commit is contained in:
Henrik Giesel 2021-05-05 15:12:02 +02:00
parent 26f85a0f9d
commit e1cc22b9ee
5 changed files with 113 additions and 45 deletions

View file

@ -3,21 +3,21 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="typescript"> <script lang="typescript">
import ButtonGroupItem from "./ButtonGroupItem.svelte";
import type { SvelteComponentTyped } from "svelte"; import type { SvelteComponentTyped } from "svelte";
import { setContext } from "svelte"; import { setContext } from "svelte";
import type { Writable } from "svelte/store"; import type { Writable } from "svelte/store";
import { writable } from "svelte/store"; import { writable } from "svelte/store";
import { buttonGroupKey } from "./contextKeys"; import { buttonGroupKey } from "./contextKeys";
import type { Identifier } from "./identifier"; import type { Identifier } from "./identifier";
import { insert, add, update } from "./identifier"; import { insert, add, update, find } from "./identifier";
export let id: string | undefined = undefined; export let id: string | undefined = undefined;
let className: string = ""; let className: string = "";
export { className as class }; export { className as class };
export let api = {}; export let api = {};
export let buttonGroupRef: HTMLDivElement; let buttonGroupRef: HTMLDivElement;
$: root = buttonGroupRef?.getRootNode() as Document;
interface ButtonRegistration { interface ButtonRegistration {
detach: Writable<boolean>; detach: Writable<boolean>;
@ -25,20 +25,28 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const items: ButtonRegistration[] = []; const items: ButtonRegistration[] = [];
function registerButton(): ButtonRegistration { function makeRegistration(): ButtonRegistration {
const detach = writable(false); const detach = writable(false);
const registration = { detach }; return { detach };
items.push(registration); }
function registerButton(
index = items.length,
registration = makeRegistration()
): ButtonRegistration {
items.splice(index, 0, registration);
return registration; return registration;
} }
const dynamicItems: ButtonRegistration[] = [];
let dynamic: SvelteComponentTyped[] = []; let dynamic: SvelteComponentTyped[] = [];
function addButton( function addButton(
button: SvelteComponentTyped, button: SvelteComponentTyped,
add: (added: Element, parent: Element) => number add: (added: Element, parent: Element) => number
): void { ): void {
const registration = makeRegistration();
const callback = ( const callback = (
mutations: MutationRecord[], mutations: MutationRecord[],
observer: MutationObserver observer: MutationObserver
@ -48,6 +56,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
if (addedNode.nodeType === Node.ELEMENT_NODE) { if (addedNode.nodeType === Node.ELEMENT_NODE) {
const index = add(addedNode as Element, buttonGroupRef); const index = add(addedNode as Element, buttonGroupRef);
if (index >= 0) {
registerButton(index, registration);
}
} }
} }
@ -57,6 +69,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const observer = new MutationObserver(callback); const observer = new MutationObserver(callback);
observer.observe(buttonGroupRef, { childList: true }); observer.observe(buttonGroupRef, { childList: true });
dynamicItems.push(registration);
dynamic = [...dynamic, button]; dynamic = [...dynamic, button];
} }
@ -64,12 +77,26 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
addButton(button, (added, parent) => insert(added, parent, id)); addButton(button, (added, parent) => insert(added, parent, id));
const appendButton = (button: SvelteComponentTyped, id: Identifier = -1) => const appendButton = (button: SvelteComponentTyped, id: Identifier = -1) =>
addButton(button, (added, parent) => add(added, parent, id)); addButton(button, (added, parent) => add(added, parent, id));
function updateRegistration(
f: (registration: ButtonRegistration) => void,
id: Identifier
): void {
const match = find(buttonGroupRef.children, id);
if (match) {
const [index] = match;
const registration = items[index];
f(registration);
}
}
const showButton = (id: Identifier) => const showButton = (id: Identifier) =>
update((element) => element.removeAttribute("hidden"), buttonGroupRef, id); updateRegistration(({ detach }) => detach.update(() => false), id);
const hideButton = (id: Identifier) => const hideButton = (id: Identifier) =>
update((element) => element.setAttribute("hidden", ""), buttonGroupRef, id); updateRegistration(({ detach }) => detach.update(() => true), id);
const toggleButton = (id: Identifier) => const toggleButton = (id: Identifier) =>
update((element) => element.toggleAttribute("hidden"), buttonGroupRef, id); updateRegistration(({ detach }) => detach.update((old) => !old), id);
setContext( setContext(
buttonGroupKey, buttonGroupKey,
@ -97,7 +124,9 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<div bind:this={buttonGroupRef} {id} class={className} dir="ltr"> <div bind:this={buttonGroupRef} {id} class={className} dir="ltr">
<slot /> <slot />
{#each dynamic as component} {#each dynamic as component, i}
<ButtonGroupItem registration={dynamicItems[i]}>
<svelte:component this={component} /> <svelte:component this={component} />
</ButtonGroupItem>
{/each} {/each}
</div> </div>

View file

@ -8,8 +8,10 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import { getContext } from "svelte"; import { getContext } from "svelte";
import { buttonGroupKey } from "./contextKeys"; import { buttonGroupKey } from "./contextKeys";
export let registration = undefined;
const { registerButton } = getContext(buttonGroupKey); const { registerButton } = getContext(buttonGroupKey);
const { detach } = registerButton(); const { detach } = registration ?? registerButton();
</script> </script>
<Detachable detach={$detach}> <Detachable detach={$detach}>

View file

@ -2,54 +2,86 @@
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html // License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
export type Identifier = string | number; export type Identifier = string | number;
function find(collection: HTMLCollection, idOrIndex: Identifier): Element | null { export function find(
let element: Element | null = null; collection: HTMLCollection,
idOrIndex: Identifier
): [number, Element] | null {
let result: [number, Element] | null = null;
if (typeof idOrIndex === "string") { if (typeof idOrIndex === "string") {
element = collection.namedItem(idOrIndex); const element = collection.namedItem(idOrIndex);
if (element) {
const index = Array.prototype.indexOf.call(collection, element);
result = [index, element];
}
} else if (idOrIndex < 0) { } else if (idOrIndex < 0) {
const normalizedIndex = collection.length + idOrIndex; const index = collection.length + idOrIndex;
element = collection.item(normalizedIndex); const element = collection.item(index);
if (element) {
result = [index, element];
}
} else { } else {
element = collection.item(idOrIndex); const index = idOrIndex;
const element = collection.item(index);
if (element) {
result = [index, element];
}
} }
return element; return result;
} }
export function insert( export function insert(
element: Element, element: Element,
collection: Element, collection: Element,
idOrIndex: Identifier idOrIndex: Identifier
): void { ): number {
const reference = find(collection.children, idOrIndex); const match = find(collection.children, idOrIndex);
if (reference) { if (match) {
collection.insertBefore(element, reference); const [index, reference] = match;
collection.insertBefore(element, reference[0]);
return index;
} }
return -1;
} }
export function add( export function add(
element: Element, element: Element,
collection: Element, collection: Element,
idOrIndex: Identifier idOrIndex: Identifier
): void { ): number {
const before = find(collection.children, idOrIndex); const match = find(collection.children, idOrIndex);
if (before) { if (match) {
const [index, before] = match;
const reference = before.nextElementSibling ?? null; const reference = before.nextElementSibling ?? null;
collection.insertBefore(element, reference); collection.insertBefore(element, reference);
return index + 1;
} }
return -1;
} }
export function update( export function update(
f: (element: Element) => void, f: (element: Element) => void,
collection: Element, collection: Element,
idOrIndex: Identifier idOrIndex: Identifier
): void { ): number {
const element = find(collection.children, idOrIndex); const match = find(collection.children, idOrIndex);
if (element) { if (match) {
f(element); const [index, element] = match;
f(element[0]);
return index;
} }
return -1;
} }

View file

@ -5,8 +5,8 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<script lang="typescript"> <script lang="typescript">
import * as tr from "lib/i18n"; import * as tr from "lib/i18n";
import IconButton from "components/IconButton.svelte";
import ButtonGroup from "components/ButtonGroup.svelte"; import ButtonGroup from "components/ButtonGroup.svelte";
import IconButton from "components/IconButton.svelte";
import WithState from "components/WithState.svelte"; import WithState from "components/WithState.svelte";
import WithShortcut from "components/WithShortcut.svelte"; import WithShortcut from "components/WithShortcut.svelte";

View file

@ -7,20 +7,24 @@ 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 ButtonGroup from "components/ButtonGroup.svelte"; import ButtonGroup from "components/ButtonGroup.svelte";
import ButtonGroupItem from "components/ButtonGroupItem.svelte";
import LabelButton from "components/LabelButton.svelte"; import LabelButton from "components/LabelButton.svelte";
import WithShortcut from "components/WithShortcut.svelte"; import WithShortcut from "components/WithShortcut.svelte";
export let api = {}; export let api = {};
</script> </script>
<ButtonGroup id="notetype" class="" {api}> <ButtonGroup id="notetype" {api}>
<ButtonGroupItem>
<LabelButton <LabelButton
disables={false} disables={false}
tooltip={tr.editingCustomizeFields()} tooltip={tr.editingCustomizeFields()}
on:click={() => bridgeCommand('fields')}> on:click={() => bridgeCommand('fields')}>
{tr.editingFields()}... {tr.editingFields()}...
</LabelButton> </LabelButton>
</ButtonGroupItem>
<ButtonGroupItem>
<WithShortcut shortcut="Control+KeyL" let:createShortcut let:shortcutLabel> <WithShortcut shortcut="Control+KeyL" let:createShortcut let:shortcutLabel>
<LabelButton <LabelButton
disables={false} disables={false}
@ -30,4 +34,5 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
{tr.editingCards()}... {tr.editingCards()}...
</LabelButton> </LabelButton>
</WithShortcut> </WithShortcut>
</ButtonGroupItem>
</ButtonGroup> </ButtonGroup>