GithubHelp home page GithubHelp logo

jamiebuilds / tinykeys Goto Github PK

View Code? Open in Web Editor NEW
3.5K 21.0 75.0 1.71 MB

A tiny (~400 B) & modern library for keybindings.

Home Page: https://jamiebuilds.github.io/tinykeys/

License: MIT License

HTML 51.70% TypeScript 48.30%

tinykeys's Introduction

tinykeys

A tiny (~400 B) & modern library for keybindings. See Demo

Install

npm install --save tinykeys

Or for a CDN version, you can use it on unpkg.com

Usage

import { tinykeys } from "tinykeys" // Or `window.tinykeys` using the CDN version

tinykeys(window, {
  "Shift+D": () => {
    alert("The 'Shift' and 'd' keys were pressed at the same time")
  },
  "y e e t": () => {
    alert("The keys 'y', 'e', 'e', and 't' were pressed in order")
  },
  "$mod+KeyD": event => {
    event.preventDefault()
    alert("Either 'Control+d' or 'Meta+d' were pressed")
  },
})

Alternatively, if you want to only create the keybinding handler, and register it as an event listener yourself:

import { createKeybindingsHandler } from "tinykeys"

let handler = createKeybindingsHandler({
  "Shift+D": () => {
    alert("The 'Shift' and 'd' keys were pressed at the same time")
  },
  "y e e t": () => {
    alert("The keys 'y', 'e', 'e', and 't' were pressed in order")
  },
  "$mod+KeyD": event => {
    event.preventDefault()
    alert("Either 'Control+d' or 'Meta+d' were pressed")
  },
})

window.addEventListener("keydown", handler)

React Hooks Example

If you're using tinykeys within a component, you should also make use of the returned unsubscribe() function.

import { useEffect } from "react"
import { tinykeys } from "tinykeys"

function useKeyboardShortcuts() {
  useEffect(() => {
    let unsubscribe = tinykeys(window, {
      // ...
    })
    return () => {
      unsubscribe()
    }
  })
}

Commonly used key's and code's

Keybindings will be matched against KeyboardEvent.key andKeyboardEvent.code which may have some names you don't expect.

Windows macOS key code
N/A Command / Meta MetaLeft / MetaRight
Alt Option / Alt AltLeft / AltRight
Control Control / ^ Control ControlLeft / ControlRight
Shift Shift Shift ShiftLeft / ShiftRight
Space Space N/A Space
Enter Return Enter Enter
Esc Esc Escape Escape
1, 2, etc 1, 2, etc 1, 2, etc Digit1, Digit2, etc
a, b, etc a, b, etc a, b, etc KeyA, KeyB, etc
- - - Minus
= = = Equal
+ + + Equal*

Something missing? Check out the key logger on the demo website

* Some keys will have the same code as others because they appear on the same key on the keyboard. Keep in mind how this is affected by international keyboards which may have different layouts.

Key aliases

In some instances, tinykeys will alias keys depending on the platform to simplify cross-platform keybindings on international keyboards.

AltGraph (modifier)

On Windows, on many non-US standard keyboard layouts, there is a key named Alt Gr or AltGraph in the browser, in some browsers, pressing Control+Alt will report AltGraph as being pressed instead.

Similarly on macOS, the Alt (Option) key will sometimes be reported as the AltGraph key.

Note: The purpose of the Alt Gr key is to type "Alternate Graphics" so you will often want to use the event.code (KeyS) for letters instead of event.key (S)

tinykeys(window, {
  "Control+Alt+KeyS": event => {
    // macOS: `Control+Alt+S` or `Control+AltGraph+S`
    // Windows: `Control+Alt+S` or `Control+AltGraph+S` or `AltGraph+S`
  },
  "$mod+Alt+KeyS": event => {
    // macOS: `Meta+Alt+S` or `Meta+AltGraph+S`
    // Windows: `Control+Alt+S` or `Control+AltGraph+S` or `AltGraph+S`
  },
})

Keybinding Syntax

Keybindings are made up of a sequence of presses.

A press can be as simple as a single key which matches against KeyboardEvent.code and KeyboardEvent.key (case-insensitive).

// Matches `event.key`:
"d"
// Matches: `event.code`:
"KeyD"

Presses can optionally be prefixed with modifiers which match against any valid value to KeyboardEvent.getModifierState().

"Control+d"
"Meta+d"
"Shift+D"
"Alt+KeyD"
"Meta+Shift+D"

There is also a special $mod modifier that makes it easy to support cross platform keybindings:

  • Mac: $mod = Meta (⌘)
  • Windows/Linux: $mod = Control
"$mod+D" // Meta/Control+D
"$mod+Shift+D" // Meta/Control+Shift+D

Keybinding Sequences

Keybindings can also consist of several key presses in a row:

"g i" // i.e. "Go to Inbox"
"g a" // i.e. "Go to Archive"
"ArrowUp ArrowUp ArrowDown ArrowDown ArrowLeft ArrowRight ArrowLeft ArrowRight B A"

Each press can optionally be prefixed with modifier keys:

"$mod+K $mod+1" // i.e. "Toggle Level 1"
"$mod+K $mod+2" // i.e. "Toggle Level 2"
"$mod+K $mod+3" // i.e. "Toggle Level 3"

Each press in the sequence must be pressed within 1000ms of the last.

Display the keyboard sequence

You can use the parseKeybinding method to get a structured representation of a keyboard shortcut. It can be useful when you want to display it in a fancier way than a plain string.

import { parseKeybinding } from "tinykeys"

let parsedShortcut = parseKeybinding("$mod+Shift+K $mod+1")

Results into:

[
  [["Meta", "Shift"], "K"],
  [["Meta"], "1"],
]

Additional Configuration Options

You can configure the behavior of tinykeys in a couple ways using a third options parameter.

tinykey(
  window,
  {
    M: toggleMute,
  },
  {
    event: "keyup",
  },
)

options.event

Valid values: "keydown", "keyup"

Key presses will listen to this event (default: "keydown").

Note: Do not pass "keypress", it is deprecated in browsers.

options.timeout

Keybinding sequences will wait this long between key presses before cancelling (default: 1000).

Note: Setting this value too low (i.e. 300) will be too fast for many of your users.

tinykeys's People

Contributors

after-finitude avatar cilice avatar dependabot[bot] avatar hamirmahal avatar itaditya avatar jamiebuilds avatar julionav avatar nus3 avatar progfay avatar sdkayy avatar siddharthkp avatar wachunei avatar wickynilliams avatar yuler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tinykeys's Issues

How can I trigger the different key combinations within a test?

First of all, congratulations on TinyKeys. It has been very helpful.

Now I need to write the tests of my application that's using the package and haven't been able to cover the code triggered by the shortcuts. How can I get that done?

I'm using Jest, Enzyme and Chai in my tests.

Thank you very much.

AltGraph

From MDN:

AltGraph (on Gecko)

  • Windows: Both Alt and Ctrl keys are pressed, or AltGr key is pressed.
  • Linux: Level 3 Shift key (or Level 5 Shift key) pressed
  • macOS: ⌥ Option key pressed
  • Android: Not supported

Note: This all seems to hold true across browsers in my testing in Chrome/Safari/Firefox.

This key isn't used on US keyboards, but is on the US International layout and many other regional layouts.

The problem arises when you have keyboard shortcuts like Ctrl+Alt+(key) on Windows. Browsers will report AltGraph and not Ctrl or Alt.

Today you can deal with that by writing keyboard shortcuts like:

tinykeys(window, {
  "Ctrl+Alt+S": doSomething,
  "AltGraph+KeyS": isWindows ? doSomething : noop, // "S" turns into "ß" (or something else depending on the layout)
})

However, I want to see if I can make this easier for everyone.

Options:

  • $ctrlAlt+KeyS (or some other name)
  • Ctrl+Alt+KeyS (Map AltGraph to Ctrl+Alt behind the scenes)
    • Downside: People might write Ctrl+Alt+S and miss the behavior of AltGraph

How to use in CDN mode ?

Hi Dear Friends, How to use in CDN mode?

<script src="https://unpkg.com/[email protected]/dist/tinykeys.umd.js"></script>

I have attached the file to the html document but I do not know what script to use, what to change this script?

tinykeys(window, {
"Shift+D": () => {
alert("The 'Shift' and 'd' keys were pressed at the same time")
},
"y e e t": () => {
alert("The keys 'y', 'e', 'e', and 't' were pressed in order")
},
"$mod+KeyD": event => {
event.preventDefault()
alert("Either 'Control+d' or 'Meta+d' were pressed")
},
})

Please provide a complete description and example of how to use in CDN mode.

tinykeys cannot work

Uncaught SyntaxError: The requested module '/node_modules/.vite/deps/tinykeys.js?v=88983d99' does not provide an export named 'default' (at useKeyboardShortcuts.ts:1:8)

import tinykeys from "tinykeys"

import { onMounted } from "vue"

export default function useKeyboardShortcuts() {
  onMounted(() => {
    tinykeys(window, {
      "Escape": () => {
        console.log("The 'Shift' and 'd' keys were pressed at the same time")
      }
    })
  })
}
tinykeys:
  specifier: ^2.1.0
  version: 2.1.0

[feat] Record next sequence of keys

Hello,
Is there a way to record the sequence of keys, after pressing "Shift+D" for example, and having a callback associated to it ?

I found this behavior on mousetrap/record quite useful to record a variable from user.

Env : React

tinykeys_1.default is not a function

With the latest version v1.3.0 I'm getting

TypeError: (0 , tinykeys_1.default) is not a function

in my webpack build and also in my unit tests that are running with ts-node/register/transpile-only. My import looks as in the previous versions

import tinykeys from 'tinykeys';

and I'm using TypeScript with esModuleInterop enabled.

How to use this in angular?

Would it be possible to show an angular snippet? I feel very lost translating pure Java libs into angular components..

Thanks.

navigator.platform "deprecated"

Just wondering what your thoughts are on platform being deprecated despite the fact that it's still supported everywhere.

I was having an internal debate about detecting which key is effectively Mod, and noticed you use navigator.platform to do just that.

Feel free to close, I just wanted to see if you were considering an alternative, as user agent parsing heuristics are bloated at best.

Expose the parse method

Often times, the keyboard shortcut assigned to a handler also needs to be displayed within the UI like CMD + Shift + g instead of just a plain string. For that we need to convert the shortcut string into a data structure. We also need to convert $mod into CMD vs Ctrl based on platform.

Possible Solutions-

  1. The parse method that is being used internally is almost doing the same thing. If it's exported and documented then people can import it in their application.
  2. The createKeybindingsHandler function can be configured to return a tuple where first item is the event handler and second argument is the parsed shortcuts.

I can raise a PR if the maintainers like this proposal.

Tinykeys 2

Opening this to track some thoughts about a potential next version of this package

Specifically I want to push developers to make more accessible keyboard shortcuts.

  • Declare keyboard shortcuts directly on elements (in the general case)
    • Only visible & focusable & clickable elements
  • Display the shortcut on the clickable element

I'm certain there are edge cases to this, but I think you should have to opt-out of these requirements.

Draft API

In React terms, it would look something like this:

<TinyKeys
  // tinykeys props
  binding="$mod+KeyS" bindingLabel="$mod+S" 
  // button props
  as={Button} onClick={save}
>
	Save
</TinyKeys>
<button>
  Save
  <kbd>Ctrl+S</kbd>
</button>
Save Ctrl+S

By putting the keyboard shortcut on the clickable element you know how the two types of interactions are related

I don't want to make this library very React specific though.

Add longpress detection feature

Can we add a long-press detection feature? Not sure if we can implement this using the current library.

I'm happy to help contribute to this feature.

Multiple key sequence binding

Hi! Absolutely love tinykeys.
I was wondering if there's a possibility to bind multiple shortcuts to the same function.
I would like the same function to be triggered if any of the sequences match.

So instead of writing

tinykeys(window, {
	"ArrowUp": (e) => {
		e.preventDefault();
		e.stopPropagation();
		//Do something here
	},
	"Equal": () => {
		e.preventDefault();
		e.stopPropagation();
		//Do something here
	},
	"Home": () => {
		e.preventDefault();
		e.stopPropagation();
		//Do something here
	},
});

I could do instead

tinykeys(window, {
	"ArrowUp, Equal, Home": () => {
		e.preventDefault();
		e.stopPropagation();
		//Do something here if either of ArrowUp, Equal or Home key were pressed
	},
});

Meta vs Cmd

On Apple keyboards, the ⌘ key is labelled "cmd" for Command. Meta is more often found on Linux.

It could reduce confusion for Apple keyboard users to accept Cmd as a modifier, and include it in $mod.

Image of an Apple US keyboard

Docs

Might want to consider adding something like the following to the docs

"Control+S": (e) => { e.preventDefault(); alert("The 'CTRL' and 'S' keys were pressed at the same time") },

Iframe events are not working

I am sending to the keybindings function an iframe body reference and the events are not fired.
I found out this is because of this check if (!(event instanceof KeyboardEvent)) { return }.
From what I read on MDN, instanceof doesn't work as expected when used between different contexts, such as different frames, because they have different execution environments so even the KeyboardEvent type is considered different.

Shift++ keybind not working

Hi,

I've been testing migrating keybinds in our application to tinykeys and I've run into the problem of 'Shift++' keybind not triggering at all. I've found similar problem in this issue #19 but the only solution is to hardcode it to Equals. This is a problem due to variety of possible keyboard layouts. In our application users can also set their own keybinds, which complicates the issue further.

I am a bit of a boomer, not really wrapping my head about contributing using pull requests etc. but I downloaded the repo and investigated the issue. It seems that the problem is in the line
let mods = press.split("+")
In case of any ++ keybind, this will do a double split, resulting in an array of
["Shift","",""]
Solution may be simple, as we can do the split using regexp, and only do this for first plus in a pair
let mods = press.split(/(?<!\+)\+/)
What do you guys think?

Provide a prebuilt version

Possibly provide a minified version as well. This would be useful for those not using a build system for small projects, which this library is excellent for.

Shortcut conflicts

If I have a shortcut that is g a (go to all mail) and a (archive) then they are both fired when I press g a I would expect only g a to fire in this case.

Incompatibility with iOS / Safari

Hey!

Trying to run a React App with tinykeys in iOS crashes with error "Invalid regular expression: invalid group specifier name"

I have tracked down the error to a regex in tiny keys

var n = e.split(/(?<!\+)\+/)

Is there a chance you could have a look at the issue? This is definitely a problem on the Apples side, but you know....

key combinations

Hey,

I am wondering why this is not working:

import tinykeys from 'tinykeys';

tinykeys(window, {
  "KeyD+KeyA": () => {
    console.log("a + d")
  }
});

Isn't it meant to use ~normal keys within combinations?

Typescript types not resolving when using tsconfig with `moduleResolution: "Bundler"`

Could not find a declaration file for module 'tinykeys'. './node_modules/tinykeys/dist/tinykeys.module.js' implicitly has an 'any' type.
  There are types at './node_modules/tinykeys/dist/tinykeys.d.ts', but this result could not be resolved when respecting package.json "exports". The 'tinykeys' library may need to update its package.json or typings.ts(7016)

Here's what I found while researching this issue:
microsoft/TypeScript#52363
https://arethetypeswrong.github.io/?p=tinykeys%402.1.0
https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/UntypedResolution.md
https://stackoverflow.com/questions/72457791/typescript-packages-that-ship-with-mjs-and-d-ts-but-without-d-mts-how-to-i

ability to create handler without adding event listener

In some cases, it is useful to just have access to the handler without it being registered as a listener. One use case is where you do not yet have access to the element instance, but wish to eagerly create a handler.

Something like:

import { createListener } from "tinykeys";

const handler = createListener(shortcuts)

// later
someElement.addEventListener("keydown", handler)

Is this something you would support being added? Happy to make a PR if so

keyup

This library looks very promising :) Is there any chance that you are going to support keyup/release?

Keybindings not working when input is focused inside iframe

Let's say a react component <Foo/> is rendered inside an iframe using react-frame-component. Now <Foo/> has a tinykeys hook registered. Everything works as expected. <Foo/> also has an input element. When the input element comes into focus, tinykeys doesn't work anymore. Even if the input gets out of focus, tinykeys doesn't work. As long as no input/contentEditable div is ever in focus, no issues. If it gets in focus even once, tinykeys stops working, until a page refresh. Where should I look at?

Since it's a little weird setup (I'm working on an extension using React), I'm not adding an issue-reproducible repo code. Based on what exactly would be useful for troubleshooting, I can create an issue-reproducible code snippet.

Thanks in advance!

ReferenceError: navigator is not defined

Hey

I noticed this when loading the library on the server, the navigator object does not exist and causes a crash when building the pages for projects like next/nuxt that have out of the box server rendering.

Codesandbox: https://codesandbox.io/s/lingering-dawn-v88hi?file=/pages/index.js (the library is imported in index.js

I managed to bypass this by lazy loading it on the client, but probably more people will get into the same issue. I'm wondering if it makes sense for the library to ignore the server environment with a check for window or navigator.

sidenote: really nice library, I've been looking for something like this for a while.

HELP: debounce keypress

Hi,
I was wondering if there are any plans on integrating debounce into the library?
or otherwise would you have recommendations on how to achieve this in some key combinations?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.