mirror of
https://github.com/ankitects/anki.git
synced 2025-09-25 01:06:35 -04:00
Replace svelte-contextmenu with a custom implementation
Keyboard navigation is to be handled.
This commit is contained in:
parent
b1771895c5
commit
01fd4df9cb
8 changed files with 138 additions and 16 deletions
|
@ -77,8 +77,7 @@
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lru-cache": "^10.2.0",
|
"lru-cache": "^10.2.0",
|
||||||
"marked": "^5.1.0",
|
"marked": "^5.1.0",
|
||||||
"mathjax": "^3.1.2",
|
"mathjax": "^3.1.2"
|
||||||
"svelte-contextmenu": "^1.0.2"
|
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"canvas": "npm:empty-npm-package@1.0.0",
|
"canvas": "npm:empty-npm-package@1.0.0",
|
||||||
|
|
82
ts/lib/context-menu/ContextMenu.svelte
Normal file
82
ts/lib/context-menu/ContextMenu.svelte
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<!-- Copyright: Ankitects Pty Ltd and contributors -->
|
||||||
|
<!-- License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { tick } from "svelte";
|
||||||
|
import type { ContextMenuMouseEvent } from "./types";
|
||||||
|
|
||||||
|
let visible = $state(false);
|
||||||
|
let x = $state(0);
|
||||||
|
let y = $state(0);
|
||||||
|
let contextMenuElement = $state<HTMLDivElement>();
|
||||||
|
|
||||||
|
const { children } = $props();
|
||||||
|
|
||||||
|
export async function show(event: ContextMenuMouseEvent) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
x = event.clientX;
|
||||||
|
y = event.clientY;
|
||||||
|
visible = true;
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
const rect = contextMenuElement!.getBoundingClientRect();
|
||||||
|
const viewportWidth = window.innerWidth;
|
||||||
|
const viewportHeight = window.innerHeight;
|
||||||
|
|
||||||
|
if (x + rect.width > viewportWidth) {
|
||||||
|
x = viewportWidth - rect.width;
|
||||||
|
}
|
||||||
|
if (y + rect.height > viewportHeight) {
|
||||||
|
y = viewportHeight - rect.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = Math.max(0, x);
|
||||||
|
y = Math.max(0, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hide() {
|
||||||
|
visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClickOutside(event: MouseEvent) {
|
||||||
|
if (
|
||||||
|
visible &&
|
||||||
|
contextMenuElement &&
|
||||||
|
!contextMenuElement.contains(event.target as Node)
|
||||||
|
) {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:document on:click={handleClickOutside} />
|
||||||
|
|
||||||
|
{#if visible}
|
||||||
|
<div
|
||||||
|
bind:this={contextMenuElement}
|
||||||
|
class="context-menu"
|
||||||
|
style="left: {x}px; top: {y}px;"
|
||||||
|
role="menu"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={hide}
|
||||||
|
onkeydown={hide}
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.context-menu {
|
||||||
|
position: fixed;
|
||||||
|
background: var(--canvas);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||||
|
padding: 4px 0;
|
||||||
|
min-width: 120px;
|
||||||
|
z-index: 1000;
|
||||||
|
font-size: 13px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
</style>
|
33
ts/lib/context-menu/Item.svelte
Normal file
33
ts/lib/context-menu/Item.svelte
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<!-- Copyright: Ankitects Pty Ltd and contributors -->
|
||||||
|
<!-- License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html -->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
const { click, children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="context-menu-item"
|
||||||
|
onclick={click}
|
||||||
|
onkeydown={click}
|
||||||
|
role="menuitem"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
{@render children?.()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.context-menu-item {
|
||||||
|
padding: 8px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--highlight-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--highlight-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
6
ts/lib/context-menu/index.ts
Normal file
6
ts/lib/context-menu/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
export { default as ContextMenu } from "./ContextMenu.svelte";
|
||||||
|
export { default as Item } from "./Item.svelte";
|
||||||
|
export type { ContextMenuAPI, ContextMenuMouseEvent } from "./types";
|
12
ts/lib/context-menu/types.ts
Normal file
12
ts/lib/context-menu/types.ts
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright: Ankitects Pty Ltd and contributors
|
||||||
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
|
|
||||||
|
export interface ContextMenuMouseEvent {
|
||||||
|
clientX: number;
|
||||||
|
clientY: number;
|
||||||
|
preventDefault(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ContextMenuAPI {
|
||||||
|
show(event: ContextMenuMouseEvent): void;
|
||||||
|
}
|
|
@ -14,7 +14,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
||||||
import LabelContainer from "./LabelContainer.svelte";
|
import LabelContainer from "./LabelContainer.svelte";
|
||||||
import LabelName from "./LabelName.svelte";
|
import LabelName from "./LabelName.svelte";
|
||||||
import { EditorState, type EditorMode } from "./types";
|
import { EditorState, type EditorMode } from "./types";
|
||||||
import ContextMenu, { Item } from "svelte-contextmenu";
|
import { ContextMenu, Item } from "$lib/context-menu";
|
||||||
|
|
||||||
export interface NoteEditorAPI {
|
export interface NoteEditorAPI {
|
||||||
fields: EditorFieldAPI[];
|
fields: EditorFieldAPI[];
|
||||||
|
@ -1226,7 +1226,7 @@ components and functionality for general note editing.
|
||||||
<ContextMenu bind:this={contextMenu}>
|
<ContextMenu bind:this={contextMenu}>
|
||||||
{#each contextMenuItems as item}
|
{#each contextMenuItems as item}
|
||||||
<Item
|
<Item
|
||||||
on:click={() => {
|
click={() => {
|
||||||
item.action();
|
item.action();
|
||||||
contextMenuInput?.focus();
|
contextMenuInput?.focus();
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
// Copyright: Ankitects Pty Ltd and contributors
|
// 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
|
||||||
|
|
||||||
|
import type { ContextMenu, ContextMenuMouseEvent } from "$lib/context-menu";
|
||||||
|
|
||||||
import { openMedia, showInMediaFolder } from "@generated/backend";
|
import { openMedia, showInMediaFolder } from "@generated/backend";
|
||||||
import * as tr from "@generated/ftl";
|
import * as tr from "@generated/ftl";
|
||||||
import { bridgeCommand } from "@tslib/bridgecommand";
|
import { bridgeCommand } from "@tslib/bridgecommand";
|
||||||
import { getSelection } from "@tslib/cross-browser";
|
import { getSelection } from "@tslib/cross-browser";
|
||||||
import type ContextMenu from "svelte-contextmenu";
|
|
||||||
import type { ContextMenuMouseEvent } from "svelte-contextmenu/ContextMenuMouseEvent";
|
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import type { EditingInputAPI } from "./EditingArea.svelte";
|
import type { EditingInputAPI } from "./EditingArea.svelte";
|
||||||
import type { NoteEditorAPI } from "./NoteEditor.svelte";
|
import type { NoteEditorAPI } from "./NoteEditor.svelte";
|
||||||
|
@ -114,8 +114,6 @@ export function setupContextMenu(): [
|
||||||
if (contextMenuItems.length > 0) {
|
if (contextMenuItems.length > 0) {
|
||||||
contextMenu?.show(event);
|
contextMenu?.show(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [onContextMenu, contextMenuItems];
|
return [onContextMenu, contextMenuItems];
|
||||||
|
|
|
@ -1761,7 +1761,6 @@ __metadata:
|
||||||
sass: "npm:<1.77"
|
sass: "npm:<1.77"
|
||||||
svelte: "npm:^5.34.9"
|
svelte: "npm:^5.34.9"
|
||||||
svelte-check: "npm:^4.2.2"
|
svelte-check: "npm:^4.2.2"
|
||||||
svelte-contextmenu: "npm:^1.0.2"
|
|
||||||
svelte-preprocess: "npm:^6.0.3"
|
svelte-preprocess: "npm:^6.0.3"
|
||||||
svelte-preprocess-esbuild: "npm:^3.0.1"
|
svelte-preprocess-esbuild: "npm:^3.0.1"
|
||||||
svgo: "npm:^3.2.0"
|
svgo: "npm:^3.2.0"
|
||||||
|
@ -6229,13 +6228,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"svelte-contextmenu@npm:^1.0.2":
|
|
||||||
version: 1.0.2
|
|
||||||
resolution: "svelte-contextmenu@npm:1.0.2"
|
|
||||||
checksum: 10c0/33cf79540337862278927f4b732b5d97f2c029348666ebcd3105b5c08bed54498a1d66f61b0212c18c204942cf54c9f6ce5ed509981d9f19f99f9334d8961cdf
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"svelte-eslint-parser@npm:^0.43.0":
|
"svelte-eslint-parser@npm:^0.43.0":
|
||||||
version: 0.43.0
|
version: 0.43.0
|
||||||
resolution: "svelte-eslint-parser@npm:0.43.0"
|
resolution: "svelte-eslint-parser@npm:0.43.0"
|
||||||
|
|
Loading…
Reference in a new issue