@github/hotkey
Advanced tools
Comparing version 1.6.1 to 2.0.0
export default function hotkey(event) { | ||
const elideShift = event.code.startsWith('Key') && event.shiftKey && event.key.toUpperCase() === event.key; | ||
return `${event.ctrlKey ? 'Control+' : ''}${event.altKey ? 'Alt+' : ''}${event.metaKey ? 'Meta+' : ''}${event.shiftKey && !elideShift ? 'Shift+' : ''}${event.key}`; | ||
const { ctrlKey, altKey, metaKey, key } = event; | ||
const hotkeyString = []; | ||
const modifiers = [ctrlKey, altKey, metaKey, showShift(event)]; | ||
for (const [i, mod] of modifiers.entries()) { | ||
if (mod) | ||
hotkeyString.push(modifierKeyNames[i]); | ||
} | ||
if (!modifierKeyNames.includes(key)) { | ||
hotkeyString.push(key); | ||
} | ||
return hotkeyString.join('+'); | ||
} | ||
const modifierKeyNames = [`Control`, 'Alt', 'Meta', 'Shift']; | ||
function showShift(event) { | ||
const { shiftKey, code, key } = event; | ||
return shiftKey && !(code.startsWith('Key') && key.toUpperCase() === key); | ||
} |
@@ -97,9 +97,47 @@ class Leaf { | ||
function expandHotkeyToEdges(hotkey) { | ||
return hotkey.split(',').map(edge => edge.split(' ')); | ||
const output = []; | ||
let acc = ['']; | ||
let commaIsSeparator = false; | ||
for (let i = 0; i < hotkey.length; i++) { | ||
if (commaIsSeparator && hotkey[i] === ',') { | ||
output.push(acc); | ||
acc = ['']; | ||
commaIsSeparator = false; | ||
continue; | ||
} | ||
if (hotkey[i] === ' ') { | ||
acc.push(''); | ||
commaIsSeparator = false; | ||
continue; | ||
} | ||
else if (hotkey[i] === '+') { | ||
commaIsSeparator = false; | ||
} | ||
else { | ||
commaIsSeparator = true; | ||
} | ||
acc[acc.length - 1] += hotkey[i]; | ||
} | ||
output.push(acc); | ||
return output.map(h => h.filter(k => k !== '')).filter(h => h.length > 0); | ||
} | ||
function hotkey(event) { | ||
const elideShift = event.code.startsWith('Key') && event.shiftKey && event.key.toUpperCase() === event.key; | ||
return `${event.ctrlKey ? 'Control+' : ''}${event.altKey ? 'Alt+' : ''}${event.metaKey ? 'Meta+' : ''}${event.shiftKey && !elideShift ? 'Shift+' : ''}${event.key}`; | ||
const { ctrlKey, altKey, metaKey, key } = event; | ||
const hotkeyString = []; | ||
const modifiers = [ctrlKey, altKey, metaKey, showShift(event)]; | ||
for (const [i, mod] of modifiers.entries()) { | ||
if (mod) | ||
hotkeyString.push(modifierKeyNames[i]); | ||
} | ||
if (!modifierKeyNames.includes(key)) { | ||
hotkeyString.push(key); | ||
} | ||
return hotkeyString.join('+'); | ||
} | ||
const modifierKeyNames = [`Control`, 'Alt', 'Meta', 'Shift']; | ||
function showShift(event) { | ||
const { shiftKey, code, key } = event; | ||
return shiftKey && !(code.startsWith('Key') && key.toUpperCase() === key); | ||
} | ||
@@ -106,0 +144,0 @@ const hotkeyRadixTrie = new RadixTrie(); |
@@ -25,3 +25,27 @@ export function isFormField(element) { | ||
export function expandHotkeyToEdges(hotkey) { | ||
return hotkey.split(',').map(edge => edge.split(' ')); | ||
const output = []; | ||
let acc = ['']; | ||
let commaIsSeparator = false; | ||
for (let i = 0; i < hotkey.length; i++) { | ||
if (commaIsSeparator && hotkey[i] === ',') { | ||
output.push(acc); | ||
acc = ['']; | ||
commaIsSeparator = false; | ||
continue; | ||
} | ||
if (hotkey[i] === ' ') { | ||
acc.push(''); | ||
commaIsSeparator = false; | ||
continue; | ||
} | ||
else if (hotkey[i] === '+') { | ||
commaIsSeparator = false; | ||
} | ||
else { | ||
commaIsSeparator = true; | ||
} | ||
acc[acc.length - 1] += hotkey[i]; | ||
} | ||
output.push(acc); | ||
return output.map(h => h.filter(k => k !== '')).filter(h => h.length > 0); | ||
} |
{ | ||
"name": "@github/hotkey", | ||
"version": "1.6.1", | ||
"version": "2.0.0", | ||
"description": "", | ||
@@ -17,4 +17,3 @@ "main": "dist/index.js", | ||
"pretest": "npm run build", | ||
"prepublishOnly": "npm run build", | ||
"postpublish": "npm publish --ignore-scripts --@github:registry='https://npm.pkg.github.com'" | ||
"prepublishOnly": "npm run build" | ||
}, | ||
@@ -37,3 +36,3 @@ "files": [ | ||
"karma-mocha": "^2.0.1", | ||
"karma-mocha-reporter": "^2.2.5", | ||
"karma-mocha-reporter": "^2.2.3", | ||
"mocha": "^8.3.2", | ||
@@ -40,0 +39,0 @@ "rollup": "^2.4.0", |
# Hotkey Behavior | ||
Trigger an action on a target element when a key or sequence of keys is pressed | ||
```html | ||
<button data-hotkey="Shift+?">Show help dialog</button> | ||
``` | ||
Trigger an action on a target element when a key, or sequence of keys, is pressed | ||
on the keyboard. This triggers a focus event on form fields, or a click event on | ||
others. | ||
other elements. | ||
@@ -11,37 +15,6 @@ By default, hotkeys are extracted from a target element's `data-hotkey` | ||
Multiple hotkeys are separated by a `,`; key combinations are separated | ||
by a `+`; and key sequences are separated by a space. | ||
## How is this used on GitHub? | ||
Two-keypress sequences such as `g c` and `g i` are stored | ||
under the 'g' key in a nested object with 'c' and 'i' keys. | ||
All shortcuts (for example `g i`, `.`, `Meta+k`) within GitHub use hotkey to declare shortcuts in server side templates. This is used on almost every page on GitHub. | ||
``` | ||
mappings = | ||
'c' : <a href="/rails/rails/issues/new" data-hotkey="c">New Issue</a> | ||
'g' : | ||
'c' : <a href="/rails/rails" data-hotkey="g c">Code</a> | ||
'i' : <a href="/rails/rails/issues" data-hotkey="g i">Issues</a> | ||
``` | ||
In this example, both `g c` and `c` could be available as hotkeys on the | ||
same page, but `g c` and `g` can't coexist. If the user presses | ||
`g`, the `c` hotkey will be unavailable for 1500 ms while we | ||
wait for either `g c` or `g i`. | ||
## Accessibility considerations | ||
### Character Key Shortcuts | ||
Please note that adding this functionality to your site can be a drawback for | ||
certain users. Providing a way in your system to disable hotkeys or remap | ||
them makes sure that those users can still use your site (given that it's | ||
accessible to those users). | ||
See ["Understanding Success Criterion 2.1.4: Character Key Shortcuts"](https://www.w3.org/WAI/WCAG21/Understanding/character-key-shortcuts.html) | ||
for further reading on this topic. | ||
### Interactive Elements | ||
Wherever possible, hotkeys should be add to [interactive and focusable elements](https://html.spec.whatwg.org/#interactive-content). If a static element must be used, please follow the guideline in ["Adding keyboard-accessible actions to static HTML elements"](https://www.w3.org/WAI/WCAG21/Techniques/client-side-script/SCR29.html). | ||
## Installation | ||
@@ -54,3 +27,2 @@ | ||
## Usage | ||
### HTML | ||
@@ -96,2 +68,55 @@ | ||
## Hotkey string format | ||
1. Hotkey matches against the `event.key`, and uses standard W3C key names for keys and modifiers as documented in [UI Events KeyboardEvent key Values](https://www.w3.org/TR/uievents-key/). | ||
2. At minimum a hotkey string must specify one bare key. | ||
3. Multiple hotkeys (aliases) are separated by a `,`. For example the hotkey `a,b` would activate if the user typed `a` or `b`. | ||
4. Multiple keys separated by a blank space represent a key sequence. For example the hotkey `g n` would activate when a user types the `g` key followed by the `n` key. | ||
5. Modifier key combos are separated with a `+` and are prepended to a key in a consistent order as follows: `Control+Alt+Meta+Shift+KEY`. | ||
6. You can use the comma key `,` as a hotkey, e.g. `a,,` would activate if the user typed `a` or `,`. `Control+,,x` would activate for `Control+,` or `x`. | ||
### Example | ||
The following hotkey would match if the user typed the key sequence `a` and then `b`, OR if the user held down the `Control`, `Alt` and `/` keys at the same time. | ||
```js | ||
"a b,Control+Alt+/" | ||
``` | ||
🔬 **Hotkey Mapper** is a tool to help you determine the correct hotkey string for your key combination: https://github.github.io/hotkey/examples/hotkey_mapper.html | ||
#### Key-sequence considerations | ||
Two-key-sequences such as `g c` and `g i` are stored | ||
under the 'g' key in a nested object with 'c' and 'i' keys. | ||
``` | ||
mappings = | ||
'c' : <a href="/rails/rails/issues/new" data-hotkey="c">New Issue</a> | ||
'g' : | ||
'c' : <a href="/rails/rails" data-hotkey="g c">Code</a> | ||
'i' : <a href="/rails/rails/issues" data-hotkey="g i">Issues</a> | ||
``` | ||
In this example, both `g c` and `c` could be available as hotkeys on the | ||
same page, but `g c` and `g` can't coexist. If the user presses | ||
`g`, the `c` hotkey will be unavailable for 1500 ms while we | ||
wait for either `g c` or `g i`. | ||
## Accessibility considerations | ||
### Character Key Shortcuts | ||
Please note that adding this functionality to your site can be a drawback for | ||
certain users. Providing a way in your system to disable hotkeys or remap | ||
them makes sure that those users can still use your site (given that it's | ||
accessible to those users). | ||
See ["Understanding Success Criterion 2.1.4: Character Key Shortcuts"](https://www.w3.org/WAI/WCAG21/Understanding/character-key-shortcuts.html) | ||
for further reading on this topic. | ||
### Interactive Elements | ||
Wherever possible, hotkeys should be add to [interactive and focusable elements](https://html.spec.whatwg.org/#interactive-content). If a static element must be used, please follow the guideline in ["Adding keyboard-accessible actions to static HTML elements"](https://www.w3.org/WAI/WCAG21/Techniques/client-side-script/SCR29.html). | ||
## Development | ||
@@ -98,0 +123,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
18924
377
129
1