HextaUI

useHeldKeys

今まさに押されているキー。1組の window リスナーを通じて、すべての購読者で共有されます。

Hold down any keys
[]
"use client"

import { Kbd, KbdGroup } from "@/components/ui/kbd"
import { useHeldKeys } from "@/hooks/use-held-keys"

export function UseHeldKeysDemo() {
  const held = useHeldKeys(true)

  return (
    <div className="flex flex-col items-center gap-3">
      <div className="flex h-8 items-center">
        {held.size > 0 ? (
          <KbdGroup>
            {[...held].map((key) => (
              <Kbd key={key} keys={key} size="lg" />
            ))}
          </KbdGroup>
        ) : (
          <span className="text-sm text-muted-foreground">
            Hold down any keys
          </span>
        )}
      </div>
      <code className="font-mono text-xs text-muted-foreground">
        {JSON.stringify([...held])}
      </code>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/use-held-keys.json

フックと、その依存関係をプロジェクトに追加します。

import { useHeldKeys } from "@/hooks/use-held-keys"
const held = useHeldKeys(true)
const showShortcutHints = held.has("meta") || held.has("ctrl")

キーを押すことではなく押し続けることに反応するものに使います。たとえば、押し込まれるキーキャップ、⌘ を押している間に表示されるショートカットのヒント、ドラッグ中に Alt で複製するようなツールを切り替える修飾キーです。

フックを呼ぶすべてのコンポーネントは、1つのストアを共有します。最初の購読者が window にパッシブな keydown と keyup のリスナーを追加し、最後に購読を解除した購読者がそれらを削除します。50個のキーキャップが監視していても、リスナーは1組だけです。

リスナーはイベントを読み取るだけです。preventDefault は呼ばず、React 自身のハンドラーの後で window に置かれるため、フィールドへの入力が遅れたり変更されたりすることはありません。

"meta" "ctrl" "alt" "shift"     modifiers, left and right alike
"a" … "z"  "0" … "9"            letters and digits, by physical key
"space" "enter" "escape" "tab"   named keys, lowercased
"arrowup" "arrowdown" "f1" …
  • 文字と数字は物理キーである event.code から取得するため、Mac で Option+K を押していても、˚ ではなく k が報告されます。
  • キーリピートは無視され、キーが押されている間は何も再レンダリングされません。
  • macOS では、⌘ が押されている間、他のキーの keyup が送られません。⌘ が離されると、ストアはまだ押されている修飾キーだけを保持するため、文字が押されたまま残ることはありません。
  • ウィンドウがフォーカスを失うか、タブが非表示になると、すべて解放されます。アプリを切り替えるショートカットで、押されたままになるものはありません。

押し込まれるキーキャップ

<Kbd listen> はこのフックの上に構築されています。各キーキャップは、そのキーが押されている間押し込まれます。

"use client"

import { Kbd, KbdGroup } from "@/components/ui/kbd"
import { useHeldKeys } from "@/hooks/use-held-keys"
import { useIsApple } from "@/lib/hotkey"

export function UseHeldKeysShortcut() {
  const held = useHeldKeys(true)
  const apple = useIsApple()
  const modifier = apple ? "meta" : "ctrl"
  const ready = held.has(modifier) && held.has("shift")

  return (
    <div className="flex flex-col items-center gap-3 text-sm">
      <KbdGroup>
        <Kbd keys="mod" listen />
        <Kbd keys="shift" listen />
        <Kbd keys="p" listen />
      </KbdGroup>
      <p className="text-muted-foreground">
        {ready ? "Now press P" : "Hold the modifiers to see the hint"}
      </p>
    </div>
  )
}
  • リッスンを止めるには false を渡します。フックは空のセットを返してリスナーを追加しないので、無条件に呼び出してもコストはかかりません。
  • セットはキーが押されたとき、または離されたときにだけ置き換えられるため、その同一性はメモ化や effect の依存配列として使えます。
  • アクションを実行するショートカットには、代わりに keydown ハンドラーで Hotkey の matchesHotkey を使ってください。キーの押下保持は状態を表示するためのもので、コマンドを実行するためのものではありません。
  • サーバー上とハイドレーション前は、セットは空です。
プロパティ型デフォルト
enabledリッスンするかどうか。false の場合、何もアタッチされません。
boolean–
戻り値説明
ReadonlySet<string>いま押されているキーの名前。

listen prop を通じた Kbd と KbdGroup。