| 1 | <script lang="ts"> |
| 2 | import { onMount } from "svelte"; |
| 3 | import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow"; |
| 4 | const appWindow = getCurrentWebviewWindow() |
| 5 | |
| 6 | export let items = []; |
| 7 | export let target: HTMLElement = null; |
| 8 | |
| 9 | export let menuOpen = false; |
| 10 | |
| 11 | let cursorPos = { x: 0, y: 0 } |
| 12 | let menuPos = { h: 0, w: 0 } |
| 13 | let windowSize = { h: 0, w: 0 } // keep track of window borders |
| 14 | |
| 15 | onMount(() => { |
| 16 | return () => { |
| 17 | if (target !== null) { |
| 18 | target.removeEventListener("contextmenu", openContextMenu) |
| 19 | } |
| 20 | } |
| 21 | }) |
| 22 | |
| 23 | async function openContextMenu(e) { |
| 24 | const appWindowSize = await appWindow.innerSize(); |
| 25 | let windowWidth = appWindowSize.width; |
| 26 | let windowHeight = appWindowSize.height; |
| 27 | |
| 28 | menuOpen = true; |
| 29 | windowSize = {w: windowWidth, h: windowHeight}; |
| 30 | cursorPos = {x: e.clientX, y: e.clientY}; |
| 31 | |
| 32 | // Adjust context menu position based on where it is on the window |
| 33 | // If it overlaps with the window border then move it to the right/left/top/bottom accordingly |
| 34 | if (windowSize.h - cursorPos.y < menuPos.h) { |
| 35 | cursorPos.y = cursorPos.y - menuPos.h |
| 36 | } |
| 37 | if (windowSize.w - cursorPos.x < menuPos.w) { |
| 38 | cursorPos.x = cursorPos.x - menuPos.w |
| 39 | } |
| 40 | } |
| 41 | |
| 42 | function getContextMenuDimension(node){ |
| 43 | // This function will get context menu dimension |
| 44 | // when navigation is shown |
| 45 | let height = node.offsetHeight; |
| 46 | let width = node.offsetWidth; |
| 47 | menuPos = { |
| 48 | w: width, |
| 49 | h: height |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | $: if (target !== null) { |
| 54 | target.addEventListener("contextmenu", openContextMenu) |
| 55 | } |
| 56 | </script> |
| 57 | |
| 58 | <svelte:window on:contextmenu|preventDefault on:click={(e) => { |
| 59 | menuOpen = false; |
| 60 | }} on:mouseup={(e) => { |
| 61 | if (e.button === 2) { |
| 62 | menuOpen = false; |
| 63 | } |
| 64 | }}></svelte:window> |
| 65 | |
| 66 | {#if menuOpen} |
| 67 | <div use:getContextMenuDimension class="context-menu" style="top:{cursorPos.y}px; left:{cursorPos.x}px"> |
| 68 | {#each items as item} |
| 69 | <!-- svelte-ignore a11y-click-events-have-key-events --> |
| 70 | <div class="context-menu-option" title="" class:disabled={item.disabled} on:click={() => { |
| 71 | if (!item.disabled) { |
| 72 | item.action(); |
| 73 | } |
| 74 | }}> |
| 75 | <span class="option-name">{item.name}</span> |
| 76 | {#if item.shortcut} |
| 77 | <span class="option-shortcut">{item.shortcut}</span> |
| 78 | {/if} |
| 79 | </div> |
| 80 | {/each} |
| 81 | </div> |
| 82 | {/if} |
| 83 | |
| 84 | <style lang="scss"> |
| 85 | .context-menu { |
| 86 | min-width: 10rem; |
| 87 | max-width: 18rem; |
| 88 | padding: 0.25rem; |
| 89 | z-index: 9999; |
| 90 | position: absolute; |
| 91 | border-radius: 3px; |
| 92 | } |
| 93 | .context-menu-option { |
| 94 | display: flex; |
| 95 | align-items: center; |
| 96 | justify-content: space-between; |
| 97 | height: 17px; |
| 98 | padding: 6px 0; |
| 99 | width: 100%; |
| 100 | font-size: 0.875rem; |
| 101 | cursor: pointer; |
| 102 | } |
| 103 | .option-name, .option-shortcut { |
| 104 | padding: 0 10px; |
| 105 | } |
| 106 | .option-shortcut { |
| 107 | margin-left: 10px; |
| 108 | } |
| 109 | .disabled { |
| 110 | cursor: not-allowed; |
| 111 | } |
| 112 | </style> |