PhoenixKitWeb.Components.Core.ContextMenu (phoenix_kit v2.16.0)

Copy Markdown View Source

A desktop-style context menu: right-click (or touch-and-hold) any matching element to open an action menu at the pointer.

One menu element serves every row it matches. A tree of five hundred nodes renders one hidden <ul>, not five hundred — the ContextMenu JS hook finds the row the pointer landed on, copies its identifier onto each item's phx-value-*, and opens the menu there. Opening costs no server round-trip, and the event that fires afterwards still carries the right target.

Usage

Mark the rows, then declare one menu that selects them:

<li data-context-value={folder.uuid} data-context-label={folder.name}>
  
</li>

<.context_menu id="folder-menu" selector="[data-context-value]" value_name="folder-uuid">
  <.context_menu_button phx-click="start_rename_folder" icon="hero-pencil" label="Rename" />
  <.context_menu_divider />
  <.context_menu_button phx-click="delete_folder" icon="hero-trash" label="Delete" variant="error" />
</.context_menu>

A right-click on that row opens the menu and the "Delete" item fires delete_folder with %{"folder-uuid" => folder.uuid} — the same param shape the row's own phx-click already uses, so handlers are shared rather than duplicated.

Two menus on one page

Declare one per row kind, each with its own selector:

<.context_menu id="folder-menu" selector="[data-context-kind=folder]" >
<.context_menu id="note-menu"   selector="[data-context-kind=note]" >

When a click matches more than one (a note row nested inside a folder row), the deepest match wins — DOM order does not decide it. Use within to confine a menu to one region of the page when the same selector appears in several.

Touch

A press held for long_press_ms (default 450ms, cancelled by a 10px move) opens the menu at the touch point and vibrates briefly where supported. The click that follows the release is swallowed, so holding a row does not also activate it. Pass long_press={false} for a page whose touch gesture is already taken.

Long-press collides with MediaDragDrop

MediaDragDrop binds its own 450ms long press to [data-draggable-file] / [data-draggable-folder] and pushes long_press_select. On a page running both, one hold fires both gestures.

A disjoint selector is not enough: rows are matched with Element.closest/1, so the two collide whenever one element merely contains the other — and in FolderExplorer they do, both ways (data-draggable-folder sits on a button inside the data-context-kind="folder" row; a leaf <li> carries data-draggable-file and data-context-kind="item" together). The MediaBrowser sidebar is exactly this shape.

There is a second effect worth knowing: this hook swallows the post-long-press click at document capture, which is upstream of the per-element listener MediaDragDrop uses to clear its own _lpFired flag — so that flag stays set and eats one later tap on the same card.

On a page running both, pass long_press={false} (right-click still works) or don't wire MediaDragDrop.

What items can do

Items are buttons and links whose markup is TableRowMenu's, so a context menu and a row menu look identical. Only phx-value-* is stamped per row: an item's navigate/href is fixed at render time and cannot vary by target. For "open this one", use a button and push_navigate/2 from the handler.

Where the events land

The menu is portaled to <body> while open, so position: fixed escapes any <dialog> or transformed ancestor. LiveView routes clicks from an element outside every view root to the main LiveView, which is what a plain LiveView or a phx-target-carrying item wants. Inside a nested LiveView (a sticky one, say), pass an explicit phx-target on each item — the fallback would otherwise deliver to the main view.

Summary

Functions

Renders one context menu for every element matching selector.

An action item. Takes phx-click and friends through rest; the hook adds phx-value-* for the right-clicked row.

A separator between item groups.

A navigation item with a fixed destination.

Functions

context_menu(assigns)

Renders one context menu for every element matching selector.

Attributes

  • id — unique element id (required); the hook attaches here.
  • selector — CSS selector for the elements that open this menu (required).
  • within — optional CSS selector confining selector to one container.
  • value_attr — row attribute holding the target's identifier (default "data-context-value").
  • value_name — the phx-value-* suffix stamped onto items (default "uuid", i.e. phx-value-uuid).
  • label_attr — row attribute holding a heading to show above the items (default "data-context-label"); the heading hides when the row has none.
  • show_label — render the heading slot at all (default true).
  • long_press / long_press_ms — the touch gesture (default true / 450).
  • labelaria-label for the menu; pass a translated string.
  • class — extra classes for the floating <ul>.

Attributes

  • id (:string) (required)

  • selector (:string) (required) - CSS selector for the elements a right-click on which opens this menu.

  • within (:string) - Optional container selector; rows outside it are ignored. Defaults to nil.

  • value_attr (:string) - Row attribute read for the target identifier. Defaults to "data-context-value".

  • value_name (:any) - Stamped onto items as phx-value-<value_name>. A list stamps every name, for a menu whose items were written against handlers that spell the param differently ("folder-uuid" for one, "folder_uuid" for another).

    Defaults to "uuid".

  • label_attr (:string) - Row attribute read for the menu heading. Defaults to "data-context-label".

  • show_label (:boolean) - Defaults to true.

  • long_press (:boolean) - Defaults to true.

  • long_press_ms (:integer) - Defaults to 450.

  • label (:string) - Defaults to "Context menu".

  • class (:any) - Defaults to nil.

Slots

  • inner_block (required)

context_menu_button(assigns)

An action item. Takes phx-click and friends through rest; the hook adds phx-value-* for the right-clicked row.

See PhoenixKitWeb.Components.Core.TableRowMenu.table_row_menu_button/1 — this is that component under a name that reads right inside a context menu.

Attributes

  • icon (:string) - Defaults to nil.
  • label (:string) (required)
  • variant (:string) - Defaults to "default".
  • Global attributes are accepted.

context_menu_divider(assigns)

A separator between item groups.

context_menu_link(assigns)

A navigation item with a fixed destination.

navigate/patch/href are rendered once and do not vary per row — only phx-value-* is stamped. For a per-row destination use context_menu_button/1 and navigate from the handler.

Attributes

  • navigate (:string) - Defaults to nil.
  • patch (:string) - Defaults to nil.
  • href (:string) - Defaults to nil.
  • icon (:string) - Defaults to nil.
  • label (:string) (required)
  • variant (:string) - Defaults to "default".
  • Global attributes are accepted. Supports all globals plus: ["target", "rel", "method", "csrf_token", "data-confirm"].