mirror of
https://github.com/ankitects/anki.git
synced 2025-12-20 10:22:57 -05:00
Add hiding functionality in ButtonGroup
This commit is contained in:
parent
26f85a0f9d
commit
e1cc22b9ee
5 changed files with 113 additions and 45 deletions
|
|
@ -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}
|
||||||
<svelte:component this={component} />
|
<ButtonGroupItem registration={dynamicItems[i]}>
|
||||||
|
<svelte:component this={component} />
|
||||||
|
</ButtonGroupItem>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -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}>
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,27 +7,32 @@ 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}>
|
||||||
<LabelButton
|
<ButtonGroupItem>
|
||||||
disables={false}
|
|
||||||
tooltip={tr.editingCustomizeFields()}
|
|
||||||
on:click={() => bridgeCommand('fields')}>
|
|
||||||
{tr.editingFields()}...
|
|
||||||
</LabelButton>
|
|
||||||
|
|
||||||
<WithShortcut shortcut="Control+KeyL" let:createShortcut let:shortcutLabel>
|
|
||||||
<LabelButton
|
<LabelButton
|
||||||
disables={false}
|
disables={false}
|
||||||
tooltip={`${tr.editingCustomizeCardTemplates()} (${shortcutLabel})`}
|
tooltip={tr.editingCustomizeFields()}
|
||||||
on:click={() => bridgeCommand('cards')}
|
on:click={() => bridgeCommand('fields')}>
|
||||||
on:mount={createShortcut}>
|
{tr.editingFields()}...
|
||||||
{tr.editingCards()}...
|
|
||||||
</LabelButton>
|
</LabelButton>
|
||||||
</WithShortcut>
|
</ButtonGroupItem>
|
||||||
|
|
||||||
|
<ButtonGroupItem>
|
||||||
|
<WithShortcut shortcut="Control+KeyL" let:createShortcut let:shortcutLabel>
|
||||||
|
<LabelButton
|
||||||
|
disables={false}
|
||||||
|
tooltip={`${tr.editingCustomizeCardTemplates()} (${shortcutLabel})`}
|
||||||
|
on:click={() => bridgeCommand('cards')}
|
||||||
|
on:mount={createShortcut}>
|
||||||
|
{tr.editingCards()}...
|
||||||
|
</LabelButton>
|
||||||
|
</WithShortcut>
|
||||||
|
</ButtonGroupItem>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue