Contextmenu #

Basic usage
#

Try opening the contextmenu on this panel with rightclick or longpress.

<Contextmenu_Root scoped>
  <Contextmenu>
    {#snippet entries()}
      <Contextmenu_Entry run={() => (greeted = !greeted)}> <!-- false />
        Hello world
      </Contextmenu_Entry>
      <Contextmenu_Entry run={() => (greeted_icon_snippet = !greeted_icon_snippet)}> <!-- false />
        {#snippet icon()}🌞{/snippet}
        Hello with an optional icon snippet
      </Contextmenu_Entry>
      <Contextmenu_Entry run={() => (greeted_icon_string = !greeted_icon_string)} icon="🌚"> <!-- false />
        Hello with an optional icon string
      </Contextmenu_Entry>
    {/snippet}
    ...markup with the above contextmenu behavior...
  </Contextmenu>
  ...markup with only default contextmenu behavior...
</Contextmenu_Root>
...markup without contextmenu behavior...
greeted = false
greeted_icon_snippet = false
greeted_icon_string = false

Default behaviors
#

<Contextmenu_Root scoped>
  ...<a href="https://www.fuz.dev/">
    a link like this one
  </a>...
</Contextmenu_Root>

Opening the contextmenu on a link like this one has special behavior by default. To accesss your browser's normal contextmenu, open the contextmenu on the link inside the contextmenu itself or hold Shift.

Although disruptive to default browser behavior, this allows links to have contextmenu behaviors, and it allows you to open the contextmenu anywhere to access all contextual behaviors.

Custom instance
#

const contextmenu = create_contextmenu();
<Contextmenu_Root {contextmenu} scoped>...

The Contextmenu_Root prop contextmenu provides more control. Try opening the contextmenu on this panel.

Select text
#

If a contextmenu is triggered on selected text, it includes a Copy text entry.

Try and then opening the contextmenu on it.

Opening the contextmenu on an input or textarea opens the browser's default contextmenu.

contenteditable likewise has your browser's default contextmenu behavior.

contenteditable

contenteditable="plaintext-only"

Full example
#

🏠
😺Alyssa
😸Ben
🌄
View example source

Expected behaviors
#

The Contextmenu overrides the system contextmenu to provide capabilities specific to your app. The motivation docs explain why Fuz breaks web platform expectations.

On touch devices, we detect tap-and-hold (aka longpress) instead of simply overriding the web's contextmenu event because iOS does not support this web standard as of July 2023 as described in this WebKit bug report. The Fuz implementation therefore has hacks that may cause corner case bugs on various devices and browsers, and it breaks navigator.vibrate on all mobile browsers that I've tested because it triggers the gesture on a timeout, not a user action.

When you rightclick or longpress inside a Contextmenu_Root, it searches for behaviors defined with Contextmenu starting from the target element up to the root. If any behaviors are found, the Fuz contextmenu opens, with the caveats below. The contextmenu displays the available behaviors which you can then activate. If no behaviors are found, the system contextmenu opens.

Devices with a mouse

  • rightclick opens the Fuz contextmenu and not the system contextmenu except on input/textarea/contenteditable
  • rightclick on the Fuz contextmenu opens the system contextmenu
  • rightclick while holding Shift opens the system contextmenu
  • keyboard navigation and activation should work similarly to the W3C APG menubar pattern

Touch devices

  • longpress opens the Fuz contextmenu and not the system contextmenu
  • longpress on the Fuz contextmenu (two longpresses) opens the system contextmenu
  • double-tap-and-hold (aka tap-then-longpress) opens the system contextmenu or performs other default behavior like selecting text - does not work for cases where the first tap performs some action on an element, like links - use two longpresses for those cases (this may need more design work, possibly adding a different gesture or a contextmenu entry for touch devices that triggers the system conextmenu on the next longpress)
  • a longpress is canceled if you move the touch past a threshold before it triggers
  • the contextmenu closes if you move past a threshold without lifting the longpress touch that opened it
  • gives haptic feedback on open with navigator.vibrate (currently broken, may remain so due to the iOS longpress workaround)

Motivation
#

Fuz takes two things very seriously, in no particular order:

  1. giving users a powerful and customizable UX
  2. aligning with the web platform and not breaking its standard behaviors

For #1, Fuz includes a custom contextmenu. Like Google Docs, when you right-click or tap-and-hold (aka longpress) on an element inside Fuz's Contextmenu, you'll see app-specific options and actions for your current context.

This is a powerful UX pattern, but it violates #2. The Fuz contextmenu breaks the normal browser behavior of showing the system contextmenu and device-specific behaviors like selecting text on a longpress.

Balancing these two concerns is going to be an ongoing challenge, and my current belief is that the contextmenu is too useful and powerful to ignore. I'm open to critical feedback, and I'll do what I can to minimize the harmful effects of choices like this.

Mitigations:

  • The Fuz contextmenu does not open on elements that allow clipboard pasting like inputs, textareas, and contenteditables.
  • To bypass the Fuz contextmenu on a device with a keyboard, hold the Shift key.
  • To bypass the Fuz contextmenu on a touch device, like to select text, tap one extra time before your longpress. This means double-tap-and-hold should behave the same as tap-and-hold on standard web pages.
  • Triggering the contextmenu inside of the Fuz contextmenu shows your system contextmenu. This means you can either double-right-click or longpress twice to access your system contextmenu as an alternative to holding Shift or double-tap-and-hold, However a caveat is that the target of your action will be some element inside the Fuz contextmenu, so to select text or access a link's system contextmenu on a touch device, you must use double-tap-and-hold. When you open the Fuz contextmenu on a link, you'll see the link again in the menu under your pointer by default, so to access your system's functionality on links, tap-and-hold twice.