diff --git a/src/HotkeysProvider.tsx b/src/HotkeysProvider.tsx index a663f287..02bb8bb9 100644 --- a/src/HotkeysProvider.tsx +++ b/src/HotkeysProvider.tsx @@ -1,6 +1,7 @@ import { Hotkey } from './types' -import { createContext, ReactNode, useMemo, useState, useContext } from 'react' +import { createContext, ReactNode, useState, useContext, useCallback } from 'react' import BoundHotkeysProxyProviderProvider from './BoundHotkeysProxyProvider' +import deepEqual from './deepEqual' export type HotkeysContextType = { hotkeys: ReadonlyArray @@ -32,41 +33,51 @@ export const HotkeysProvider = ({initiallyActiveScopes = ['*'], children}: Props const [internalActiveScopes, setInternalActiveScopes] = useState(initiallyActiveScopes?.length > 0 ? initiallyActiveScopes : ['*']) const [boundHotkeys, setBoundHotkeys] = useState([]); - const isAllActive = useMemo(() => internalActiveScopes.includes('*'), [internalActiveScopes]) + const enableScope = useCallback((scope: string) => { + setInternalActiveScopes((prev) => { + if (prev.includes('*')) { + return [scope] + } - const enableScope = (scope: string) => { - if (isAllActive) { - setInternalActiveScopes([scope]) - } else { - setInternalActiveScopes(Array.from(new Set([...internalActiveScopes, scope]))) - } - } + return Array.from(new Set([...prev, scope])) + }) + }, []) - const disableScope = (scope: string) => { - const scopes = internalActiveScopes.filter(s => s !== scope) + const disableScope = useCallback((scope: string) => { + setInternalActiveScopes((prev) => { + if (prev.filter(s => s !== scope).length === 0) { + return ['*'] + } else { + return prev.filter(s => s !== scope) + } + }) + }, []) - if (scopes.length === 0) { - setInternalActiveScopes(['*']) - } else { - setInternalActiveScopes(scopes) - } - } + const toggleScope = useCallback((scope: string) => { + setInternalActiveScopes((prev) => { + if (prev.includes(scope)) { + if (prev.filter(s => s !== scope).length === 0) { + return ['*'] + } else { + return prev.filter(s => s !== scope) + } + } else { + if (prev.includes('*')) { + return [scope] + } - const toggleScope = (scope: string) => { - if (internalActiveScopes.includes(scope)) { - disableScope(scope) - } else { - enableScope(scope) - } - } + return Array.from(new Set([...prev, scope])) + } + }) + }, []) - const addBoundHotkey = (hotkey: Hotkey) => { - setBoundHotkeys([...boundHotkeys, hotkey]) - } + const addBoundHotkey = useCallback((hotkey: Hotkey) => { + setBoundHotkeys((prev) => [...prev, hotkey]) + }, []) - const removeBoundHotkey = (hotkey: Hotkey) => { - setBoundHotkeys(boundHotkeys.filter(h => h.keys !== hotkey.keys)) - } + const removeBoundHotkey = useCallback((hotkey: Hotkey) => { + setBoundHotkeys((prev) => prev.filter(h => !deepEqual(h, hotkey))) + }, []) return ( diff --git a/src/useHotkeys.ts b/src/useHotkeys.ts index 2e718526..68efa22e 100644 --- a/src/useHotkeys.ts +++ b/src/useHotkeys.ts @@ -51,7 +51,7 @@ export default function useHotkeys( } // 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 - // REF IS THE ACTIVE ELEMENT. THIS IS A PROBLEM SINCE FOCUSED SUB COMPONENTS WONT TRIGGER THE HOTKEY. + // REF IS THE ACTIVE ELEMENT. THIS IS A PROBLEM SINCE FOCUSED SUB COMPONENTS WON'T TRIGGER THE HOTKEY. if (ref.current !== null && document.activeElement !== ref.current && !ref.current.contains(document.activeElement)) { stopPropagation(e) diff --git a/tests/HotkeysProvider.test.tsx b/tests/HotkeysProvider.test.tsx index 38a14ab4..ef719237 100644 --- a/tests/HotkeysProvider.test.tsx +++ b/tests/HotkeysProvider.test.tsx @@ -182,8 +182,14 @@ test('should update bound hotkeys when useHotkeys changes its scopes', () => { return useHotkeysContext() } - const wrapper = ({ children }: { children: ReactNode }) => {children} + const wrapper = ({ children }: { children: ReactNode }) => { + return ( + + {children} + + ) + } + const { result, rerender } = renderHook<{ scopes: string[] }, HotkeysContextType>(({ scopes }) => useIntegratedHotkeys(scopes), { // @ts-ignore wrapper,