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