Context menu thing
This commit is contained in:
47
src/lib/components/menu/ContextMenu.svelte
Normal file
47
src/lib/components/menu/ContextMenu.svelte
Normal file
@@ -0,0 +1,47 @@
|
||||
<script>
|
||||
import Menu from './Menu.svelte';
|
||||
import MenuOption from './MenuOption.svelte';
|
||||
import MenuDivider from './MenuDivider.svelte';
|
||||
import { tick } from 'svelte'
|
||||
|
||||
import Icon from './Icon.svelte'
|
||||
|
||||
let pos = { x: 0, y: 0 };
|
||||
let showMenu = false;
|
||||
|
||||
async function onRightClick(e) {
|
||||
if (showMenu) {
|
||||
showMenu = false;
|
||||
await new Promise(res => setTimeout(res, 100));
|
||||
}
|
||||
|
||||
pos = { x: e.clientX, y: e.clientY };
|
||||
showMenu = true;
|
||||
}
|
||||
|
||||
function closeMenu() {
|
||||
showMenu = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if showMenu}
|
||||
<Menu {...pos} on:click={closeMenu} on:clickoutside={closeMenu}>
|
||||
<MenuOption
|
||||
on:click={console.log}
|
||||
text="Do nothing" />
|
||||
<MenuOption
|
||||
on:click={console.log}
|
||||
text="Do nothing, but twice" />
|
||||
<MenuDivider />
|
||||
<MenuOption
|
||||
isDisabled={true}
|
||||
on:click={console.log}
|
||||
text="Whoops, disabled!" />
|
||||
<MenuOption on:click={console.log}>
|
||||
<Icon />
|
||||
<span>Look! An icon!</span>
|
||||
</MenuOption>
|
||||
</Menu>
|
||||
{/if}
|
||||
|
||||
<svelte:body on:contextmenu|preventDefault={onRightClick} />
|
||||
3
src/lib/components/menu/Icon.svelte
Normal file
3
src/lib/components/menu/Icon.svelte
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M11.2584 13.039C10.3201 13.647 9.2013 14 8 14C4.68629 14 2 11.3137 2 8C2 6.66837 2.4338 5.43806 3.1678 4.44268L11.2584 13.039ZM12.7332 11.6878L4.60537 3.05194C5.57075 2.38838 6.74002 2 8 2C11.3137 2 14 4.68629 14 8C14 9.39043 13.527 10.6704 12.7332 11.6878ZM16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 548 B |
45
src/lib/components/menu/Menu.svelte
Normal file
45
src/lib/components/menu/Menu.svelte
Normal file
@@ -0,0 +1,45 @@
|
||||
<script>
|
||||
import { setContext, createEventDispatcher } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { key } from './menu.ts';
|
||||
|
||||
export let x;
|
||||
export let y;
|
||||
|
||||
// whenever x and y is changed, restrict box to be within bounds
|
||||
$: (() => {
|
||||
if (!menuEl) return;
|
||||
|
||||
const rect = menuEl.getBoundingClientRect();
|
||||
x = Math.min(window.innerWidth - rect.width, x);
|
||||
if (y > window.innerHeight - rect.height) y -= rect.height;
|
||||
})(x, y);
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
setContext(key, {
|
||||
dispatchClick: () => dispatch('click')
|
||||
});
|
||||
|
||||
let menuEl;
|
||||
function onPageClick(e) {
|
||||
if (e.target === menuEl || menuEl.contains(e.target)) return;
|
||||
dispatch('clickoutside');
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:body on:click={onPageClick} />
|
||||
<div class="menu" bind:this={menuEl} style="top: {y}px; left: {x}px;">
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.menu {
|
||||
z-index: var(--layer-top);
|
||||
position: absolute;
|
||||
display: grid;
|
||||
border: 1px solid #0003;
|
||||
box-shadow: 2px 2px 5px 0px #0002;
|
||||
background: white;
|
||||
}
|
||||
</style>
|
||||
9
src/lib/components/menu/MenuDivider.svelte
Normal file
9
src/lib/components/menu/MenuDivider.svelte
Normal file
@@ -0,0 +1,9 @@
|
||||
<hr />
|
||||
|
||||
<style>
|
||||
hr {
|
||||
border-top: 1px solid #0003;
|
||||
width: 100%;
|
||||
margin: 2px 0;
|
||||
}
|
||||
</style>
|
||||
50
src/lib/components/menu/MenuOption.svelte
Normal file
50
src/lib/components/menu/MenuOption.svelte
Normal file
@@ -0,0 +1,50 @@
|
||||
<script>
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { key } from './menu.ts';
|
||||
|
||||
export let isDisabled = false;
|
||||
export let text = '';
|
||||
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
const { dispatchClick } = getContext(key);
|
||||
|
||||
const handleClick = e => {
|
||||
if (isDisabled) return;
|
||||
|
||||
dispatch('click');
|
||||
dispatchClick();
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class:disabled={isDisabled}
|
||||
on:click={handleClick}
|
||||
>
|
||||
{#if text}
|
||||
{text}
|
||||
{:else}
|
||||
<slot />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
padding: 4px 15px;
|
||||
cursor: default;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
grid-gap: 5px;
|
||||
}
|
||||
div:hover {
|
||||
background: #0002;
|
||||
}
|
||||
div.disabled {
|
||||
color: #0006;
|
||||
}
|
||||
div.disabled:hover {
|
||||
background: white;
|
||||
}
|
||||
</style>
|
||||
3
src/lib/components/menu/menu.ts
Normal file
3
src/lib/components/menu/menu.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
const key = {};
|
||||
|
||||
export { key };
|
||||
Reference in New Issue
Block a user