Make it so you can include DropdownMenus on all kinds of buttons
This commit is contained in:
parent
5eb07d3fc7
commit
391f64f648
@ -1,10 +1,6 @@
|
||||
<script lang="typescript" context="module">
|
||||
export type Buttons =
|
||||
| { component: SvelteComponent; [...arg: string]: unknown }
|
||||
| Buttons[];
|
||||
</script>
|
||||
|
||||
<script lang="typescript">
|
||||
import type { Buttons } from "./types";
|
||||
|
||||
export let buttons: Buttons;
|
||||
</script>
|
||||
|
||||
|
@ -1,15 +1,14 @@
|
||||
<script lang="typescript">
|
||||
export let className: string;
|
||||
export let id = "";
|
||||
export let className = "";
|
||||
export let props: Record<string, string> = {};
|
||||
|
||||
export let onChange: (event: ChangeEvent) => void;
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
button {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
padding: 0;
|
||||
|
||||
&:hover {
|
||||
@ -24,19 +23,23 @@
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 28px;
|
||||
height: calc(28px - 4px);
|
||||
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button tabindex="-1" on:mousedown|preventDefault>
|
||||
<span class={className}> <input type="color" on:change={onChange} /> </span>
|
||||
<button tabindex="-1" {id} class={className} {...props} on:mousedown|preventDefault>
|
||||
<span> <input type="color" on:change={onChange} /> </span>
|
||||
</button>
|
||||
|
@ -34,9 +34,12 @@
|
||||
</script>
|
||||
|
||||
<script lang="typescript">
|
||||
import InnerButton from "./InnerButton.svelte";
|
||||
import SquareButton from "./SquareButton.svelte";
|
||||
|
||||
export let id = "";
|
||||
export let className = "";
|
||||
export let props: Record<string, string> = {};
|
||||
|
||||
export let icon = "";
|
||||
export let command: string;
|
||||
export let activatable = true;
|
||||
@ -57,6 +60,6 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<InnerButton {className} {active} {onClick}>
|
||||
<SquareButton {id} {className} {props} {active} {onClick}>
|
||||
{@html icon}
|
||||
</InnerButton>
|
||||
</SquareButton>
|
||||
|
@ -1,25 +0,0 @@
|
||||
<script lang="typescript">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
export let buttonId = `dropdownMenuButton123`;
|
||||
|
||||
// ${Math.random().toString(36).substring(2)}`
|
||||
let dropdownButtonRef: HTMLButtonElement;
|
||||
|
||||
onMount(() => {
|
||||
/* Prevent focus on menu activation */
|
||||
const noop = () => {};
|
||||
Object.defineProperty(dropdownButtonRef, 'focus', { value: noop });
|
||||
|
||||
/* Set custom menu without using .dropdown
|
||||
* Rendering the menu here would cause the menu to
|
||||
* be displayed outside of the visible area
|
||||
*/
|
||||
const dropdown = new bootstrap.Dropdown(dropdownButtonRef);
|
||||
dropdown._menu = document.getElementById(buttonId);
|
||||
})
|
||||
</script>
|
||||
|
||||
<button bind:this={dropdownButtonRef} class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" on:mousedown|preventDefault>
|
||||
Dropdown button
|
||||
</button>
|
40
ts/editor-toolbar/DropdownMenu.svelte
Normal file
40
ts/editor-toolbar/DropdownMenu.svelte
Normal file
@ -0,0 +1,40 @@
|
||||
<script lang="typescript">
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import type { ButtonDefinition } from "./types";
|
||||
|
||||
export let button: ButtonDefinition;
|
||||
export let menuId: string;
|
||||
|
||||
function extend({ className, props, ...rest }: ButtonDefinition): ButtonDefinition {
|
||||
return {
|
||||
className: `${className} dropdown-toggle`,
|
||||
props: {
|
||||
"data-bs-toggle": "dropdown",
|
||||
"aria-expanded": "false",
|
||||
...props,
|
||||
},
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
|
||||
function createDropdown({ detail }: CustomEvent): void {
|
||||
const button: HTMLButtonElement = detail.button;
|
||||
|
||||
/* Prevent focus on menu activation */
|
||||
const noop = () => {};
|
||||
Object.defineProperty(button, "focus", { value: noop });
|
||||
|
||||
/* Set custom menu without using .dropdown
|
||||
* Rendering the menu here would cause the menu to
|
||||
* be displayed outside of the visible area
|
||||
*/
|
||||
const dropdown = new bootstrap.Dropdown(button);
|
||||
dropdown._menu = button.getRootNode().getElementById(menuId);
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:component
|
||||
this={button.component}
|
||||
{...extend(button)}
|
||||
on:mount={createDropdown} />
|
@ -5,7 +5,7 @@
|
||||
import { disabledKey, nightModeKey } from "./contextKeys";
|
||||
|
||||
import ButtonGroup from "./ButtonGroup.svelte";
|
||||
import type { Buttons } from "./ButtonGroup.svelte";
|
||||
import type { Buttons } from "./types";
|
||||
|
||||
export let buttons: Buttons = [];
|
||||
export let nightMode: boolean;
|
||||
@ -25,7 +25,7 @@
|
||||
background: var(--bg-color);
|
||||
border-bottom: 1px solid var(--border);
|
||||
|
||||
/* Remove most outer marigns */
|
||||
/* Remove outermost marigns */
|
||||
& > :global(ul) {
|
||||
& > :global(li:nth-child(1)) {
|
||||
margin-left: 0;
|
||||
|
@ -1,11 +1,14 @@
|
||||
<script lang="typescript">
|
||||
import InnerButton from "./InnerButton.svelte";
|
||||
import SquareButton from "./SquareButton.svelte";
|
||||
|
||||
export let id = "";
|
||||
export let className = "";
|
||||
export let props: Record<string, string> = {};
|
||||
|
||||
export let icon = "";
|
||||
export let onClick: (event: ClickEvent) => void;
|
||||
</script>
|
||||
|
||||
<InnerButton {className} {onClick}>
|
||||
<SquareButton {id} {className} {props} {onClick}>
|
||||
{@html icon}
|
||||
</InnerButton>
|
||||
</SquareButton>
|
||||
|
@ -1,5 +1,27 @@
|
||||
<script lang="typescript">
|
||||
import { onMount, createEventDispatcher, getContext } from "svelte";
|
||||
import type { Readable } from "svelte/store";
|
||||
import { disabledKey } from "./contextKeys";
|
||||
|
||||
export let id = "";
|
||||
export let className = "";
|
||||
export let props: Record<string, string> = {};
|
||||
|
||||
export let label: string;
|
||||
export let onClick: (event: ClickEvent) => void;
|
||||
|
||||
let buttonRef: HTMLButtonElement;
|
||||
|
||||
function extendClassName(className: string): string {
|
||||
return `${className} btn btn-secondary`;
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
onMount(() => dispatch("mount", { button: buttonRef }));
|
||||
|
||||
const disabledStore = getContext(disabledKey);
|
||||
$: disabled = $disabledStore;
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@ -7,15 +29,34 @@
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: auto;
|
||||
height: 26px;
|
||||
height: calc(28px + 2px);
|
||||
|
||||
padding: 0 10px;
|
||||
margin: 0 3px;
|
||||
|
||||
border-radius: 0;
|
||||
border-color: var(--faint-border);
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 12px 4px rgb(255 255 255 / 0.5);
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
|
||||
border-color: var(--faint-border);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<button class="btn btn-secondary" tabindex="-1" on:click> {label} </button>
|
||||
<button
|
||||
bind:this={buttonRef}
|
||||
{disabled}
|
||||
{id}
|
||||
class={extendClassName(className)}
|
||||
{...props}
|
||||
tabindex="-1"
|
||||
on:click={onClick}
|
||||
on:mousedown|preventDefault>
|
||||
{label}
|
||||
</button>
|
||||
|
@ -3,31 +3,24 @@
|
||||
import type { Readable } from "svelte/store";
|
||||
import { disabledKey } from "./contextKeys";
|
||||
|
||||
export let className: string = "";
|
||||
export let id = "";
|
||||
export let className = "";
|
||||
export let props: Record<string, string> = {};
|
||||
|
||||
export let onClick: (event: ClickEvent) => void;
|
||||
export let active = false;
|
||||
|
||||
const disabledStore = getContext(disabledKey)
|
||||
const disabledStore = getContext(disabledKey);
|
||||
$: disabled = $disabledStore;
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
button {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
|
||||
background-color: white;
|
||||
|
||||
& > :global(svg),
|
||||
& > :global(img) {
|
||||
fill: currentColor;
|
||||
vertical-align: unset;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
@ -57,14 +50,38 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
/* constrain icon */
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
& > :global(svg),
|
||||
& > :global(img) {
|
||||
fill: currentColor;
|
||||
vertical-align: unset;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* for DropdownMenu */
|
||||
:global(.dropdown-toggle)::after {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button
|
||||
class="p-1 {className}"
|
||||
{id}
|
||||
class={className}
|
||||
{...props}
|
||||
class:active
|
||||
tabindex="-1"
|
||||
{disabled}
|
||||
on:click={onClick}
|
||||
on:mousedown|preventDefault>
|
||||
<slot />
|
||||
<span class="p-1"><slot /></span>
|
||||
</button>
|
@ -7,11 +7,11 @@
|
||||
}
|
||||
|
||||
.rainbow {
|
||||
background: linear-gradient(217deg, rgba(255, 0, 0, 0.8), rgba(255, 0, 0, 0) 70.71%),
|
||||
linear-gradient(127deg, rgba(0, 255, 0, 0.8), rgba(0, 255, 0, 0) 70.71%),
|
||||
linear-gradient(336deg, rgba(0, 0, 255, 0.8), rgba(0, 0, 255, 0) 70.71%);
|
||||
|
||||
background-clip: content-box;
|
||||
/* Boostrap .rounded has .25rem */
|
||||
border-radius: 0.375rem;
|
||||
background: content-box
|
||||
linear-gradient(217deg, rgba(255, 0, 0, 0.8), rgba(255, 0, 0, 0) 70.71%),
|
||||
content-box
|
||||
linear-gradient(127deg, rgba(0, 255, 0, 0.8), rgba(0, 255, 0, 0) 70.71%),
|
||||
content-box
|
||||
linear-gradient(336deg, rgba(0, 0, 255, 0.8), rgba(0, 0, 255, 0) 70.71%),
|
||||
border-box white;
|
||||
}
|
||||
|
@ -20,12 +20,12 @@ function wrapWithForecolor(color: string): void {
|
||||
export const forecolorButton = {
|
||||
component: IconButton,
|
||||
icon: squareFillIcon,
|
||||
className: "forecolor p-1",
|
||||
className: "forecolor",
|
||||
onClick: () => wrapWithForecolor(getForecolor()),
|
||||
};
|
||||
|
||||
export const colorpickerButton = {
|
||||
component: ColorPicker,
|
||||
className: "rainbow p-1",
|
||||
className: "rainbow",
|
||||
onChange: ({ currentTarget }) => setForegroundColor(currentTarget.value),
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ import { setupI18n, ModuleName } from "anki/i18n";
|
||||
import EditorToolbarSvelte from "./EditorToolbar.svelte";
|
||||
|
||||
import LabelButton from "./LabelButton.svelte";
|
||||
import DropdownIconButton from "./DropdownIconButton.svelte";
|
||||
import DropdownMenu from "./DropdownMenu.svelte";
|
||||
|
||||
// @ts-ignore
|
||||
export { updateActiveButtons, clearActiveButtons } from "./CommandIconButton.svelte";
|
||||
@ -46,8 +46,6 @@ const defaultButtons = [
|
||||
],
|
||||
[forecolorButton, colorpickerButton],
|
||||
[attachmentButton, recordButton, clozeButton, mathjaxButton, htmlButton],
|
||||
[ { component: DropdownIconButton }],
|
||||
|
||||
];
|
||||
|
||||
class EditorToolbar extends HTMLElement {
|
||||
@ -57,16 +55,18 @@ class EditorToolbar extends HTMLElement {
|
||||
connectedCallback(): void {
|
||||
this.disabled = writable(false);
|
||||
|
||||
setupI18n({ modules: [ModuleName.STATISTICS, ModuleName.SCHEDULING] }).then(() => {
|
||||
this.component = new EditorToolbarSvelte({
|
||||
target: this,
|
||||
props: {
|
||||
buttons: defaultButtons,
|
||||
nightMode: checkNightMode(),
|
||||
disabled: this.disabled,
|
||||
},
|
||||
});
|
||||
})
|
||||
setupI18n({ modules: [ModuleName.STATISTICS, ModuleName.SCHEDULING] }).then(
|
||||
() => {
|
||||
this.component = new EditorToolbarSvelte({
|
||||
target: this,
|
||||
props: {
|
||||
buttons: defaultButtons,
|
||||
nightMode: checkNightMode(),
|
||||
disabled: this.disabled,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
enableButtons(): void {
|
||||
|
12
ts/editor-toolbar/types.ts
Normal file
12
ts/editor-toolbar/types.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { SvelteComponent } from "svelte/internal";
|
||||
|
||||
export interface ButtonDefinition {
|
||||
component: SvelteComponent;
|
||||
id?: string;
|
||||
className?: string;
|
||||
props?: Record<string, string>;
|
||||
button: HTMLButtonElement;
|
||||
[arg: string]: unknown;
|
||||
}
|
||||
|
||||
export type Buttons = ButtonDefinition | Buttons[];
|
Loading…
Reference in New Issue
Block a user