134 lines
3.4 KiB
Svelte
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}
|