atagen

malice

public
Star0
Watch0
d8772d4eb40883257a65e37f7a80a044e1d015f9
2
ssh://brewery@pls.lobotomise.me:33/atagen/malice.git

Blame: web/src/lib/ContextMenu.svelte

d8624c214c04atagen2026-10-24 08:24 UTC1
<script lang="ts" context="module">
d8624c214c04atagen2026-10-24 08:24 UTC2
  export interface MenuItem {
d8624c214c04atagen2026-10-24 08:24 UTC3
    label: string;
d8624c214c04atagen2026-10-24 08:24 UTC4
    action: () => void;
d8624c214c04atagen2026-10-24 08:24 UTC5
    danger?: boolean;
d8624c214c04atagen2026-10-24 08:24 UTC6
    disabled?: boolean;
05a48e9e7942atagen2026-10-24 09:32 UTC7
    divider?: boolean;
d8624c214c04atagen2026-10-24 08:24 UTC8
  }
d8624c214c04atagen2026-10-24 08:24 UTC9
</script>
d8624c214c04atagen2026-10-24 08:24 UTC10
d8624c214c04atagen2026-10-24 08:24 UTC11
<script lang="ts">
d8624c214c04atagen2026-10-24 08:24 UTC12
  import { onMount, tick } from 'svelte';
445bf199a9cfatagen2026-10-25 02:56 UTC13
  import EmojiText from './EmojiText.svelte';
d8624c214c04atagen2026-10-24 08:24 UTC14
d8624c214c04atagen2026-10-24 08:24 UTC15
  export let items: MenuItem[];
d8624c214c04atagen2026-10-24 08:24 UTC16
  export let x: number;
d8624c214c04atagen2026-10-24 08:24 UTC17
  export let y: number;
d8624c214c04atagen2026-10-24 08:24 UTC18
  export let onClose: () => void;
05a48e9e7942atagen2026-10-24 09:32 UTC19
  export let above = false;
d8624c214c04atagen2026-10-24 08:24 UTC20
d8624c214c04atagen2026-10-24 08:24 UTC21
  const MARGIN = 8;
d8624c214c04atagen2026-10-24 08:24 UTC22
d8624c214c04atagen2026-10-24 08:24 UTC23
  let menu: HTMLDivElement;
d8624c214c04atagen2026-10-24 08:24 UTC24
  let left = x;
d8624c214c04atagen2026-10-24 08:24 UTC25
  let top = y;
d8624c214c04atagen2026-10-24 08:24 UTC26
d8624c214c04atagen2026-10-24 08:24 UTC27
  onMount(async () => {
d8624c214c04atagen2026-10-24 08:24 UTC28
    await tick();
d8624c214c04atagen2026-10-24 08:24 UTC29
    const rect = menu.getBoundingClientRect();
d8624c214c04atagen2026-10-24 08:24 UTC30
    left = Math.max(MARGIN, Math.min(x, window.innerWidth - rect.width - MARGIN));
05a48e9e7942atagen2026-10-24 09:32 UTC31
    top = Math.max(MARGIN, Math.min(above ? y - rect.height : y, window.innerHeight - rect.height - MARGIN));
d8624c214c04atagen2026-10-24 08:24 UTC32
    menu.querySelector<HTMLButtonElement>('button:not(:disabled)')?.focus();
d8624c214c04atagen2026-10-24 08:24 UTC33
  });
d8624c214c04atagen2026-10-24 08:24 UTC34
d8624c214c04atagen2026-10-24 08:24 UTC35
  function pick(item: MenuItem): void {
d8624c214c04atagen2026-10-24 08:24 UTC36
    onClose();
d8624c214c04atagen2026-10-24 08:24 UTC37
    item.action();
d8624c214c04atagen2026-10-24 08:24 UTC38
  }
d8624c214c04atagen2026-10-24 08:24 UTC39
d8624c214c04atagen2026-10-24 08:24 UTC40
  function outside(ev: PointerEvent): void {
d8624c214c04atagen2026-10-24 08:24 UTC41
    if (!menu.contains(ev.target as Node)) onClose();
d8624c214c04atagen2026-10-24 08:24 UTC42
  }
d8624c214c04atagen2026-10-24 08:24 UTC43
d8624c214c04atagen2026-10-24 08:24 UTC44
  function key(ev: KeyboardEvent): void {
d8624c214c04atagen2026-10-24 08:24 UTC45
    if (ev.key === 'Escape') {
d8624c214c04atagen2026-10-24 08:24 UTC46
      ev.stopPropagation();
d8624c214c04atagen2026-10-24 08:24 UTC47
      onClose();
d8624c214c04atagen2026-10-24 08:24 UTC48
    }
d8624c214c04atagen2026-10-24 08:24 UTC49
  }
d8624c214c04atagen2026-10-24 08:24 UTC50
</script>
d8624c214c04atagen2026-10-24 08:24 UTC51
d8624c214c04atagen2026-10-24 08:24 UTC52
<svelte:window on:pointerdown|capture={outside} on:keydown|capture={key} on:resize={onClose} on:blur={onClose} />
d8624c214c04atagen2026-10-24 08:24 UTC53
d8624c214c04atagen2026-10-24 08:24 UTC54
<div class="context-menu" role="menu" style={`left:${left}px;top:${top}px`} bind:this={menu}>
d8624c214c04atagen2026-10-24 08:24 UTC55
  {#each items as item}
05a48e9e7942atagen2026-10-24 09:32 UTC56
    {#if item.divider}<hr />{/if}
d8624c214c04atagen2026-10-24 08:24 UTC57
    <button type="button" role="menuitem" class:danger={item.danger} disabled={item.disabled} on:click={() => pick(item)}>
445bf199a9cfatagen2026-10-25 02:56 UTC58
      <EmojiText text={item.label} />
d8624c214c04atagen2026-10-24 08:24 UTC59
    </button>
d8624c214c04atagen2026-10-24 08:24 UTC60
  {/each}
d8624c214c04atagen2026-10-24 08:24 UTC61
</div>
d8624c214c04atagen2026-10-24 08:24 UTC62
d8624c214c04atagen2026-10-24 08:24 UTC63
<style>
d8624c214c04atagen2026-10-24 08:24 UTC64
  .context-menu {
05a48e9e7942atagen2026-10-24 09:32 UTC65
    animation: pop-in var(--pop);
d8624c214c04atagen2026-10-24 08:24 UTC66
    position: fixed;
d8624c214c04atagen2026-10-24 08:24 UTC67
    z-index: 90;
d8624c214c04atagen2026-10-24 08:24 UTC68
    min-width: 180px;
d8624c214c04atagen2026-10-24 08:24 UTC69
    display: flex;
d8624c214c04atagen2026-10-24 08:24 UTC70
    flex-direction: column;
d8624c214c04atagen2026-10-24 08:24 UTC71
    padding: 4px;
d8624c214c04atagen2026-10-24 08:24 UTC72
    background: var(--panel-2);
d8624c214c04atagen2026-10-24 08:24 UTC73
    border: 1px solid var(--border);
05a48e9e7942atagen2026-10-24 09:32 UTC74
    border-radius: var(--r-md);
d8624c214c04atagen2026-10-24 08:24 UTC75
    box-shadow: 0 8px 24px rgb(0 0 0 / 0.35);
d8624c214c04atagen2026-10-24 08:24 UTC76
  }
d8624c214c04atagen2026-10-24 08:24 UTC77
  button {
d8624c214c04atagen2026-10-24 08:24 UTC78
    background: transparent;
d8624c214c04atagen2026-10-24 08:24 UTC79
    border: none;
05a48e9e7942atagen2026-10-24 09:32 UTC80
    border-radius: var(--r-sm);
d8624c214c04atagen2026-10-24 08:24 UTC81
    color: var(--text);
d8624c214c04atagen2026-10-24 08:24 UTC82
    font: inherit;
d8624c214c04atagen2026-10-24 08:24 UTC83
    font-size: 13.5px;
d8624c214c04atagen2026-10-24 08:24 UTC84
    text-align: left;
d8624c214c04atagen2026-10-24 08:24 UTC85
    padding: 7px 10px;
d8624c214c04atagen2026-10-24 08:24 UTC86
    cursor: pointer;
d8624c214c04atagen2026-10-24 08:24 UTC87
  }
d8624c214c04atagen2026-10-24 08:24 UTC88
  button:hover:not(:disabled), button:focus-visible { background: var(--hover); outline: none; }
d8624c214c04atagen2026-10-24 08:24 UTC89
  button:disabled { opacity: 0.5; cursor: default; }
d8624c214c04atagen2026-10-24 08:24 UTC90
  .danger { color: var(--danger); }
05a48e9e7942atagen2026-10-24 09:32 UTC91
  hr { border: none; border-top: 1px solid var(--border); margin: 4px 2px; }
d8624c214c04atagen2026-10-24 08:24 UTC92
</style>