コンテキストメニュー
useContextMenu で本物の OS コンテキストメニューを宣言する — HTML ポップアップは一切関与しません。
Murasaki のコンテキストメニューは、マークアップではなくフックで宣言するため、あなたの
state のすぐ隣で使えます — その action は useState のセッターをクロージャできます。
HTML は一切レンダーされません。項目は Rust 側に投げられ、本物の OS メニュー(macOS で
は NSMenu、Windows では HMENU)がポップアップします。宣言する各rendererへ
menu:context を付与します。
window: { capabilities: ['menu:context', 'clipboard:writeText'] }native roleには対応するcapabilityも必要です。この例の clipboard:writeText は
<Action.Copy /> 用で、plain custom callbackなら 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を参照してください。
native context menuには常にmenu:contextが必要です。native role actionには、直接native
APIを呼ぶ場合と同じpermissionも必要です。
| action | 追加で必要なcapability |
|---|---|
Copy, Cut | clipboard:writeText |
Paste | clipboard:readText |
Quit | app:quit |
SelectAll, Undo, Redo | menu:context 以外は不要 |
必要なrole capabilityが欠ける場合、Murasakiはmenu payload全体を拒否します。client behaviorと自作callbackには追加のnative capabilityは不要です。
createActions による再利用可能な Actions
アプリの actions を(典型的には src/lib/action.ts に、store に裏打ちされた形で)一度
だけ定義すれば、どこからでも呼び出せます — そして <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(Action.Copy、
Action.Reload など)も含まれるため、1つのファイルで Action を import するだけです
べてをまかなえます。
ウィンドウ全体のメニュー(id なしの useContextMenu(items))は、同時に1つだけマウン
トされるべきです — 2つ目をマウントすると開発時に警告がログに出力され、最後にマウント
されたものが優先されます。