Tucano v0.37.2

Right-click menu

The action menu that shows up where the person clicked. It is the same dropdown menu — same markup, same items, same keyboard — only opened with the right button and anchored at the click point. In a list the items usually change with the row, so items also takes a function.

Right-click a row. From the keyboard, Tab to a row and press the menu key or Shift+F10.

ClientDue dateAmount
Padaria Pão Quente12/09/202612400
Oficina Duas Rodas03/07/20263890.5
Marcenaria Horizonte28/11/2026760

How to use

The area listens for the right button; match says which part of it is the target. The target goes to items, and that is where the actions for that row come from.

new Tucano.ContextMenu('#contracts', {
  match: 'tbody tr',
  items: (row) => [
    { label: row.dataset.name },
    { text: 'Edit', shortcut: '⌘E', onClick: () => edit(row.dataset.id) },
    { text: 'Duplicate', onClick: () => duplicate(row.dataset.id) },
    { separator: true },
    { text: 'Delete', variant: 'danger', onClick: () => remove(row.dataset.id) },
  ],
});

Without match, the target is the whole area — the way to give a card or a panel a single menu. Outside a target the browser's own menu still shows: an action menu that does not know what it acts on is worse than none.

The browser's menu stays one Shift away

Shift + right-click shows the browser's own menu, anywhere in the area. Whoever needs "open in new tab", the spell checker or the inspector is not left out — and that is not something to take from anyone.

Examples

Whole area, no match

A card with the same actions anywhere on it.

Contract 4821

Right-click anywhere on this card.

Menu written in the template

When the server is the one that knows what the person may do, as in the dropdown.

Invoice 118

Right-click here to open the template's menu.

<table id="contracts" data-tuc-contextmenu="#row-actions" data-match="tbody tr">
  {% for obj in page_obj %}
    <tr tabindex="0" data-id="{{ obj.pk }}">…</tr>
  {% endfor %}
</table>

<div class="tuc-dropdown" id="row-actions" hidden>
  <button type="button" class="tuc-dropdown__item">
    <span class="tuc-dropdown__text">Edit</span>
  </button>
</div>

With the template panel the items are the same for every row: code that needs to know which one was clicked reads menu.target inside the onclick, or uses items as a function.

Keyboard and accessibility

The panel is the dropdown's: role="menu", items as menuitem, focus inside the menu and handed back to whoever had it. Opened with the right button, no item starts highlighted — like the system menu, the first arrow key is what highlights the first one. Opened from the keyboard, the first item is already highlighted, because whoever pressed the menu key wants to move through the items.

KeyWhereAction
Menu key, Shift+F10Focused targetOpens the menu anchored to the target
↑ ↓Open menuMoves through the items, wrapping around
Home EndOpen menuFirst and last item
Enter SpaceItemPicks it and closes
EscOpen menuCloses and hands the focus back
Shift + right-clickAreaLets the browser's menu through

The target has to be focusable

The menu key and Shift+F10 open from whatever has the focus. In a table, put tabindex="0" on the rows — without it, anything that lives only in the right-click menu is out of reach for people who do not use a mouse. On phones, a long press opens the menu on Android; on the iPhone, Safari does not tell the page, so no action can live only here.

API

Generated from the code on every build — if something is not here, it does not exist.

Markup
[data-tuc-contextmenu]
In JS
new Tucano.ContextMenu(alvo, opcoes)
Attributes
data-match data-placement data-tuc-contextmenu
Methods
openAt close

Options

The notes in this table come from comments in the source code, which are written in Portuguese.

OptionDefaultWhat for
placement'bottom-start'
itemsnullarray como o do Dropdown, ou (alvo) => array
matchnullseletor do alvo dentro da area; sem ele, a area inteira
closeOnPicktrue
onOpennull(alvo, instancia) — para marcar a linha clicada, por exemplo
panelnullpainel ja escrito no template, no lugar de `items`