TirayaFrontend/src/lib/components/contextmenu/TestCtxMenu2.svelte
2024-05-23 16:50:36 +02:00

134 lines
3.4 KiB
Svelte

<script lang="ts">
import { TEST_MENU } from "./menus";
import { type ContentAction } from "svelte-floating-ui";
import TestCtxMenu2Item from "./TestCtxMenu2Item.svelte";
import { kbd } from "@melt-ui/svelte/internal/helpers";
import { writable, type Writable } from "svelte/store";
import outclick from "$lib/util/actions/outclick";
import { LIST_PAGENAV_N } from "$lib/util/constants";
import { clamp } from "$lib/util/functions";
export let menu = TEST_MENU;
export let menuOpen = false;
export let floatingContent: ContentAction | (() => void) = () => {};
export let mainMenuClose: (() => void) | undefined = undefined;
export let submenuClose: (() => void) | undefined = undefined;
let menuElements: TestCtxMenu2Item[] = [];
let openSubmenu: Writable<string | null> = writable(null);
export function open() {
menuOpen = true;
}
export function close() {
menuOpen = false;
}
export function toggle() {
if (menuOpen) close();
else open();
}
export function focusFirst() {
if (menuElements.length > 0 && getCurrentIndex() === -1) menuElements[0].focus();
}
function getCurrentIndex(): number {
return menuElements.findIndex((elm) => elm.getIsActive());
}
function onKeydown(e: KeyboardEvent) {
if (handleMenuNavigation(e)) {
e.stopPropagation();
}
}
// https://github.com/melt-ui/melt-ui/blob/develop/src/lib/builders/menu/create.ts
function handleMenuNavigation(e: KeyboardEvent): boolean {
e.preventDefault();
// Index of the currently focused item in the candidate nodes array
const currentIndex = getCurrentIndex();
if (currentIndex === -1) return false;
// Calculate the index of the next menu item
let nextIndex = currentIndex;
switch (e.key) {
case kbd.ARROW_DOWN:
if (e.ctrlKey) nextIndex = menuElements.length - 1;
else nextIndex++;
break;
case kbd.ARROW_UP:
if (e.ctrlKey) nextIndex = 0;
else nextIndex--;
break;
case kbd.HOME:
nextIndex = 0;
break;
case kbd.END:
nextIndex = menuElements.length - 1;
break;
case kbd.PAGE_DOWN:
nextIndex += LIST_PAGENAV_N;
break;
case kbd.PAGE_UP:
nextIndex -= LIST_PAGENAV_N;
break;
case kbd.ARROW_RIGHT:
if (menu[currentIndex].submenu) {
menuElements[currentIndex].focus(true, true);
}
return true;
case kbd.ARROW_LEFT:
if (submenuClose) {
submenuClose();
return true;
}
return false;
default:
return false;
}
nextIndex = clamp(nextIndex, 0, menuElements.length - 1);
menuElements[nextIndex].focus(false);
return true;
}
function onOutclick() {
console.log("outclick");
menuOpen = false;
openSubmenu.set(null);
}
const condOutclick = submenuClose ? () => {} : outclick;
</script>
<svelte:window
on:keydown={(e) => {
if (e.key === kbd.ESCAPE) {
onOutclick();
}
}}
/>
{#if menuOpen}
<div
class="ctxmenu"
role="menu"
use:floatingContent
tabindex={submenuClose ? -1 : 0}
on:keydown={onKeydown}
use:condOutclick
on:outclick={onOutclick}
>
{#each menu as menuItem, i}
<TestCtxMenu2Item
{menuItem}
{openSubmenu}
mainMenuClose={mainMenuClose ?? close}
bind:this={menuElements[i]}
/>
{/each}
</div>
{/if}