Skip to content

Commit 3fb8845

Browse files
author
Anonymous
committed
Add customLocationPrefixes config
1 parent 9974edc commit 3fb8845

File tree

5 files changed

+87
-53
lines changed

5 files changed

+87
-53
lines changed

README.md

Lines changed: 67 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ const MyNode = () => {
7373
const deleteNode = React.useCallback(() => {
7474
// logic here
7575
}, [])
76-
76+
7777
const handlers = {
7878
DELETE_NODE: deleteNode
7979
};
@@ -162,7 +162,7 @@ export default MyNode;
162162

163163
## Licenses
164164

165-
`react-hotkeys` is released under the [ISC License](/LICENSE).
165+
`react-hotkeys` is released under the [ISC License](/LICENSE).
166166

167167
However, please note: the source code found in the lib/vendor directory is under the MIT License - please see the license file for each directory for more information.
168168

@@ -171,7 +171,7 @@ However, please note: the source code found in the lib/vendor directory is under
171171
If you use React Hotkeys and it has saved you time or money, please consider contributing. You will be supporting `react-hotkeys` by supporting its maintainer.
172172

173173
Please see my [Patreon Page](https://www.patreon.com/aleckgreenham) for details of why your support is needed, and how it will be used.
174-
174+
175175
For recurring and publicly acknowledged support:
176176

177177
| Payment Option | Link/Address |
@@ -330,10 +330,10 @@ You can specify data used to display the application's key maps using the object
330330

331331
```javascript
332332
{
333-
SHOW_DIALOG: {
334-
name: 'Display keyboard shortcuts',
335-
sequence: 'shift+?',
336-
action: 'keyup'
333+
SHOW_DIALOG: {
334+
name: 'Display keyboard shortcuts',
335+
sequence: 'shift+?',
336+
action: 'keyup'
337337
}
338338
}
339339
```
@@ -342,10 +342,10 @@ If you want to also provide alternative key sequences for the same action, use t
342342

343343
```javascript
344344
{
345-
SHOW_DIALOG: {
346-
name: 'Display keyboard shortcuts',
347-
sequences: ['shift+?', { sequence: '`', action: 'keyup' }],
348-
action: 'keyup'
345+
SHOW_DIALOG: {
346+
name: 'Display keyboard shortcuts',
347+
sequences: ['shift+?', { sequence: '`', action: 'keyup' }],
348+
action: 'keyup'
349349
}
350350
}
351351
```
@@ -357,11 +357,11 @@ As a general rule, you should use the syntax that is the most brief, but still a
357357

358358
| Syntax Type | Use when you ... |
359359
| :--------------------------- | :------------------------------------------------------------------------------------------------------------------ |
360-
| String | Have a single key sequence and don't have any special requirements (Default case) |
360+
| String | Have a single key sequence and don't have any special requirements (Default case) |
361361
| Array of strings | Need alternative key maps that trigger the same action, and are happy with them triggering on the default key event |
362-
| Array of objects | Need alternative key maps that trigger the same action, and want to them to trigger on a different key event |
363-
| Object | Have a single key sequence and want to specify a different key event or display data |
364-
| Object (sequences attribute) | Have multiple key sequences that trigger the same action, and want to specify a different key event or display data |
362+
| Array of objects | Need alternative key maps that trigger the same action, and want to them to trigger on a different key event |
363+
| Object | Have a single key sequence and want to specify a different key event or display data |
364+
| Object (sequences attribute) | Have multiple key sequences that trigger the same action, and want to specify a different key event or display data |
365365

366366
#### Defining custom key codes
367367

@@ -373,8 +373,8 @@ import {configure} from 'react-hotkeys';
373373

374374
configure({
375375
customKeyCodes: {
376-
10009: 'BackTV'
377-
}
376+
10009: 'BackTV'
377+
},
378378
})
379379
```
380380

@@ -386,21 +386,38 @@ const keyMap = {
386386
};
387387
```
388388

389+
#### Handling different key locations
390+
391+
To distinguish between left/right variants of the same keys (e.g Ctrl, Shift, Numpad etc.), you can set a custom prefix for each location key as below:
392+
393+
```javascript
394+
configure({
395+
customLocationPrefixes: {
396+
1: "Left",
397+
2: "Right",
398+
3: "Numpad",
399+
}
400+
})
401+
```
402+
403+
KeyNames will then be prefixed according to the location, e.g, `shift` becomes `leftshift`
404+
405+
389406
#### Setting dynamic hotkeys at runtime
390407

391408
`react-hotkeys` has basic support for setting dynamic hotkeys - i.e. letting the user set their own keyboard shortcuts at runtime. Once you have set up the necessary UI for [viewing the current keyboard shortcuts](#displaying-a-list-of-available-hot-keys) (and opting to change them), you can then use the `recordKeyCombination` function to capture the keys the user wishes to use.
392409

393410
`recordKeyCombination` accepts a callback function that will be called on the last `keyup` of the next key combination - immediately after the user has pressed the key combination they wish to assign. The callback then unbinds itself, so you do not have to worry about tidying up after it.
394411

395-
`recordKeyCombination` returns a function you can call at any time after binding the listener, to cancel listening without waiting for the key combination to complete.
412+
`recordKeyCombination` returns a function you can call at any time after binding the listener, to cancel listening without waiting for the key combination to complete.
396413

397414
The callback function receives a single argument with the following schema:
398415

399416
```javascript
400417
{
401418
/**
402419
* Combination ID that can be passed to the keyMap prop to (re)define an
403-
* action's key sequence
420+
* action's key sequence
404421
*/
405422
id: '',
406423
/**
@@ -412,17 +429,17 @@ The callback function receives a single argument with the following schema:
412429
// Example:
413430

414431
{
415-
id: 'a',
432+
id: 'a',
416433
keys: { a: true }
417434
}
418435
```
419436

420437
If you are updating hotkeys without changing focus or remounting the component that defines them, you will need to make sure you use the [`allowChanges` prop](#hotkeys-component-api) to ensure the new keymaps are honoured immediately.
421-
422-
An example, rendering two dialogs:
438+
439+
An example, rendering two dialogs:
423440

424441
* One for displaying the application's key maps using the [getApplicationKeyMap](#displaying-a-list-of-available-hot-keys) function
425-
* Another for telling the user when to press the keys they want to bind to an action, meanwhile listening with `recordKeyCombination()`
442+
* Another for telling the user when to press the keys they want to bind to an action, meanwhile listening with `recordKeyCombination()`
426443

427444
```javascript
428445
import { recordKeyCombination } from 'react-hotkeys';
@@ -440,10 +457,10 @@ renderDialog(){
440457

441458
<table>
442459
<tbody>
443-
{
460+
{
444461
Object.keys(keyMap).reduce((memo, actionName) => {
445462
const { sequences, name } = keyMap[actionName];
446-
463+
447464
memo.push(
448465
<tr key={name || actionName}>
449466
<td style={styles.KEYMAP_TABLE_CELL}>
@@ -459,7 +476,7 @@ renderDialog(){
459476
</td>
460477
</tr>
461478
);
462-
479+
463480
return memo;
464481
})
465482
}
@@ -469,18 +486,18 @@ renderDialog(){
469486
);
470487
} else if (this.state.changingActionShortcut) {
471488
const { cancel } = this.state.changingActionShortcut;
472-
489+
473490
const keyMap = getApplicationKeyMap();
474491
const { name } = keyMap[this.state.changingActionShortcut];
475-
492+
476493
return (
477494
<div style={styles.DIALOG}>
478495
Press the keys you would like to bind to #{name}.
479-
496+
480497
<button onClick={cancel}>
481498
Cancel
482499
</button>
483-
</div>
500+
</div>
484501
);
485502
}
486503
}
@@ -492,24 +509,24 @@ showChangeShortcutDialog(actionName) {
492509
changingActionShortcut: null,
493510
keyMap: {
494511
...this.state.keyMap,
495-
[actionName]: id
512+
[actionName]: id
496513
}
497-
});
514+
});
498515
});
499-
516+
500517
this.setState({
501518
showShortcutsDialog: false,
502519
changingActionShortcut: {
503520
cancel: () => {
504521
cancelListening();
505-
522+
506523
this.setState({
507524
showShortcutsDialog: true,
508525
changingActionShortcut: null
509-
});
526+
});
510527
}
511528
}
512-
});
529+
});
513530
}
514531
```
515532

@@ -629,9 +646,9 @@ However, it [does require that its children be wrapped in a DOM-mounted node](#h
629646
* to get a reference to the node, so you can call .focus() on it
630647
*/
631648
innerRef: {undefined}
632-
649+
633650
/**
634-
* Whether this is the root HotKeys node - this enables some special
651+
* Whether this is the root HotKeys node - this enables some special
635652
* behaviour
636653
*/
637654
root={false}
@@ -791,7 +808,7 @@ The GlobalHotKeys component provides a declarative and native JSX syntax for def
791808

792809
### How nested key maps are matched
793810

794-
For keymaps defined with `<HotKeys/>` components, how close your `<HotKeys/>` component is to the element currently focused in the DOM has the greatest affect on how actions are resolved. Whenever a key event occurs (`keydown`, `keypress` or `keyup`), `react-hotkeys` starts at the `<HotKeys/>` component closest to the event's target (the focused element in the browser) and searches up through the hierarchy of focused `<HotKeys/>` components, examining each `keyMap` for actions for which the current key completes the specified combination or sequence.
811+
For keymaps defined with `<HotKeys/>` components, how close your `<HotKeys/>` component is to the element currently focused in the DOM has the greatest affect on how actions are resolved. Whenever a key event occurs (`keydown`, `keypress` or `keyup`), `react-hotkeys` starts at the `<HotKeys/>` component closest to the event's target (the focused element in the browser) and searches up through the hierarchy of focused `<HotKeys/>` components, examining each `keyMap` for actions for which the current key completes the specified combination or sequence.
795812

796813
Regardless of where `<GlobalHotKeys>` components appear in the render tree, they are matched with key events after the event has finished propagating through the React app (if the event originated in the React at all). This means if your React app is in focus and it handles a key event, it will be ignored by the `<GlobalHotKeys>` components.
797814

@@ -803,11 +820,11 @@ It is recommended to use `<HotKeys>` components whenever possible for better per
803820
804821
### How combinations and sequences are matched
805822

806-
For key combinations, the action only matches if the key is the last one needed to complete the combination. For sequences, the action matches for the last key to complete the last combination in the sequence.
823+
For key combinations, the action only matches if the key is the last one needed to complete the combination. For sequences, the action matches for the last key to complete the last combination in the sequence.
807824

808825
By default, sub-matches are disabled so if you have two actions bound to `cmd+a` and `a`, and you press the `cmd` key and then the `a` key (without releasing the `cmd` key), then the `cmd+a` combination is matched. This allows you to define longer, application-wide key combinations at the top of your app, without them being hidden by shorter context-dependent combinations in different parts of your app. However, it does depend on the order the keys are pressed: in the above example, if `a` was pressed first and then `cmd`, the `a` action would be matched. The trade-off for this behaviour is that combinations are not permitted to overlap: if you have two actions bound to `a` and `b` and the user presses `a` and then `b` without first releasing `a`, only the action associated with `a` will be called (because there are no actions associated with `a+b`). If you want allow sub-matches, you can use the [`allowCombinationSubmatches` configuration option](#configuration).
809826

810-
The match occurs on the key event you have specified when defining your keymap (the default is `keydown` if you have not overridden the [`defaultKeyEvent` configuration option](#configuration)).
827+
The match occurs on the key event you have specified when defining your keymap (the default is `keydown` if you have not overridden the [`defaultKeyEvent` configuration option](#configuration)).
811828

812829
Once a matching action is found, `react-hotkeys` then searches for the corresponding action handler.
813830

@@ -839,11 +856,11 @@ Regardless of which syntax you used to define the keymap, they always appear in
839856
/**
840857
* Optional attributes - only present if you defined them
841858
*/
842-
859+
843860
name: 'name',
844861
group: 'group',
845862
description: 'description',
846-
863+
847864
/**
848865
* Attributes always present
849866
* /
@@ -855,7 +872,7 @@ Regardless of which syntax you used to define the keymap, they always appear in
855872
// ...
856873
]
857874
},
858-
// ...
875+
// ...
859876
}
860877
```
861878

@@ -881,7 +898,7 @@ renderDialog(){
881898
<tbody>
882899
{ Object.keys(keyMap).reduce((memo, actionName) => {
883900
const { sequences, name } = keyMap[actionName];
884-
901+
885902
memo.push(
886903
<tr key={name || actionName}>
887904
<td style={styles.KEYMAP_TABLE_CELL}>
@@ -892,7 +909,7 @@ renderDialog(){
892909
</td>
893910
</tr>
894911
);
895-
912+
896913
return memo;
897914
})
898915
}
@@ -1229,7 +1246,7 @@ configure({
12291246
* @type {boolean}
12301247
*/
12311248
ignoreRepeatedEventsWhenKeyHeldDown: true,
1232-
1249+
12331250
/**
12341251
* Whether React HotKeys should simulate keypress events for the keys that do not
12351252
* natively emit them.
@@ -1258,14 +1275,14 @@ configure({
12581275
* allowed to propagate any further through the Render tree).
12591276
*/
12601277
stopEventPropagationAfterIgnoring: true,
1261-
1278+
12621279
/**
1263-
* Whether to allow combination submatches - e.g. if there is an action
1280+
* Whether to allow combination submatches - e.g. if there is an action
12641281
* bound to cmd, pressing shift+cmd will *not* trigger that action when
12651282
* allowCombinationSubmatches is false.
12661283
*/
12671284
allowCombinationSubmatches: false,
1268-
1285+
12691286
/**
12701287
* A mapping of custom key codes to key names that you can then use in your
12711288
* key sequences

index.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface ExtendedKeyMapOptions extends KeyMapOptions {
1616
sequences: Array<MouseTrapKeySequence> | Array<KeyMapOptions>;
1717
name?: string;
1818
group?: string;
19+
extraData?: string;
1920
description?: string;
2021
}
2122

@@ -318,6 +319,12 @@ export interface ConfigurationOptions {
318319
* key sequences
319320
*/
320321
customKeyCodes?: { [key: number]: string },
322+
323+
/**
324+
* A mapping of custom prefixes to prepend to key names according to event.location,
325+
* where 0 = Default, 1 = Left, 2 = Right, 3 = Numpad
326+
*/
327+
customLocationPrefixes?: { [key: 0 | 1 | 2 | 3]: string },
321328
}
322329

323330
/**

src/helpers/resolving-handlers/getKeyName.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import hasKey from '../../utils/object/hasKey';
99

1010
function keyNameFromEvent(event) {
1111
const customKeyCodes = Configuration.option('customKeyCodes');
12+
const customLocationPrefixes = Configuration.option('customLocationPrefixes');
1213

1314
// noinspection JSDeprecatedSymbols
1415
const keyCode = event.keyCode || event.charCode;
@@ -17,10 +18,17 @@ function keyNameFromEvent(event) {
1718
return customKeyCodes[keyCode];
1819
}
1920

21+
const location = event.location;
22+
23+
let locationPrefix = "";
24+
if (hasKey(customLocationPrefixes, location)) {
25+
locationPrefix = customLocationPrefixes[location]
26+
}
27+
2028
if (event.nativeEvent) {
21-
return event.key;
29+
return locationPrefix + event.key;
2230
} else {
23-
return reactsGetEventKey(event);
31+
return locationPrefix + reactsGetEventKey(event);
2432
}
2533
}
2634

src/lib/config/Configuration.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ const _defaultConfiguration = {
122122
* @type {Object.<Number, KeyName>}
123123
*/
124124
customKeyCodes: {},
125+
126+
customLocationPrefixes: {},
125127
};
126128

127129
const _configuration = {

src/lib/definitions/ApplicationKeyMapBuilder.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import copyAttributes from '../../utils/object/copyAttributes';
44
import arrayFrom from '../../utils/array/arrayFrom';
55

66
const SEQUENCE_ATTRIBUTES = ['sequence', 'action'];
7-
const KEYMAP_ATTRIBUTES = ['name', 'description', 'group'];
7+
const KEYMAP_ATTRIBUTES = ['name', 'description', 'group', 'extraData'];
88

99
function createSequenceFromConfig(keyMapConfig) {
1010
return arrayFrom(keyMapConfig).map((sequenceOrKeyMapOptions) => {

0 commit comments

Comments
 (0)