npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-use-shortcuts

v1.0.0

Published

a full react shortcut solution

Downloads

3

Readme

react-use-shortcuts


Full shortcut solution for react app.

Features

  • Strict/Loose mode.

  • Page scoped register.

  • Dynamic register shortcut.

  • Dynamic enable/disable shortcut registered.

  • Flexible normal key combinations.

  • Use modern browser API.

  • Full types supported.

  • Shortcut validation.

Installation

# npm
npm install react-use-shortcuts
# yarn
yarn add react-use-shortcuts
# pnpm
pnpm add react-use-shortcuts

Supported Keys

Modfiers

| Key | Alias | Notes | | -------------- | ----------------------------------------- | ---------------------------------------- | | ControlLeft | Ctrl Control ControlOrCommand | | | ControlRight | Ctrl Control ControlOrCommand | | | MetaLeft | Command ConmandLeft ControlOrCommand | Windows on Windows, Command on MacOS | | MetaRight | Command ConmandRight ControlOrCommand | Windows on Windows, Command on MacOS | | ShiftLeft | Shift | | | ShiftRight | Shift | | | AltLeft | Option OptionLeft | Option is only available on MacOS. | | AltRight | Option OptionRight | Option is only available on MacOS. |

Normal Keys

| Key | Notes | | ------------ | ------------------------------------------------- | | 0 ~ 9 | Number keys on keyboard main area or numpad area. | | a ~ z | Alphabet keys | | F1~F12 | Function keys | | , | Comma | | . | Period or Decimal on numpad | | / | Slash | | ; | Semicolon | | ' | Quote | | [ | BracketLeft | | ] | BracketRight | | \ | Backslash | | ` | Backquote | | Escape | Alias Esc | | - | Minus | | = | Equal | | + | Add on numpad. not Shift+= | | * | Multiple on numpad. not Shift+8 | | Backspace | Backspace | | Delete | Alias Del | | Tab | Tab | | CapsLock | Capslock | | Enter | Enter or Enter on numpad. | | ArrowUp | ArrowUp | | ArrowDown | ArrowDown | | ArrowLeft | ArrowLeft | | ArrowRight | ArrowRight | | Insert | Insert | | Home | Home | | End | End | | PageUp | PageUp | | PageDown | PageDown | | Space | Space |

Example

1. Register single key shortcut.

import React, { useEffect } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';

function App() {
  return (
    <ReactShortcutProvider>
      <Main />
    </ReactShortcutProvider>
  );
}

function Main() {
  const { registerShortcut, unregisterShortcut } = useShortcut();

  // RegisterShortcut should be invoked in useEffect.
  useEffect(() => {
    registerShortcut('a', (event) => {
      // event is the latest emitted keydown event.
      // you can invoke preventDefault to prevent browser default behavior.
      event.preventDefault();
      // invoked whenever key A pressed.
      console.log('You pressed A');
    });
    return () => {
      unregisterShortcut('Ctrl+a');
    };
  }, []);

  return <h1>Hello World!</h1>;
}

2. Register shortcut combined with modifier and normal key.

import React, { useEffect } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';

function App() {
  return (
    <ReactShortcutProvider>
      <Main />
    </ReactShortcutProvider>
  );
}

function Main() {
  const { registerShortcut, unregisterShortcut } = useShortcut();

  useEffect(() => {
    registerShortcut('Ctrl+a', (event) => {
      // invoked whenever key Control and key A  pressed.
      console.log('You pressed Ctrl and A');
    });
    return () => {
      unregisterShortcut('Ctrl+a');
    };
  }, []);

  return <h1>Hello World!</h1>;
}

3. Register scoped shortcut.

import React, { useEffect, useRef } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';

function App() {
  const scope1 = useRef<HTMLDivElement>(null);
  const scope2 = useRef<HTMLDivElement>(null);
  return (
    <div id="root">
      <ReactShortcutProvider options={{ scope: scope1 }}>
        <div ref={scope1} tabIndex={-1}>
          <Main />
        </div>
      </ReactShortcutProvider>
      <ReactShortcutProvider options={{ scope: scope2 }}>
        <div ref={scope2} tabIndex={-1}>
          <Main />
        </div>
        <Main />
      </ReactShortcutProvider>
    </div>
  );
}

function Main() {
  const { registerShortcut, unregisterShortcut } = useShortcut();

  useEffect(() => {
    registerShortcut('Ctrl+a', (event) => {
      // invoked whenever key Control and key A  pressed.
      console.log('You pressed Ctrl and A');
    });
    return () => {
      unregisterShortcut('Ctrl+a');
    };
  }, []);

  return <h1>Hello World!</h1>;
}

‼️ Important: Set element tabIndex property to -1 is to make this element to focusable. Scoped shortcut will not work without this.

4. Loose mode.

react-use-shortcuts work in strict mode by default, if you want to enable loose mode, you can set strict to false. it is only affect the getCurrentKeyPressed API.

import React, { useEffect } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';

function App() {
  return (
    <ReactShortcutProvider options={{ strict: false }}>
      <Main />
    </ReactShortcutProvider>
  );
}

function Main() {
  const { getElement, getCurrentKeyPressed } = useShortcut();

  useEffect(() => {
    const cb = () => {
      // if you pressed ControlLeft and A.
      // print ControlLeft+a in strict mode.
      // print Control+a in loose mode.
      console.log(getCurrentKeyPressed());
    };
    getElement()!.addEventListener('keydown', cb);
    return () => {
      getElement()!.removeEventListener('keydown', cb);
    };
  }, []);

  return <h1>Hello World!</h1>;
}

5. Normal keys combinations.

import React, { useEffect } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';

function App() {
  return (
    <ReactShortcutProvider>
      <Main />
    </ReactShortcutProvider>
  );
}

function Main() {
  const { registerShortcut, unregisterShortcut } = useShortcut();

  // RegisterShortcut should be invoked in useEffect.
  useEffect(() => {
    registerShortcut('a+b', (event) => {
      // invoked whenever key A pressed.
      console.log('You pressed A and B');
    });
    registerShortcut('a+a', (event) => {
      // invoked whenever key A pressed twice.
      console.log('You pressed A twice');
    });
    return () => {
      unregisterShortcut('a+b');
      unregisterShortcut('a+a');
    };
  }, []);

  return <h1>Hello World!</h1>;
}

6. Dynamic enable/disable shortcut.

import React, { useEffect, useCallback, useState } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';

function App() {
  return (
    <ReactShortcutProvider>
      <Main />
    </ReactShortcutProvider>
  );
}

function Main() {
  const { registerShortcut, unregisterShortcut, enableShortcut, disableShortcut } = useShortcut();
  const [enable, setEnable] = useState<boolean>(true);

  const handleClick = useCallback(() => {
    setEnable((prev) => {
      if (prev) {
        disableShortcut('Ctrl+a');
      } else {
        enableShortcut('Ctrl+a');
      }
      return !prev;
    });
  }, []);

  // RegisterShortcut should be invoked in useEffect.
  useEffect(() => {
    registerShortcut('Ctrl+a', (event) => {
      // invoked when key Control and key A pressed and enable is true.
      console.log('You pressed Control and A');
    });
    return () => {
      unregisterShortcut('Ctrl+a');
    };
  }, []);

  return <button onClick={handleClick}>{enable ? 'disable' : 'enable'}</button>;
}

6. Custom event filter, default behavior is filter event triggered by input/textarea/select or contentEditable element.

import React, { useEffect, useCallback, useState } from 'react';
import { ReactShortcutProvider, useShortcut } from 'react-use-shortcuts';

function App() {
  return (
    <ReactShortcutProvider options={{ filter: (event) => event.target.tagName !== 'INPUT' }}>
      <Main />
    </ReactShortcutProvider>
  );
}

function Main() {
  const { registerShortcut, unregisterShortcut } = useShortcut();

  useEffect(() => {
    registerShortcut('Ctrl+a', (event) => {
      // listener is not invoked when focus on input element
      console.log('You pressed Control and A');
    });
    return () => {
      unregisterShortcut('Ctrl+a');
    };
  }, []);

  return (
    <div>
      <input />
    </div>
  );
}

Some shortcut match rules example.

| Actions | Accelerator | Matched | | ------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ----------- | | press ControlLeft press AltLeft release AltLeft press A | Control+a | ✅ | | press ControlLeft press AltLeft press A | Control+a | ❌ | | press ControlRight press A | Control+a | ✅ | | press ControlRight press B release B press A | Control+a | ✅ | | press ControlLeft press A | ControlOrCommand+a | ✅ | | press MetaLeft press A | ControlOrCommand+a | ✅ | | press ControlLeft press 1 press 2 release 1 release 2 press A release A press B release B press C release C press D | Control+a+b+c+d | ✅ | | press ControlLeft press A release A press 1 release 1 press B release B press C release C press D | Control+a+b+c+d | ❌ | | press ControlLeft press A release A press A release A | Control+a+a | ✅ |

API Reference

Interface Definition

type Accelerator = string;
type Dispose = () => void;
type KeyboardEventListener = (event: KeyboardEvent) => void;

interface ReactShortcutOptions {
  // work mode, default to true.
  strict?: boolean;
  // print the debug message, default to false.
  debug?: boolean;
  // filter some event which does not want to handled.
  // default behavior is filter event triggered by input/textarea/select or contentEditable element.
  filter?: Filter;
  // the element to listen keyboard event. fallback to window if this options is not set.
  scope?: React.RefObject<HTMLElement>;
}

interface ReactShortcutProviderProps {
  options?: ReactShortcutOptions;
  children?: ReactNode;
}

interface ReactShortcutContextValue {
  registerShortcut(accelerator: Accelerator, callback: KeyboardEventListener): boolean;
  unregisterShortcut(accelerator: Accelerator): boolean;
  enableShortcut(accelerator: Accelerator): boolean;
  disableShortcut(accelerator: Accelerator): boolean;
  isShortcutRegistered(accelerator: Accelerator): boolean;
  getCurrentKeyPressed(): Accelerator;
  onKeydown(listener: KeyboardEventListener): Dispose;
  onKeyup(listener: KeyboardEventListener): Dispose;
}

Accelerator: string;

Shortcut description, consist of multiple modifiers or normal keys join with +,for example Ctrl+Alt+a. All supported keys have list above. The order of modifiers does not affect, so the Ctrl+Alt+a and Alt+Ctrl+a are exact the same. But Ctrl+Alt+a+b is not equal to Ctrl+Alt+b+a. Modifiers must preceding normal keys, a+Ctrl is invalid.

ReactShortcutProvider: React.FC<ReactShortcutProviderProps>;

React Context Provider of react-use-shortcuts. The most common used case is wrap in the root react component. You can also apply multiple ReactShortcutProvider to different part of your page to achieve scoped shortcut register.

useShortcut: () => ReactShortcutContextValue;

React Hook, used to get react-use-shortcuts API.

ReactShortcutContextValue.registerShortcut: (accelerator: Accelerator, callback: KeyboardEventListener) => boolean;

Register shortcut handler, return false if current shortcut has registered or current shortcut is invalid.

ReactShortcutContextValue.unregisterShortcut: (accelerator: Accelerator) => boolean;

Unregister shortcut handler, return false if current shortcut has not registered or shortcut is invalid.

ReactShortcutContextValue.enableShortcut: (accelerator: Accelerator) => boolean;

enable shortcut, return false if current shortcut has not registered or shortcut is invalid.

ReactShortcutContextValue.disableShortcut: (accelerator: Accelerator) => boolean;

disable shortcut, return false if current shortcut has not registered or shortcut is invalid.

ReactShortcutContextValue.isShortcutRegistered: (accelerator: Accelerator) => boolean;

Return true is current short has registered.

ReactShortcutContextValue.getCurrentKeyPressed: () => Accelerator;

Return current keys pressed.

ReactShortcutContextValue.onKeydown: (listener: KeyboardEventListener) => Dispose;

Register keydown keyboardEvent listener on element attached, unlike registerShortcut, listener will be invoked whenever key pressed.

ReactShortcutContextValue.onKeyup: (listener: KeyboardEventListener) => Dispose;

Register keyup keyboardEvent listener on element attached, unlike registerShortcut, listener will be invoked whenever key released.If you pressed Command key on MacOS, the keyup event may be not triggered because it is a browser default behavior, more detail see: https://github.com/electron/electron/issues/5188.

Browser Compatibility

  • Chrome ≥ 48

  • Firefox ≥ 38

  • Safari ≥ 10.1

  • Edge ≥ 79

Alternatives

Comparisons

| Features | react-use-shortcuts | react-hotkeys-hook | react-hot-keys | | ------------------------------------------ | ----------------------- | ---------------------- | ------------------ | | Dynamic register | ✅ | ❌ | ❌ | | Page scoped register | ✅ | ✅ | ❌ | | Strict/Loose mode | ✅ | ❌ | ❌ | | Dynamic enable/disable shortcut registered | ✅ | ✅ | ❌ | | Normal key combinations | ✅ | ✅ | ✅ | | Namespace | ❌ | ❌ | ✅ | | Shortcuts validation | ✅ | ❌ | ❌ | | Used React ≤ 16.8.0 | ❌ | ❌ | ✅ |

License

Distributed under the MIT License. See LICENSE for more information.