HextaUI

useHeldKeys

Les touches actuellement maintenues, partagées entre tous les abonnés via un seul jeu d'écouteurs sur 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

Ajoute le hook et tout ce dont il dépend à votre projet.

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

Utilisez-le pour tout ce qui réagit à des touches maintenues plutôt qu'appuyées : des capuchons qui s'enfoncent, des indices de raccourci qui apparaissent quand vous maintenez ⌘, ou un modificateur qui change d'outil, comme Alt pour dupliquer pendant un glissement.

Chaque composant qui appelle le hook partage un même store. Le premier abonné ajoute des écouteurs passifs keydown et keyup sur window, et le dernier à se désabonner les retire. Une page de cinquante capuchons à l'écoute n'a toujours qu'une paire d'écouteurs.

Les écouteurs ne font que lire les événements. Ils n'appellent jamais preventDefault et se placent sur window, après les gestionnaires propres de React : la saisie dans un champ n'est donc jamais retardée ni modifiée.

"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" …
  • Les lettres et chiffres viennent de event.code, la touche physique : maintenir Option+K sur un Mac signale donc toujours k et non ˚.
  • La répétition de touche est ignorée, et rien n'est re-rendu tant qu'une touche est maintenue.
  • macOS n'envoie pas keyup pour les autres touches tant que ⌘ est enfoncée. Quand ⌘ est relâchée, le store ne garde que les modificateurs encore maintenus, pour que les lettres ne restent pas bloquées.
  • Tout est relâché quand la fenêtre perd le focus ou que l'onglet est masqué. Un raccourci qui change d'application ne laisse rien de maintenu.

Des capuchons qui s'enfoncent

<Kbd listen> repose sur ce hook. Chaque capuchon s'enfonce tant que sa touche est maintenue.

"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>
  )
}
  • Passez false pour arrêter d'écouter. Le hook renvoie alors un ensemble vide et n'ajoute aucun écouteur : l'appeler sans condition ne coûte rien.
  • L'ensemble n'est remplacé que lorsqu'une touche est enfoncée ou relâchée, si bien que son identité sert de dépendance à un memo ou à un effet.
  • Pour les raccourcis qui déclenchent une action, utilisez plutôt matchesHotkey de Hotkey dans un gestionnaire keydown. Suivre les touches maintenues sert à montrer un état, pas à exécuter des commandes.
  • Côté serveur, et avant l'hydratation, l'ensemble est vide.
PropTypePar défaut
enabledIndique s'il faut écouter. Si false, rien n'est attaché.
boolean–
Valeur de retourDescription
ReadonlySet<string>Les noms des touches actuellement maintenues.

Kbd et KbdGroup via leur prop listen.