Skip to content

Commit

Permalink
Merge pull request #875 from JohannesKlauss/fix/853
Browse files Browse the repository at this point in the history
Fix rerender when using HotkeysProvider and a bug in removing bound h…
  • Loading branch information
JohannesKlauss authored Dec 22, 2022
2 parents 9793506 + 7d245e6 commit b1377e8
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 33 deletions.
71 changes: 41 additions & 30 deletions src/HotkeysProvider.tsx
Original file line number Diff line number Diff line change
@@ -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<Hotkey>
Expand Down Expand Up @@ -32,41 +33,51 @@ export const HotkeysProvider = ({initiallyActiveScopes = ['*'], children}: Props
const [internalActiveScopes, setInternalActiveScopes] = useState(initiallyActiveScopes?.length > 0 ? initiallyActiveScopes : ['*'])
const [boundHotkeys, setBoundHotkeys] = useState<Hotkey[]>([]);

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 (
<HotkeysContext.Provider value={{enabledScopes: internalActiveScopes, hotkeys: boundHotkeys, enableScope, disableScope, toggleScope}}>
Expand Down
2 changes: 1 addition & 1 deletion src/useHotkeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function useHotkeys<T extends HTMLElement>(
}

// 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)

Expand Down
10 changes: 8 additions & 2 deletions tests/HotkeysProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,14 @@ test('should update bound hotkeys when useHotkeys changes its scopes', () => {
return useHotkeysContext()
}

const wrapper = ({ children }: { children: ReactNode }) => <HotkeysProvider
initiallyActiveScopes={['foo']}>{children}</HotkeysProvider>
const wrapper = ({ children }: { children: ReactNode }) => {
return (
<HotkeysProvider initiallyActiveScopes={['foo']}>
{children}
</HotkeysProvider>
)
}

const { result, rerender } = renderHook<{ scopes: string[] }, HotkeysContextType>(({ scopes }) => useIntegratedHotkeys(scopes), {
// @ts-ignore
wrapper,
Expand Down

0 comments on commit b1377e8

Please sign in to comment.