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 PropTypes from 'prop-types'
|
||||||
import { Popover, Typography } from 'antd'
|
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) {
|
function parseShortcut(shortcut) {
|
||||||
const parts = shortcut.toLowerCase().split('+')
|
return shortcut
|
||||||
return {
|
.toLowerCase()
|
||||||
meta: parts.includes('cmd') || parts.includes('meta') || false,
|
.split('+')
|
||||||
ctrl: parts.includes('ctrl') || parts.includes('control') || false,
|
.map((part) => MODIFIER_ALIASES[part] ?? part)
|
||||||
alt: parts.includes('alt') || parts.includes('option') || false,
|
}
|
||||||
shift: parts.includes('shift') || false,
|
|
||||||
key: parts.find(
|
function getPressedKey(event) {
|
||||||
(p) =>
|
if (
|
||||||
!['cmd', 'meta', 'ctrl', 'control', 'alt', 'option', 'shift'].includes(
|
event.key === 'Meta' ||
|
||||||
p
|
event.key === 'Control' ||
|
||||||
)
|
event.key === 'Alt' ||
|
||||||
)[0]
|
event.key === 'Shift'
|
||||||
|
) {
|
||||||
|
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 { Text } = Typography
|
||||||
|
|
||||||
const KeyboardShortcut = ({ shortcut, children, hint, onTrigger }) => {
|
const KeyboardShortcut = ({
|
||||||
|
shortcut,
|
||||||
|
children,
|
||||||
|
hint,
|
||||||
|
onTrigger,
|
||||||
|
log = false
|
||||||
|
}) => {
|
||||||
const childRef = useRef()
|
const childRef = useRef()
|
||||||
const shortcutObj = parseShortcut(shortcut)
|
const pressedKeysRef = useRef(new Set())
|
||||||
|
const shortcutKeys = useMemo(() => parseShortcut(shortcut), [shortcut])
|
||||||
// Helper to get the set of keys required for the shortcut
|
const [pressedKeys, setPressedKeys] = useState([])
|
||||||
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(() => {
|
useEffect(() => {
|
||||||
const pressedKeys = new Set()
|
const syncPressedKeys = () => {
|
||||||
|
setPressedKeys([...pressedKeysRef.current])
|
||||||
|
}
|
||||||
|
|
||||||
const handleKeyDown = (event) => {
|
const handleKeyDown = (event) => {
|
||||||
if (
|
const key = getPressedKey(event)
|
||||||
event.key === 'Meta' ||
|
if (key) {
|
||||||
event.key === 'Control' ||
|
pressedKeysRef.current.add(key)
|
||||||
event.key === 'Alt' ||
|
syncPressedKeys()
|
||||||
event.key === 'Shift'
|
|
||||||
) {
|
|
||||||
pressedKeys.add(event.key)
|
|
||||||
} else if (event.code && event.code.startsWith('Key')) {
|
|
||||||
pressedKeys.add(event.code)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (log) {
|
||||||
|
console.log('Key Down:', key, 'Pressed Keys:', [...pressedKeysRef.current])
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
shortcutKeySet.size &&
|
shortcutKeys.length &&
|
||||||
[...shortcutKeySet].every((k) => pressedKeys.has(k))
|
shortcutKeys.every((k) => pressedKeysRef.current.has(k))
|
||||||
) {
|
) {
|
||||||
if (typeof onTrigger === 'function') {
|
if (typeof onTrigger === 'function') {
|
||||||
onTrigger(event)
|
onTrigger(event)
|
||||||
@ -64,27 +78,24 @@ const KeyboardShortcut = ({ shortcut, children, hint, onTrigger }) => {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleKeyUp = (event) => {
|
const handleKeyUp = (event) => {
|
||||||
if (
|
const key = getPressedKey(event)
|
||||||
event.key === 'Meta' ||
|
if (key) {
|
||||||
event.key === 'Control' ||
|
pressedKeysRef.current.delete(key)
|
||||||
event.key === 'Alt' ||
|
syncPressedKeys()
|
||||||
event.key === 'Shift'
|
|
||||||
) {
|
|
||||||
pressedKeys.delete(event.key)
|
|
||||||
} else if (event.code && event.code.startsWith('Key')) {
|
|
||||||
pressedKeys.delete(event.key.toUpperCase())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown)
|
window.addEventListener('keydown', handleKeyDown)
|
||||||
window.addEventListener('keyup', handleKeyUp)
|
window.addEventListener('keyup', handleKeyUp)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('keydown', handleKeyDown)
|
window.removeEventListener('keydown', handleKeyDown)
|
||||||
window.removeEventListener('keyup', handleKeyUp)
|
window.removeEventListener('keyup', handleKeyUp)
|
||||||
}
|
}
|
||||||
}, [shortcut, shortcutObj, onTrigger, shortcutKeySet])
|
}, [shortcutKeys, onTrigger, log])
|
||||||
|
|
||||||
// Clone the child to attach a ref
|
|
||||||
const element = cloneElement(children, { ref: childRef })
|
const element = cloneElement(children, { ref: childRef })
|
||||||
|
|
||||||
if (hint) {
|
if (hint) {
|
||||||
@ -108,7 +119,8 @@ KeyboardShortcut.propTypes = {
|
|||||||
shortcut: PropTypes.string.isRequired, // e.g. 'cmd+shift+p'
|
shortcut: PropTypes.string.isRequired, // e.g. 'cmd+shift+p'
|
||||||
onTrigger: PropTypes.func.isRequired,
|
onTrigger: PropTypes.func.isRequired,
|
||||||
children: PropTypes.element.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
|
export default KeyboardShortcut
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user