Refactor KeyboardShortcut component for improved key handling and logging
- Updated the `parseShortcut` function to utilize a modifier aliases object for better readability and maintainability. - Introduced `getPressedKey` function to streamline key detection logic. - Replaced the previous shortcut key set management with a more efficient use of `useMemo` and `useState` for tracking pressed keys. - Added optional `log` prop to enable logging of key events for debugging purposes. - Enhanced the overall structure and performance of the KeyboardShortcut component.
This commit is contained in:
parent
0e0accc115
commit
9d3de7bc01
@ -1,62 +1,76 @@
|
||||
import { useEffect, useRef, cloneElement, useCallback } from 'react'
|
||||
import { useEffect, useRef, cloneElement, useMemo, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Popover, Typography } from 'antd'
|
||||
|
||||
// Utility to parse shortcut string like 'cmd+shift+p' or 'ctrl+s'
|
||||
const MODIFIER_ALIASES = {
|
||||
cmd: 'meta',
|
||||
meta: 'meta',
|
||||
ctrl: 'control',
|
||||
control: 'control',
|
||||
alt: 'alt',
|
||||
option: 'alt',
|
||||
shift: 'shift'
|
||||
}
|
||||
|
||||
function parseShortcut(shortcut) {
|
||||
const parts = shortcut.toLowerCase().split('+')
|
||||
return {
|
||||
meta: parts.includes('cmd') || parts.includes('meta') || false,
|
||||
ctrl: parts.includes('ctrl') || parts.includes('control') || false,
|
||||
alt: parts.includes('alt') || parts.includes('option') || false,
|
||||
shift: parts.includes('shift') || false,
|
||||
key: parts.find(
|
||||
(p) =>
|
||||
!['cmd', 'meta', 'ctrl', 'control', 'alt', 'option', 'shift'].includes(
|
||||
p
|
||||
)
|
||||
)[0]
|
||||
}
|
||||
return shortcut
|
||||
.toLowerCase()
|
||||
.split('+')
|
||||
.map((part) => MODIFIER_ALIASES[part] ?? part)
|
||||
}
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
const KeyboardShortcut = ({ shortcut, children, hint, onTrigger }) => {
|
||||
const childRef = useRef()
|
||||
const shortcutObj = parseShortcut(shortcut)
|
||||
|
||||
// Helper to get the set of keys required for the shortcut
|
||||
const getShortcutKeySet = useCallback((shortcutObj) => {
|
||||
const keys = []
|
||||
if (shortcutObj.meta) keys.push('Meta')
|
||||
if (shortcutObj.ctrl) keys.push('Control')
|
||||
if (shortcutObj.alt) keys.push('Alt')
|
||||
if (shortcutObj.shift) keys.push('Shift')
|
||||
// shortcutObj.code is like 'keyp', so extract the last char
|
||||
if (shortcutObj.key) {
|
||||
keys.push('Key' + shortcutObj.key.toUpperCase())
|
||||
}
|
||||
return new Set(keys)
|
||||
}, [])
|
||||
|
||||
const shortcutKeySet = getShortcutKeySet(shortcutObj)
|
||||
|
||||
useEffect(() => {
|
||||
const pressedKeys = new Set()
|
||||
const handleKeyDown = (event) => {
|
||||
function getPressedKey(event) {
|
||||
if (
|
||||
event.key === 'Meta' ||
|
||||
event.key === 'Control' ||
|
||||
event.key === 'Alt' ||
|
||||
event.key === 'Shift'
|
||||
) {
|
||||
pressedKeys.add(event.key)
|
||||
} else if (event.code && event.code.startsWith('Key')) {
|
||||
pressedKeys.add(event.code)
|
||||
return event.key.toLowerCase()
|
||||
}
|
||||
if (event.code) {
|
||||
const code = event.code.toLowerCase()
|
||||
if (code.startsWith('key')) {
|
||||
return code.slice(3)
|
||||
}
|
||||
return code
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const { Text } = Typography
|
||||
|
||||
const KeyboardShortcut = ({
|
||||
shortcut,
|
||||
children,
|
||||
hint,
|
||||
onTrigger,
|
||||
log = false
|
||||
}) => {
|
||||
const childRef = useRef()
|
||||
const pressedKeysRef = useRef(new Set())
|
||||
const shortcutKeys = useMemo(() => parseShortcut(shortcut), [shortcut])
|
||||
const [pressedKeys, setPressedKeys] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
const syncPressedKeys = () => {
|
||||
setPressedKeys([...pressedKeysRef.current])
|
||||
}
|
||||
|
||||
const handleKeyDown = (event) => {
|
||||
const key = getPressedKey(event)
|
||||
if (key) {
|
||||
pressedKeysRef.current.add(key)
|
||||
syncPressedKeys()
|
||||
}
|
||||
|
||||
if (log) {
|
||||
console.log('Key Down:', key, 'Pressed Keys:', [...pressedKeysRef.current])
|
||||
}
|
||||
|
||||
if (
|
||||
shortcutKeySet.size &&
|
||||
[...shortcutKeySet].every((k) => pressedKeys.has(k))
|
||||
shortcutKeys.length &&
|
||||
shortcutKeys.every((k) => pressedKeysRef.current.has(k))
|
||||
) {
|
||||
if (typeof onTrigger === 'function') {
|
||||
onTrigger(event)
|
||||
@ -64,27 +78,24 @@ const KeyboardShortcut = ({ shortcut, children, hint, onTrigger }) => {
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
const handleKeyUp = (event) => {
|
||||
if (
|
||||
event.key === 'Meta' ||
|
||||
event.key === 'Control' ||
|
||||
event.key === 'Alt' ||
|
||||
event.key === 'Shift'
|
||||
) {
|
||||
pressedKeys.delete(event.key)
|
||||
} else if (event.code && event.code.startsWith('Key')) {
|
||||
pressedKeys.delete(event.key.toUpperCase())
|
||||
const key = getPressedKey(event)
|
||||
if (key) {
|
||||
pressedKeysRef.current.delete(key)
|
||||
syncPressedKeys()
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
window.addEventListener('keyup', handleKeyUp)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
window.removeEventListener('keyup', handleKeyUp)
|
||||
}
|
||||
}, [shortcut, shortcutObj, onTrigger, shortcutKeySet])
|
||||
}, [shortcutKeys, onTrigger, log])
|
||||
|
||||
// Clone the child to attach a ref
|
||||
const element = cloneElement(children, { ref: childRef })
|
||||
|
||||
if (hint) {
|
||||
@ -108,7 +119,8 @@ KeyboardShortcut.propTypes = {
|
||||
shortcut: PropTypes.string.isRequired, // e.g. 'cmd+shift+p'
|
||||
onTrigger: PropTypes.func.isRequired,
|
||||
children: PropTypes.element.isRequired,
|
||||
hint: PropTypes.string // Optional, e.g. '⌘ ⇧ P'
|
||||
hint: PropTypes.string, // Optional, e.g. '⌘ ⇧ P'
|
||||
log: PropTypes.bool // Optional, e.g. true
|
||||
}
|
||||
|
||||
export default KeyboardShortcut
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user