Context Menu

Setup

Jhana UI uses a singleton Svelte component. You have to include this once in your root layout. Then you can use the context menu system across your whole app.

/routes/+layout.svelte

<script lang="ts">
	import { CtxMenuRoot } from '@leon8ix/jhana-ui/ctx-menu';
	import { menuGlobal, menuTagMap } from '$lib/menus';
</script>

<CtxMenuRoot menu={menuGlobal} tags={menuTagMap} />

Global Menu

I like to have a single file that contains all context menus used by the app. However you can also write them specifically in those Svelte components or pages, where you actually need them.

/lib/menus.ts

import { iconPrint, iconReset, iconStore, iconUrl } from '@leon8ix/jhana-ui';
import type { CtxMenuConfig } from '@leon8ix/jhana-ui/ctx-menu';
import { app } from './app-state.svelte';
import { iconSwatch, iconSideCover, iconSideSpineBot, iconSideSpineTop } from './icons';

export const menuPrint: CtxMenuConfig = {
	anchor: 'right',
	items: [
		'Print side',
		{ text: 'Cover', icon: iconSideCover, action: () => app.print('cover') },
		{ text: 'Spine top', icon: iconSideSpineTop, action: () => app.print('spine-top') },
		{ text: 'Spine bot', icon: iconSideSpineBot, action: () => app.print('spine-bot') },
	],
};

export const menuStore: CtxMenuConfig = {
	anchor: 'right',
	items: [
		'Store with suffix',
		{ text: 'Silver', icon: iconSwatch, color: '#c4c4c4', action: () => app.store('Silber') },
		{ text: 'Gold', icon: iconSwatch, color: '#d0a949', action: () => app.store('Gold') },
		{ text: 'White', icon: iconSwatch, color: '#f0f0f0', action: () => app.store('Weiß') },
		'Export',
		{ text: 'Copy as URL', icon: iconUrl, action: () => app.copyAsUrl() },
	],
};

export const menuGlobal: CtxMenuConfig = {
	items: [
		{ text: 'Reset', kbd: 'R', icon: iconReset, action: () => app.reset() },
		{ text: 'Print', kbd: 'CTRL+P', icon: iconPrint, action: () => app.print(), items: menuPrint.items },
		{ text: 'Store', kbd: 'CTRL+S', icon: iconStore, action: () => app.store(), items: menuStore.items },
	],
};

Tag Map

You can also set up global behavior for specific element tags.

/lib/menus.ts

import { iconCopy, iconCut, iconPaste } from '@leon8ix/jhana-ui';
import type { CtxMenuConfig, CtxMenuTagMap } from '@leon8ix/jhana-ui/ctx-menu';
import { copy, cut, paste } from '@leon8ix/jhana-ui/ctx-menu';
import type { MapKey, MapValue } from '@leon8ix/utils';

export const menuInputs: CtxMenuConfig = {
	items: [
		{ text: 'Copy', icon: iconCopy, action: () => copy() },
		{ text: 'Cut', icon: iconCut, action: () => cut() },
		{ text: 'Paste', icon: iconPaste, action: () => paste() },
	],
};

export const menuTagMap: CtxMenuTagMap = new Map<MapKey<CtxMenuTagMap>, MapValue<CtxMenuTagMap>>([
	['input', menuInputs],
	['textarea', menuInputs],
	['button', false],
	['a', 'browser'],
]);
Browser menu

Binding to Elements

You've seen how you can set a global context menu, but of course you can bind one to any element.

/routes/context-buttons/+page.svelte

<script lang="ts">
	import { iconStore } from '@leon8ix/jhana-ui';
	import { ctxMenu, openMenu, type CtxMenuConfig, type CtxMenuItem } from '@leon8ix/jhana-ui/ctx-menu';
	import { delay } from '@leon8ix/utils/dom';

	const menuItems: CtxMenuItem[] = [
		{ text: 'Basic Action Sync', icon: iconStore, action: () => null },
		{ text: 'Async Action 5s', icon: iconStore, action: () => delay(5000) },
		{ text: 'Async Action 500ms', icon: iconStore, action: () => delay(500) },
	];

	const menuL: CtxMenuConfig = { items: menuItems, anchor: 'left' };
	const menuC: CtxMenuConfig = { items: menuItems, anchor: 'center' };
	const menuR: CtxMenuConfig = { items: menuItems, anchor: 'right' };
</script>

<!-- 
 Since there is a global context menu system handling the contextmenu event, you can not 
 use oncontextmenu={openMenu}, as this would interfere with the global handling. The 
 attachment handles the setup for that system.
 For normal clicks, there is no such global system, so just use onclick={openMenu(menu)}
-->
<div class="btn-group">
	<button class="btn" onclick={openMenu(menuL)}>Left click me</button>
	<button class="btn" {@attach ctxMenu(menuC)}>Right click me</button>
	<button class="btn" onclick={openMenu(menuR)} {@attach ctxMenu(menuR)}>Any click me</button>
</div>