Support (or document) `trigger="contextMenu"` on collection items (GridListItem / TreeItem / Row)

オープン
#10,444 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
4/5
見積もり時間
3〜5日
初心者へのやさしさ
52/100
issue の種類
機能追加
明瞭さ
おおむね明確
活発さ
静か
技術スタック
react, typescript

調査の方向性

useContextMenu と MenuTrigger から始め、その後 useSelectableItem.ts:409 と usePress.ts:112/1010 にある collection-item のイベント処理を比較します。issue で名前が挙げられている Menu の stories とテストを調べ、サポートされる結果がドキュメント化なのか API の変更なのかを判断します。collection の行に、ドキュメント化された動作とカバレッジを備えた、サポート対象で警告のないコンテキストメニューの経路があれば完了です。

索引モデルが issue の本文から書いたものです。

説明

Provide a general summary of the feature here

trigger="contextMenu" (new in v1.20.0) works well for a standalone trigger, but there doesn't appear to be a supported way to use it on a collection item — a GridListItem, TreeItem, or table Row. Right-clicking a row to open the same menu its kebab button opens seems like one of the most common reasons to want a context menu, so I suspect this is either a gap or a pattern I've failed to find.

Either a supported API for it, or docs guidance on the intended approach, would help.

🤔 Expected Behavior?

Being able to attach a context menu to a collection item, so that right-click / long-press / Shift+F10 on a row opens the same menu the row's kebab button opens.

Collection items already look like valid trigger candidates: the docs say MenuTrigger "works with any pressable React Aria component", and rows are pressable —

  • useGridListItem → useSelectableItem → usePress (useSelectableItem.ts:409)
  • usePress reads PressResponderContext (usePress.ts:112) and merges the remaining DOM props into its result (usePress.ts:1010)

so a row would consume MenuTrigger's onContextMenu / onKeyDown correctly if it could receive them.

😯 Current Behavior

1. MenuTrigger can't wrap a collection item. MenuTrigger must render as an ancestor of its trigger, but inside a collection it hits the useIsHidden guard and returns null during the collection-building pass, so the item never registers. Wrapping a TreeItem removes every row from the tree — the collection renders empty.

2. Wrapping the row's content in <Pressable> works, but warns once per row. The Pressable child ends up being a div with no interactive ARIA role. The row itself is role="row" (RAC's Tree is a treegrid), which isn't in Pressable's allowed-role list, so there's nothing valid to put the role on:

<Pressable> child must have an interactive ARIA role.

Adding excludeFromTabOrder just swaps it for the other warning:

<Pressable> child must be focusable. Please ensure the tabIndex prop is passed through.

Putting a fake interactive role on a div inside a treegrid row seems worse than the warning, so neither option feels right.

3. Every example uses a standalone <Button>. packages/react-aria-components/stories/Menu.stories.tsx:532, packages/@react-spectrum/s2/stories/Menu.stories.tsx:409, and packages/react-aria-components/test/Menu.test.tsx:652 / :701, plus both docs sites. As a side note, the docs example makes a non-interactive 250×150 region into a <button> purely to satisfy the pressable requirement — it's announced as a button but does nothing when pressed, which reads a little odd as the canonical example.

💁 Possible Solution

useContextMenu works today and looks like the intended escape hatch — contextMenuProps spread straight onto a TreeItem / GridListItem keeps all the platform handling, with no wrapper and no warnings. If that's the recommended pattern for collections, documenting it on the Menu page (or on GridList / Tree / Table) would settle it.

The one thing it loses versus MenuTrigger is positioning the menu at the pointer, which needs getTargetRect — that isn't part of Popover's public types, so it currently needs a @ts-expect-error. Exposing it, or otherwise providing a supported way to anchor a popover to a point, would close that gap.

🔦 Context

We have a collections sidebar built on Tree, where each row has a kebab menu (rename, share, delete, …). We wanted right-click on the row to open that same menu, keeping the kebab button as-is. The kebab stays either way — the context menu is an addition, not a replacement.

We landed on <Pressable> around the row content, which works, but it restructures the row purely to host the trigger and logs a warning per row in development, so it doesn't feel like the intended shape.

💻 Examples

Wrapping the item — collection renders empty:

// Every row disappears: MenuTrigger returns null during the collection build,
// so no TreeItem is ever registered.
<Tree aria-label="Collections">
  <MenuTrigger trigger="contextMenu">
    <TreeItem textValue="Design">
      <TreeItemContent>Design</TreeItemContent>
    </TreeItem>
    <Popover>
      <Menu>
        <MenuItem>Rename…</MenuItem>
        <MenuItem>Delete…</MenuItem>
      </Menu>
    </Popover>
  </MenuTrigger>
</Tree>

Wrapping the content — works, but warns once per row:

<TreeItem textValue="Design">
  <TreeItemContent>
    <MenuTrigger trigger="contextMenu">
      {/* warns: child must have an interactive ARIA role */}
      <Pressable><div className="row-content">Design</div></Pressable>
      <Popover>
        <Menu>
          <MenuItem>Rename…</MenuItem>
          <MenuItem>Delete…</MenuItem>
        </Menu>
      </Popover>
    </MenuTrigger>
  </TreeItemContent>
</TreeItem>

Possibly related: #7197 (pass onContextMenu in RAC Tab), #7987 (useLongPress with a Table Row — event and labelling props get filtered out), #9863 (right-click a table row containing a link).

Versions: [email protected], [email protected], [email protected], React 19.

🧢 Your Company/Team

Metaview

主要言語
TypeScript
スター
16k
フォーク
1.6k
平均マージ
3日 9時間
マージ済み PR(30日)
45

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

adobe/react-spectrum のほかの issue

adobe/react-spectrum の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。