react-hotkeys-hook 4.0.0-1 → 4.0.0-4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ export default function useDeepEqualMemo<T>(value: T): T | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-hotkeys-hook",
3
- "version": "4.0.0-1",
3
+ "version": "4.0.0-4",
4
4
  "repository": "https://JohannesKlauss@github.com/JohannesKlauss/react-keymap-hook.git",
5
5
  "homepage": "https://johannesklauss.github.io/react-hotkeys-hook/",
6
6
  "author": "Johannes Klauss",
@@ -68,8 +68,11 @@
68
68
  "tslib": "2.4.0",
69
69
  "typescript": "4.7.4"
70
70
  },
71
+ "dependencies": {
72
+ "lodash": "4.17.11"
73
+ },
71
74
  "peerDependencies": {
72
- "lodash": ">=4.17.21",
75
+ "lodash": ">=4.17.0",
73
76
  "react": ">=16.8.1",
74
77
  "react-dom": ">=16.8.1"
75
78
  }
@@ -2,7 +2,7 @@ import { Hotkey } from './types'
2
2
  import { createContext, ReactNode, useMemo, useState, useContext } from 'react'
3
3
  import BoundHotkeysProxyProviderProvider from './BoundHotkeysProxyProvider'
4
4
 
5
- type HotkeysContextType = {
5
+ export type HotkeysContextType = {
6
6
  hotkeys: ReadonlyArray<Hotkey>
7
7
  activeScopes: string[]
8
8
  toggleScope: (scope: string) => void
@@ -13,7 +13,7 @@ type HotkeysContextType = {
13
13
  // The context is only needed for special features like global scoping, so we use a graceful default fallback
14
14
  const HotkeysContext = createContext<HotkeysContextType>({
15
15
  hotkeys: [],
16
- activeScopes: [], // This array has to be empty to check if the provider is set or not
16
+ activeScopes: [], // This array has to be empty instead of containing '*' as default, to check if the provider is set or not
17
17
  toggleScope: () => {},
18
18
  activateScope: () => {},
19
19
  deactivateScope: () => {},
@@ -14,7 +14,8 @@ export function parseHotkey(hotkey: string, combinationKey: string = '+'): Hotke
14
14
  const keys = hotkey
15
15
  .toLocaleLowerCase()
16
16
  .split(combinationKey)
17
- .map((k) => k.trim())
17
+ .map(k => k.trim())
18
+ .map(k => k === 'esc' ? 'escape' : k)
18
19
 
19
20
  const modifiers: KeyboardModifiers = {
20
21
  alt: keys.includes('alt'),
package/src/types.ts CHANGED
@@ -34,7 +34,7 @@ export type Options = {
34
34
  scopes?: Scopes // Scope
35
35
  keyup?: boolean // Trigger on keyup event? (Default: undefined)
36
36
  keydown?: boolean // Trigger on keydown event? (Default: true)
37
- preventDefault?: Trigger // Prevent default browser behavior? (Default: true)
37
+ preventDefault?: Trigger // Prevent default browser behavior? (Default: false)
38
38
  description?: string // Use this option to describe what the hotkey does. (Default: undefined)
39
39
  }
40
40
 
@@ -0,0 +1,12 @@
1
+ import { useRef } from 'react'
2
+ import { isEqual } from 'lodash'
3
+
4
+ export default function useDeepEqualMemo<T>(value: T) {
5
+ const ref = useRef<T | undefined>(undefined)
6
+
7
+ if (!isEqual(ref.current, value)) {
8
+ ref.current = value
9
+ }
10
+
11
+ return ref.current
12
+ }
package/src/useHotkeys.ts CHANGED
@@ -11,6 +11,13 @@ import {
11
11
  } from './validators'
12
12
  import { useHotkeysContext } from './HotkeysProvider'
13
13
  import { useBoundHotkeysProxy } from './BoundHotkeysProxyProvider'
14
+ import useDeepEqualMemo from './useDeepEqualMemo'
15
+
16
+ const stopPropagation = (e: KeyboardEvent): void => {
17
+ e.stopPropagation()
18
+ e.preventDefault()
19
+ e.stopImmediatePropagation()
20
+ }
14
21
 
15
22
  export default function useHotkeys<T extends HTMLElement>(
16
23
  keys: Keys,
@@ -25,35 +32,43 @@ export default function useHotkeys<T extends HTMLElement>(
25
32
  const _deps = options instanceof Array ? options : dependencies instanceof Array ? dependencies : []
26
33
 
27
34
  const cb = useCallback(callback, [..._deps])
28
- const ctx = useHotkeysContext()
35
+ const memoisedOptions = useDeepEqualMemo(_options)
29
36
 
37
+ const { activeScopes } = useHotkeysContext()
30
38
  const proxy = useBoundHotkeysProxy()
31
39
 
32
40
  useLayoutEffect(() => {
33
- if (_options?.enabled === false || !isScopeActive(ctx.activeScopes, _options?.scopes)) {
41
+ if (memoisedOptions?.enabled === false || !isScopeActive(activeScopes, memoisedOptions?.scopes)) {
34
42
  return
35
43
  }
36
44
 
37
45
  const listener = (e: KeyboardEvent) => {
38
- if (isKeyboardEventTriggeredByInput(e) && !isHotkeyEnabledOnTag(e, _options?.enableOnFormTags)) {
46
+ if (isKeyboardEventTriggeredByInput(e) && !isHotkeyEnabledOnTag(e, memoisedOptions?.enableOnFormTags)) {
39
47
  return
40
48
  }
41
49
 
50
+ // TODO: SINCE THE EVENT IS NOW ATTACHED TO THE REF, THE ACTIVE ELEMENT CAN NEVER BE INSIDE THE REF. THE HOTKEY ONLY TRIGGERS IF THE
51
+ // REF IS THE ACTIVE ELEMENT. THIS IS A PROBLEM SINCE FOCUSED SUB COMPONENTS WONT TRIGGER THE HOTKEY.
52
+
42
53
  if (ref.current !== null && document.activeElement !== ref.current && !ref.current.contains(document.activeElement)) {
54
+ stopPropagation(e)
55
+
43
56
  return
44
57
  }
45
58
 
46
- if (((e.target as HTMLElement)?.isContentEditable && !_options?.enableOnContentEditable)) {
59
+ if (((e.target as HTMLElement)?.isContentEditable && !memoisedOptions?.enableOnContentEditable)) {
47
60
  return
48
61
  }
49
62
 
50
- parseKeysHookInput(keys, _options?.splitKey).forEach((key) => {
51
- const hotkey = parseHotkey(key, _options?.combinationKey)
63
+ parseKeysHookInput(keys, memoisedOptions?.splitKey).forEach((key) => {
64
+ const hotkey = parseHotkey(key, memoisedOptions?.combinationKey)
52
65
 
53
66
  if (isHotkeyMatchingKeyboardEvent(e, hotkey, pressedDownKeys) || hotkey.keys?.includes('*')) {
54
- maybePreventDefault(e, hotkey, _options?.preventDefault)
67
+ maybePreventDefault(e, hotkey, memoisedOptions?.preventDefault)
68
+
69
+ if (!isHotkeyEnabled(e, hotkey, memoisedOptions?.enabled)) {
70
+ stopPropagation(e)
55
71
 
56
- if (!isHotkeyEnabled(e, hotkey, _options?.enabled)) {
57
72
  return
58
73
  }
59
74
 
@@ -65,15 +80,20 @@ export default function useHotkeys<T extends HTMLElement>(
65
80
  const handleKeyDown = (event: KeyboardEvent) => {
66
81
  pressedDownKeys.add(event.key.toLowerCase())
67
82
 
68
- if ((_options?.keydown === undefined && _options?.keyup !== true) || _options?.keydown) {
83
+ if ((memoisedOptions?.keydown === undefined && memoisedOptions?.keyup !== true) || memoisedOptions?.keydown) {
69
84
  listener(event)
70
85
  }
71
86
  }
72
87
 
73
88
  const handleKeyUp = (event: KeyboardEvent) => {
74
- pressedDownKeys.delete(event.key.toLowerCase())
89
+ if (event.key.toLowerCase() !== 'meta') {
90
+ pressedDownKeys.delete(event.key.toLowerCase())
91
+ } else {
92
+ // On macOS pressing down the meta key prevents triggering the keyup event for any other key https://stackoverflow.com/a/57153300/735226.
93
+ pressedDownKeys.clear()
94
+ }
75
95
 
76
- if (_options?.keyup) {
96
+ if (memoisedOptions?.keyup) {
77
97
  listener(event)
78
98
  }
79
99
  }
@@ -84,7 +104,7 @@ export default function useHotkeys<T extends HTMLElement>(
84
104
  (ref.current || document).addEventListener('keydown', handleKeyDown)
85
105
 
86
106
  if (proxy) {
87
- parseKeysHookInput(keys, _options?.splitKey).forEach((key) => proxy.addHotkey(parseHotkey(key, _options?.combinationKey)))
107
+ parseKeysHookInput(keys, memoisedOptions?.splitKey).forEach((key) => proxy.addHotkey(parseHotkey(key, memoisedOptions?.combinationKey)))
88
108
  }
89
109
 
90
110
  return () => {
@@ -94,10 +114,10 @@ export default function useHotkeys<T extends HTMLElement>(
94
114
  (ref.current || document).removeEventListener('keydown', handleKeyDown)
95
115
 
96
116
  if (proxy) {
97
- parseKeysHookInput(keys, _options?.splitKey).forEach((key) => proxy.removeHotkey(parseHotkey(key, _options?.combinationKey)))
117
+ parseKeysHookInput(keys, memoisedOptions?.splitKey).forEach((key) => proxy.removeHotkey(parseHotkey(key, memoisedOptions?.combinationKey)))
98
118
  }
99
119
  }
100
- }, [keys, cb, _options])
120
+ }, [keys, cb, memoisedOptions, activeScopes])
101
121
 
102
122
  return ref
103
123
  }