Murasaki
ガイド

コンテキストメニュー

useContextMenu で本物の OS コンテキストメニューを宣言する — HTML ポップアップは一切関与しません。

Murasaki のコンテキストメニューは、マークアップではなくフックで宣言するため、あなたの state のすぐ隣で使えます — その actionuseState のセッターをクロージャできます。 HTML は一切レンダーされません。項目は Rust 側に投げられ、本物の OS メニュー(macOS で は NSMenu、Windows では HMENU)がポップアップします。宣言する各rendererへ menu:context を付与します。

murasaki.config.ts
window: { capabilities: ['menu:context', 'clipboard:writeText'] }

native roleには対応するcapabilityも必要です。この例の clipboard:writeText<Action.Copy /> 用で、plain custom callbackなら menu:context だけで足ります。

ウィンドウ全体のメニュー

id を指定せずに useContextMenu を呼び出すと、デフォルトメニューが宣言されます — スコープ付きメニューに占有されていない場所であれば、ユーザーがどこを右クリックしても開 きます:

src/app/layout.tsx
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 のロール(CopyPasteCutSelectAllUndoRedoQuit)、またはクライアントの挙動(ReloadNavigateRun)です。全リストは Native APIsを参照してください。

native context menuには常にmenu:contextが必要です。native role actionには、直接native APIを呼ぶ場合と同じpermissionも必要です。

action追加で必要なcapability
Copy, Cutclipboard:writeText
Pasteclipboard:readText
Quitapp:quit
SelectAll, Undo, Redomenu:context 以外は不要

必要なrole capabilityが欠ける場合、Murasakiはmenu payload全体を拒否します。client behaviorと自作callbackには追加のnative capabilityは不要です。

createActions による再利用可能な Actions

アプリの actions を(典型的には src/lib/action.ts に、store に裏打ちされた形で)一度 だけ定義すれば、どこからでも呼び出せます — そして <Action.name /> として、どのメニュ ーにも組み込めます:

src/lib/action.ts
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.CopyAction.Reload など)も含まれるため、1つのファイルで Action を import するだけです べてをまかなえます。

ウィンドウ全体のメニュー(id なしの useContextMenu(items))は、同時に1つだけマウン トされるべきです — 2つ目をマウントすると開発時に警告がログに出力され、最後にマウント されたものが優先されます。

次へ

GitHub でこのページを改善

On this page