Global Context Menu
Use openAtPosition to open a context menu without a trigger. Without a position or trigger, the menu cannot open.
Code
The following example assumes that you have imported the component and set up the theme.
<div class="global-context-menu-example"> <div class="context-area"> <strong>Context menu area</strong> <span>Right-click or touch and hold anywhere in this rectangle.</span> </div>
<sv-menu class="global-context-menu" mode="context"> <sv-flyout offset="0"> <button type="button" class="context-option" role="menuitem">Refresh</button> <button type="button" class="context-option" role="menuitem">Paste</button> <button type="button" class="context-option" role="menuitem">Select all</button> <div class="divider" role="separator"></div> <button type="button" class="context-option" role="menuitem">Open developer tools</button> </sv-flyout> </sv-menu>
<p class="context-result" aria-live="polite">Choose an action from the context menu.</p></div>
<script type="module"> const example = document.querySelector(".global-context-menu-example");
/** @type {HTMLElement} */ const contextArea = example.querySelector(".context-area");
/** @type {Menu} */ const menu = example.querySelector(".global-context-menu");
/** @type {Flyout} */ const flyout = menu.querySelector("sv-flyout");
/** @type {HTMLElement} */ const result = example.querySelector(".context-result");
/** @type {NodeListOf<HTMLButtonElement>} */ const options = menu.querySelectorAll(".context-option");
contextArea.addEventListener("contextmenu", event => { menu.openAtPosition = { left: event.clientX, top: event.clientY }; });
let scrollLocked = false; let previousOverflow = "";
const lockScroll = () => { if (scrollLocked) return;
scrollLocked = true; previousOverflow = document.documentElement.style.overflow; document.documentElement.style.overflow = "hidden"; };
const unlockScroll = () => { if (!scrollLocked) return;
scrollLocked = false; document.documentElement.style.overflow = previousOverflow;
// When `undefined`, the menu won't open because there is no position nor trigger. menu.openAtPosition = undefined; };
flyout.addEventListener("expandstart", lockScroll); flyout.addEventListener("collapsed", unlockScroll); window.addEventListener("pagehide", unlockScroll);
for (const option of options) { option.addEventListener("click", () => { result.textContent = `${option.textContent} selected.`; menu.close(); }); }</script>
69 collapsed lines
<style> .global-context-menu-example { display: grid; gap: 0.75em; max-inline-size: 40rem; margin: auto;
sv-flyout::part(content) { min-block-size: 100px; }
.context-area { display: grid; gap: 0.5em; place-content: center; min-block-size: 18rem; padding: 1.5em; color: var(--sv-gray-2); text-align: center; touch-action: manipulation; cursor: context-menu; user-select: none; background: linear-gradient(var(--sv-gray-7), var(--sv-gray-7)) padding-box, repeating-linear-gradient(135deg, var(--sv-gray-5) 0 1px, transparent 1px 10px) border-box; border: var(--sv-bdr-sz-sm) solid transparent; border-radius: var(--sv-bdr-rad-lg); -webkit-touch-callout: none;
span { color: var(--sv-gray-3); } }
.context-option { padding: 0.4em 0.75em; font: inherit; color: var(--sv-gray-2); text-align: start; cursor: pointer; background: transparent; border: 0; border-radius: var(--sv-bdr-rad-sm);
&:focus-visible { outline: var(--sv-bdr-sz-sm) solid var(--sv-accent); outline-offset: -0.15em; }
@media (hover: hover) and (pointer: fine) { &:hover { background: var(--sv-gray-5); } } }
.divider { block-size: 1px; background: var(--sv-gray-5); }
.context-result { min-block-size: 1.5em; margin: 0; color: var(--sv-gray-3); text-align: center; } }</style>