コンテキストメニュー
useContextMenu で本物の OS コンテキストメニューを宣言する — HTML ポップアップは一切関与しません。
Murasaki のコンテキストメニューはマークアップではなくフックとして宣言するため、状態のすぐそばに書けます — action の中で useState のセッターをクロージャとして扱えます。HTML は一切レンダーされません。項目は Rust 側に投げられ、本物の OS メニュー(macOS では NSMenu、Windows では HMENU)がポップアップします。宣言する各レンダラーに menu:context を付与します。
window: { capabilities: ['menu:context', 'clipboard:writeText'] }ネイティブロールには、それぞれ対応するケイパビリティも必要です。この例では <Action.Copy /> のために clipboard:writeText を付与しています。単純なカスタムコールバックであれば menu:context だけで十分です。
ウィンドウ全体のメニュー
id を指定せずに useContextMenu を呼び出すと、デフォルトメニューが宣言されます — スコープ付きメニューに占有されていない場所であれば、ユーザーがどこを右クリックしても開きます:
import { useContextMenu, Action } from 'murasaki'
useContextMenu([
{ label: 'Reload', shortcut: 'command,R', action: <Action.Reload /> },
{ separator: true },
{ label: 'Copy', action: <Action.Copy /> },
])スコープ付きメニュー
メニューに id を与え、対応する <ContextMenuTrigger> で領域にタグ付けします — その領域だけがそのメニューを開き、ウィンドウのデフォルトメニューより優先されます:
import { useContextMenu, ContextMenuTrigger } from 'murasaki'
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
useContextMenu('card', [
{ label: 'Increment', action: () => setCount((n) => n + 1) },
])
return (
<ContextMenuTrigger id="card">
<div>Clicked {count} times</div>
</ContextMenuTrigger>
)
}<ContextMenuTrigger> は、子要素がちょうど1つの場合はデフォルトでそれをクローンします(ラッパーノードなし)。複数の子要素がある場合など、代わりに display: contents の <span> ラッパーを強制するには asChild={false} を渡します。
項目の形
各エントリはディバイダーか、以下のいずれかです:
interface ContextMenuEntry {
label: string
shortcut?: string // 例: "command,I" — keydown にも直接紐づけられる
disabled?: boolean
action?: ContextMenuAction // 組み込みの <Action.*/> 要素、または関数
items?: ContextMenuItemSpec[] // action の代わりのサブメニュー
}
type ContextMenuItemSpec = ContextMenuEntry | { separator: true }shortcut は、メニューのネイティブなアクセラレーターラベルを設定すると同時に keydown ハンドラを登録するため、メニューが開いていなくても発火します。
Actions
action には、あなた自身の関数か、組み込みの <Action.*/> 要素のいずれかを指定できます — ネイティブ OS のロール(Copy、Paste、Cut、SelectAll、Undo、Redo、Quit)、またはクライアントの挙動(Reload、Navigate、Run)です。全リストはNative APIsを参照してください。
ネイティブのコンテキストメニューには、常に menu:context が必要です。ネイティブロールのアクションには、対応するネイティブ API を直接呼ぶ場合と同じ許可も必要です。
| アクション | 追加で必要なケイパビリティ |
|---|---|
Copy, Cut | clipboard:writeText |
Paste | clipboard:readText |
Quit | app:quit |
SelectAll, Undo, Redo | menu:context 以外は不要 |
必要なロールのケイパビリティが欠けている場合、Murasaki はメニューのペイロード全体を拒否します。クライアント側の挙動や自作のコールバックには、追加のネイティブケイパビリティは不要です。
Linux では Copy/Cut/Paste/SelectAll は X11 キーイベントの合成によって実現されます(Wayland では XWayland が必要)。一方 Undo/Redo はメニューから静かに省かれます — muda/GTK 側の既知の制約です。Linux でこれらが必要な場合は、独自の action 関数を指定してください。
createActions による再利用可能な Actions
アプリのアクションを一度だけ定義すれば(典型的には src/lib/action.ts に、ストアに裏打ちされた形で)、どこからでも呼び出せます — そして <Action.name /> として、どのメニューにも組み込めます:
import { createActions } from 'murasaki'
import { useCounter } from './counter'
export const Action = createActions({
increment: () => useCounter.getState().increment(),
reset: () => useCounter.getState().reset(),
})import { Action } from '@/lib/action'
useContextMenu('card', [
{ label: 'Increment', shortcut: 'command,I', action: <Action.increment /> },
{ label: 'Reset counter', action: <Action.reset /> },
])createActions が返すオブジェクトには組み込みのアクション(Action.Copy、Action.Reload など)も含まれるため、1つのファイルで Action をインポートするだけですべてをまかなえます。
ウィンドウ全体のメニュー(id なしの useContextMenu(items))は、同時に1つだけマウントされるべきです — 2つ目をマウントすると開発時に警告がログに出力され、最後にマウントされたものが優先されます。