1.0.0 • Published 2 years ago

react-use-shortcuts v1.0.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 years ago

react-use-shortcuts

npm.io npm.io npm.io npm.io


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

KeyAliasNotes
ControlLeftCtrl Control ControlOrCommand
ControlRightCtrl Control ControlOrCommand
MetaLeftCommand ConmandLeft ControlOrCommandWindows on Windows, Command on MacOS
MetaRightCommand ConmandRight ControlOrCommandWindows on Windows, Command on MacOS
ShiftLeftShift
ShiftRightShift
AltLeftOption OptionLeftOption is only available on MacOS.
AltRightOption OptionRightOption is only available on MacOS.

Normal Keys

KeyNotes
0 ~ 9Number keys on keyboard main area or numpad area.
a ~ zAlphabet keys
F1~F12Function keys
,Comma
.Period or Decimal on numpad
/Slash
;Semicolon
'Quote
[BracketLeft
]BracketRight
\ | Backslash
`Backquote
EscapeAlias Esc
-Minus
=Equal
+Add on numpad. not Shift+=
*Multiple on numpad. not Shift+8
BackspaceBackspace
DeleteAlias Del
TabTab
CapsLockCapslock
EnterEnter or Enter on numpad.
ArrowUpArrowUp
ArrowDownArrowDown
ArrowLeftArrowLeft
ArrowRightArrowRight
InsertInsert
HomeHome
EndEnd
PageUpPageUp
PageDownPageDown
SpaceSpace

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.

ActionsAcceleratorMatched
press ControlLeft press AltLeft release AltLeft press AControl+a
press ControlLeft press AltLeft press AControl+a
press ControlRight press AControl+a
press ControlRight press B release B press AControl+a
press ControlLeft press AControlOrCommand+a
press MetaLeft press AControlOrCommand+a
press ControlLeft press 1 press 2 release 1 release 2 press A release A press B release B press C release C press DControl+a+b+c+d
press ControlLeft press A release A press 1 release 1 press B release B press C release C press DControl+a+b+c+d
press ControlLeft press A release A press A release AControl+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

Featuresreact-use-shortcutsreact-hotkeys-hookreact-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.