Replies: 6 comments 2 replies
-
Hello, do you have a screenshot of your positioning dilemma? The documented examples seem to open in unexpected places, so I prepared this demo where it opens near the pointer: https://svelte.dev/repl/7284d4c3c0e74279a036fc3808513cb0?version=4.2.0 The demo above also showcases how to change menu items based on the selected target by doing the following:
Alternatively, you could add multiple Code snippet for posterity<script>
import {
ContextMenu, ContextMenuOption, ContextMenuDivider, ContextMenuGroup
} from "[email protected]/src/ContextMenu/index.js";
let target, differentTarget
let openId
</script>
<svelte:head>
<link href="https://unpkg.com/[email protected]/css/white.css" rel="stylesheet" />
</svelte:head>
<ContextMenu target={[target, differentTarget]} on:open={(e) => {
openId = e.detail.dataset.id
console.log(openId)
}}>
<ContextMenuOption
indented
labelText="Copy"
shortcutText="⌘C"
/>
{#if openId === 'two'}
<ContextMenuOption
indented
labelText="SECRET ITEM"
/>
{/if}
<ContextMenuOption indented labelText="Cut" shortcutText="⌘X" />
<ContextMenuDivider />
<ContextMenuOption indented labelText="Export as">
<ContextMenuGroup labelText="Export options">
<ContextMenuOption id="pdf" labelText="PDF" />
<ContextMenuOption id="txt" labelText="TXT" />
<ContextMenuOption id="mp3" labelText="MP3" />
</ContextMenuGroup>
</ContextMenuOption>
<ContextMenuDivider />
<ContextMenuOption selectable labelText="Remove metadata" />
<ContextMenuDivider />
<ContextMenuGroup labelText="Style options">
<ContextMenuOption id="0" labelText="Font smoothing" selected />
<ContextMenuOption id="1" labelText="Reduce noise" />
<ContextMenuOption id="2" labelText="Auto-sharpen" />
</ContextMenuGroup>
<ContextMenuDivider />
<ContextMenuOption indented kind="danger" labelText="Delete" />
</ContextMenu>
<div class="area" data-id="one" bind:this={target}>
Right click here
</div>
<div class="area" data-id="two" bind:this={differentTarget}>
Right click here for something different.
</div>
<style>
.area {
padding: 1rem;
margin-bottom: 1rem;
border: 2px solid red;
}
</style> |
Beta Was this translation helpful? Give feedback.
-
I'm adding context menus to the red li elements Using the ContextMenu I get this: I've had to build a custom version to avoid it. |
Beta Was this translation helpful? Give feedback.
-
Also notice that in your linked example the custom context menu doesn't close when you open the other ones. Here's my custom version, which works the way I need it to: <script>
/**
* @todo: this should be replaced when Carbon-SVelte PR gets merged: https://github.com/IBM/carbon-components-svelte/pull/622
*/
/**
* Set to `true` to open the menu
* Either `x` and `y` must be greater than zero
* @type {boolean}
*/
export let open = false;
/**
* Specify the horizontal offset of the menu position
* @type {number}
*/
export let x = 0;
/**
* Specify the vertical offset of the menu position
* @type {number}
*/
export let y = 0;
/**
* Obtain a reference to the unordered list HTML element
* @type {HTMLUListElement | null}
*/
export let ref = null;
let id = 'context-menu_' + Math.random().toString(36).substr(2, 9);
import {
setContext,
getContext,
onMount,
onDestroy,
afterUpdate,
createEventDispatcher
} from 'svelte';
import { writable } from 'svelte/store';
const position = writable([x, y]);
const dimensions = writable([0, 0]);
const currentIndex = writable(-1);
const hasPopup = writable(false);
const menuOffsetX = writable(0);
const ctx = getContext('ContextMenu');
let options = [];
let direction = 1;
let prevX = 0;
let prevY = 0;
let focusIndex = -1;
let level;
$: level = !ctx ? 1 : 2;
$: $position = [x, y];
$: $currentIndex = focusIndex;
$: if (open && level === 1 && ref != null) {
const { height, width } = ref.getBoundingClientRect();
$dimensions = [width, height];
// if the menu is too far to the right, display it on the left side of the cursor
if (window.innerWidth - width < x) x -= width;
// if the menu is too far down, display it above the cursor
if (window.innerHeight - height < y) y -= height;
}
setContext('ContextMenu', {
currentIndex,
position,
dimensions,
close,
menuOffsetX,
setPopup: (popup) => ($hasPopup = popup)
});
function handleClickOutside(event) {
if (open) {
close();
}
}
function close() {
focusIndex = -1;
open = false;
document.removeEventListener('contextmenu', handleClickOutside);
}
afterUpdate(() => {
if (open) {
options = [...ref.querySelectorAll("li[data-nested='false']")];
if (level === 1) {
if (prevX !== x || prevY !== y) ref.focus();
prevX = x;
prevY = y;
}
setTimeout(() => {
document.addEventListener('contextmenu', handleClickOutside);
document.addEventListener('click', handleClickOutside);
}, 0);
} else {
document.removeEventListener('contextmenu', handleClickOutside);
document.removeEventListener('click', handleClickOutside);
}
});
onDestroy(() => {
document.removeEventListener('contextmenu', handleClickOutside);
document.removeEventListener('click', handleClickOutside);
});
</script>
<ul
bind:this={ref}
role="menu"
tabindex="-1"
data-direction={direction}
data-level={level}
class:bx--menu={true}
class:bx--menu--open={open}
class:bx--menu--invisible={open && x === 0 && y === 0}
class:bx--menu--root={level === 1}
{...$$restProps}
style="left: {x}px; top: {y}px; {$$restProps.style}"
on:click
on:click={({ target }) => {
const closestOption = target.closest('[tabindex]');
if (closestOption && closestOption.getAttribute('role') !== 'menuitem') {
close();
}
}}
on:contextmenu
on:contextmenu={({ target }) => {
const closestOption = target.closest('[tabindex]');
if (closestOption && closestOption.getAttribute('role') !== 'menuitem') {
close();
}
}}
on:keydown
on:keydown={(e) => {
if (open) e.preventDefault();
if ($hasPopup) return;
if (e.key === 'ArrowDown') {
if (focusIndex < options.length - 1) focusIndex++;
} else if (e.key === 'ArrowUp') {
if (focusIndex === -1) {
focusIndex = options.length - 1;
} else {
if (focusIndex > 0) focusIndex--;
}
}
}}
>
<slot />
</ul> |
Beta Was this translation helpful? Give feedback.
-
This was a regression introduced in a refactoring PR that leveraged the This is a Svelte bug that was fixed in 3.59. The solution is to upgrade your Svelte version to 3.59 or higher. I upgraded the Svelte version used by the docs site (previously 3.58), and the behavior is now fixed. |
Beta Was this translation helpful? Give feedback.
-
I just upgraded Svelte to v4.2.0 to match the one in the carbon-svelte documentation and I still get the same behaviour. |
Beta Was this translation helpful? Give feedback.
-
I'm having some difficulty with ContextMenu. In the past it wasn't contextual at all. That was changed (allegedly) with the addition of the
target
prop. However, as far as I can tell, there are still issues because the positioning of the menu when it pops up is not relative. It causes my layouts to break.The whole way in which it is designed and in which the examples are created still seems to take the approach of it being a global menu, rather than an actual context menu. E.g. the multiple targets example... it's still popping up the same global menu (in the same position) for both targets. That's not contextual, that's just ubiquity, which is an entirely different thing.
The point of a context menu, is that each context is different, and thus each menu is different according to the context – both in terms of its options and its positioning. This component seems ill suited to the name ContextMenu. I still feel it should be called GlobalMenu and that another component needs to be added to handle ContextMenu. I'd be happy to be proved wrong but so far none of the examples meet the requirement in my humble opinion.
But let me play the optimist and ask... What's the correct way of getting a unique ContextMenu , with options and position that differ by context of the element being right-clicked?
None of the examples show this use-case (which is odd because I honestly struggle to think of another use case for context menus).
https://carbon-components-svelte.onrender.com/components/ContextMenu
Beta Was this translation helpful? Give feedback.
All reactions