feat(context-menu): add target prop to selectively trigger context menu

This commit is contained in:
metonym 2021-11-18 13:07:09 -08:00
commit f603106b18
4 changed files with 82 additions and 28 deletions

View file

@ -780,12 +780,13 @@ None.
### Props ### Props
| Prop name | Kind | Reactive | Type | Default value | Description | | Prop name | Kind | Reactive | Type | Default value | Description |
| :-------- | :--------------- | :------- | :---------------------------------------- | ------------------ | -------------------------------------------------------------------------------- | | :-------- | :--------------- | :------- | :-------------------------------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| ref | <code>let</code> | Yes | <code>null &#124; HTMLUListElement</code> | <code>null</code> | Obtain a reference to the unordered list HTML element | | ref | <code>let</code> | Yes | <code>null &#124; HTMLUListElement</code> | <code>null</code> | Obtain a reference to the unordered list HTML element |
| y | <code>let</code> | Yes | <code>number</code> | <code>0</code> | Specify the vertical offset of the menu position | | y | <code>let</code> | Yes | <code>number</code> | <code>0</code> | Specify the vertical offset of the menu position |
| x | <code>let</code> | Yes | <code>number</code> | <code>0</code> | Specify the horizontal offset of the menu position | | x | <code>let</code> | Yes | <code>number</code> | <code>0</code> | Specify the horizontal offset of the menu position |
| open | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` to open the menu<br />Either `x` and `y` must be greater than zero | | open | <code>let</code> | Yes | <code>boolean</code> | <code>false</code> | Set to `true` to open the menu<br />Either `x` and `y` must be greater than zero |
| target | <code>let</code> | No | <code>null &#124; HTMLElement &#124; HTMLElement[]</code> | <code>null</code> | Specify an element or list of elements to trigger the context menu.<br />If no element is specified, the context menu applies to the entire window |
### Slots ### Slots

View file

@ -1809,6 +1809,17 @@
"moduleName": "ContextMenu", "moduleName": "ContextMenu",
"filePath": "src/ContextMenu/ContextMenu.svelte", "filePath": "src/ContextMenu/ContextMenu.svelte",
"props": [ "props": [
{
"name": "target",
"kind": "let",
"description": "Specify an element or list of elements to trigger the context menu.\nIf no element is specified, the context menu applies to the entire window",
"type": "null | HTMLElement | HTMLElement[]",
"value": "null",
"isFunction": false,
"isFunctionDeclaration": false,
"constant": false,
"reactive": false
},
{ {
"name": "open", "name": "open",
"kind": "let", "kind": "let",

View file

@ -1,4 +1,11 @@
<script> <script>
/**
* Specify an element or list of elements to trigger the context menu.
* If no element is specified, the context menu applies to the entire window
* @type {null | HTMLElement | HTMLElement[]}
*/
export let target = null;
/** /**
* Set to `true` to open the menu * Set to `true` to open the menu
* Either `x` and `y` must be greater than zero * Either `x` and `y` must be greater than zero
@ -15,6 +22,7 @@
export let ref = null; export let ref = null;
import { import {
onMount,
setContext, setContext,
getContext, getContext,
afterUpdate, afterUpdate,
@ -44,6 +52,53 @@
focusIndex = -1; focusIndex = -1;
} }
/** @type {(e: MouseEvent) => void} */
function openMenu(e) {
const { height, width } = ref.getBoundingClientRect();
if (open || x === 0) {
if (window.innerWidth - width < e.x) {
x = e.x - width;
} else {
x = e.x;
}
}
if (open || y === 0) {
menuOffsetX.set(e.x);
if (window.innerHeight - height < e.y) {
y = e.y - height;
} else {
y = e.y;
}
}
position.set([x, y]);
open = true;
}
$: if (target != null) {
if (Array.isArray(target)) {
target.forEach((node) => node?.addEventListener("contextmenu", openMenu));
} else {
target.addEventListener("contextmenu", openMenu);
}
}
onMount(() => {
return () => {
if (target != null) {
if (Array.isArray(target)) {
target.forEach((node) =>
node?.removeEventListener("contextmenu", openMenu)
);
} else {
target.removeEventListener("contextmenu", openMenu);
}
}
};
});
setContext("ContextMenu", { setContext("ContextMenu", {
menuOffsetX, menuOffsetX,
currentIndex, currentIndex,
@ -78,30 +133,10 @@
<svelte:window <svelte:window
on:contextmenu|preventDefault="{(e) => { on:contextmenu|preventDefault="{(e) => {
if (target != null) return;
if (level > 1) return; if (level > 1) return;
if (!ref) return; if (!ref) return;
openMenu(e);
const { height, width } = ref.getBoundingClientRect();
if (open || x === 0) {
if (window.innerWidth - width < e.x) {
x = e.x - width;
} else {
x = e.x;
}
}
if (open || y === 0) {
menuOffsetX.set(e.x);
if (window.innerHeight - height < e.y) {
y = e.y - height;
} else {
y = e.y;
}
}
position.set([x, y]);
open = true;
}}" }}"
on:click="{(e) => { on:click="{(e) => {
if (!open) return; if (!open) return;

View file

@ -3,6 +3,13 @@ import { SvelteComponentTyped } from "svelte";
export interface ContextMenuProps export interface ContextMenuProps
extends svelte.JSX.HTMLAttributes<HTMLElementTagNameMap["ul"]> { extends svelte.JSX.HTMLAttributes<HTMLElementTagNameMap["ul"]> {
/**
* Specify an element or list of elements to trigger the context menu.
* If no element is specified, the context menu applies to the entire window
* @default null
*/
target?: null | HTMLElement | HTMLElement[];
/** /**
* Set to `true` to open the menu * Set to `true` to open the menu
* Either `x` and `y` must be greater than zero * Either `x` and `y` must be greater than zero