如何避免使用超过所需键的快捷方式?
How can I avoid shortcut working whit more than required keys?
我正在使用以下挂钩在我的应用程序中使用快捷方式:
(我从这个 repo 改编而来:https://github.com/arthurtyukayev/use-keyboard-shortcut)
import { useEffect, useCallback, useReducer } from 'react';
const disabledEventPropagation = e => {
if (e)
if (e.stopPropagation) {
e.stopPropagation();
} else if (window.event) {
window.event.cancelBubble = true;
}
};
const blacklistedTargets = ['INPUT', 'TEXTAREA'];
const keysReducer = (state, action) => {
switch (action.type) {
case 'set-key-down':
const keydownState = { ...state, [action.key]: true };
return keydownState;
case 'set-key-up':
const keyUpState = { ...state, [action.key]: false };
return keyUpState;
case 'reset-keys':
const resetState = { ...action.data };
return resetState;
default:
return state;
}
};
const useKeyboardShortcut = (shortcutKeys, callback, options) => {
if (!Array.isArray(shortcutKeys))
throw new Error('The first parameter to `useKeyboardShortcut` must be an ordered array of `KeyboardEvent.key` strings.');
if (!shortcutKeys.length)
throw new Error('The first parameter to `useKeyboardShortcut` must contain atleast one `KeyboardEvent.key` string.');
if (!callback || typeof callback !== 'function')
throw new Error('The second parameter to `useKeyboardShortcut` must be a function that will be envoked when the keys are pressed.');
const { overrideSystem } = options || {};
const initalKeyMapping = shortcutKeys.reduce((currentKeys, key) => {
currentKeys[key.toLowerCase()] = false;
return currentKeys;
}, {});
const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping);
const keydownListener = useCallback(
assignedKey => keydownEvent => {
const loweredKey = assignedKey.toLowerCase();
if (keydownEvent.repeat) return;
if (blacklistedTargets.includes(keydownEvent.target.tagName)) return;
if (loweredKey !== keydownEvent.key.toLowerCase()) return;
if (keys[loweredKey] === undefined) return;
if (overrideSystem) {
keydownEvent.preventDefault();
disabledEventPropagation(keydownEvent);
}
setKeys({ type: 'set-key-down', key: loweredKey });
return false;
},
[keys, overrideSystem],
);
const keyupListener = useCallback(
assignedKey => keyupEvent => {
const raisedKey = assignedKey.toLowerCase();
if (blacklistedTargets.includes(keyupEvent.target.tagName)) return;
if (keyupEvent.key.toLowerCase() !== raisedKey) return;
if (keys[raisedKey] === undefined) return;
if (overrideSystem) {
keyupEvent.preventDefault();
disabledEventPropagation(keyupEvent);
}
setKeys({ type: 'set-key-up', key: raisedKey });
return false;
},
[keys, overrideSystem],
);
useEffect(() => {
if (!Object.values(keys).filter(value => !value).length) {
callback(keys);
const newKeyMapping = initalKeyMapping;
newKeyMapping.control = true;
setKeys({ type: 'reset-keys', data: initalKeyMapping });
} else setKeys({ type: null });
}, [callback, keys]);
useEffect(() => {
shortcutKeys.forEach(k => window.addEventListener('keydown', keydownListener(k)));
return () => shortcutKeys.forEach(k => window.removeEventListener('keydown', keydownListener(k)));
}, []);
useEffect(() => {
shortcutKeys.forEach(k => window.addEventListener('keyup', keyupListener(k)));
return () => shortcutKeys.forEach(k => window.removeEventListener('keyup', keyupListener(k)));
}, []);
};
export default useKeyboardShortcut;
因此,我使用以下行来创建和处理快捷方式:
const shortcutUndo = ['Control', 'Z'];
useKeyboardShortcut(shortcutUndo, handleUndoShortcut);
问题是我每次按 Ctrl+Z+[任何其他键] 时,ctr+z 快捷键仍然是 运行,但它不应该是 运行ning。我想不出任何办法来防止这种情况,有人知道吗?
您可以通过重新考虑快捷方式的触发方式来解决此问题。更准确地说,您的钩子可以执行以下操作:
let sequence: string[] = [];
function onKeyDown(key: string) {
// Push key to sequence
sequence.push(key);
}
function onKeyUp(key: string) {
// Store the sequence before we pop from it
const seq = [...sequence];
const popped = sequence.pop(); // last pressed-down key
if (popped === undefined) return; // Sequence was already empty (dropped)
if (popped !== key) {
// User didn't release the last key, drop the whole sequence
sequence = [];
}
// Check whether the sequence matches
if (arrayEquals(seq, shortcutKeys)) {
// Shortcut got triggered, do whatever
}
}
}
这只是按键逻辑,我省略了所有 React 和 hook 相关的东西
这个想法是,当用户不断按下按键时,您会存储按下的按键序列。然后当用户释放最后按下的键时,我们检查是否按下了正确的序列。如果用户释放了一个完全不同的键,我们将重置整个序列以确保安全 (尽管您可能只想删除未按下的键).
这会导致(取消)按下以下键时发生以下情况:
let sequence = [];
const shortcutKeys = ['Control', 'Z'];
function onKeyDown(key) {
// Push key to sequence
sequence.push(key);
console.log('onKeyDown', key, '-> [' + sequence.join('+') + ']');
}
function onKeyUp(key) {
console.log('onKeyUp', key);
// Store the sequence before we pop from it
const seq = [...sequence];
const popped = sequence.pop(); // last pressed-down key
if (popped === undefined) return; // Sequence was already empty (dropped)
if (popped !== key) {
// User didn't release the last key, drop the whole sequence
sequence = [];
console.log('\tSequence dropped');
return;
}
console.log('\t->', '[' + sequence.join('+') + ']');
// Check whether the sequence matches
if (seq.join('+') === shortcutKeys.join('+')) {
// Shortcut got triggered, do whatever
console.log('Shortcut triggered!');
}
}
console.log('== Example 1 ==');
onKeyDown('Control');
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyUp('Control');
// Shortcut triggered once
console.log('== Example 2 ==');
onKeyDown('Control');
onKeyDown('Shift');
onKeyUp('Shift');
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyUp('Control');
console.log('== Example 3 ==');
onKeyDown('Control');
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyUp('Control');
console.log('== Example 4 ==');
onKeyDown('Control');
onKeyDown('Z');
onKeyDown('S');
onKeyDown('S');
onKeyUp('Z');
onKeyUp('Control');
// Shortcut never triggered
console.log('== Example 5 ==');
onKeyDown('Control');
onKeyDown('Z');
onKeyUp('Control');
onKeyUp('Z');
// Shortcut never triggered
这解决了您的问题,同时还允许用户“发送垃圾邮件”快捷方式,而无需重复整个序列。
您可以做的一个小改动是将 arrayEquals
替换为仅检查组合是否存在但不关心顺序的内容。例如。这将使 Ctrl+Shift+Z
和 Shift+Ctrl+Z
(还有 Ctrl+Z+Shift
)都相等。
我正在使用以下挂钩在我的应用程序中使用快捷方式:
(我从这个 repo 改编而来:https://github.com/arthurtyukayev/use-keyboard-shortcut)
import { useEffect, useCallback, useReducer } from 'react';
const disabledEventPropagation = e => {
if (e)
if (e.stopPropagation) {
e.stopPropagation();
} else if (window.event) {
window.event.cancelBubble = true;
}
};
const blacklistedTargets = ['INPUT', 'TEXTAREA'];
const keysReducer = (state, action) => {
switch (action.type) {
case 'set-key-down':
const keydownState = { ...state, [action.key]: true };
return keydownState;
case 'set-key-up':
const keyUpState = { ...state, [action.key]: false };
return keyUpState;
case 'reset-keys':
const resetState = { ...action.data };
return resetState;
default:
return state;
}
};
const useKeyboardShortcut = (shortcutKeys, callback, options) => {
if (!Array.isArray(shortcutKeys))
throw new Error('The first parameter to `useKeyboardShortcut` must be an ordered array of `KeyboardEvent.key` strings.');
if (!shortcutKeys.length)
throw new Error('The first parameter to `useKeyboardShortcut` must contain atleast one `KeyboardEvent.key` string.');
if (!callback || typeof callback !== 'function')
throw new Error('The second parameter to `useKeyboardShortcut` must be a function that will be envoked when the keys are pressed.');
const { overrideSystem } = options || {};
const initalKeyMapping = shortcutKeys.reduce((currentKeys, key) => {
currentKeys[key.toLowerCase()] = false;
return currentKeys;
}, {});
const [keys, setKeys] = useReducer(keysReducer, initalKeyMapping);
const keydownListener = useCallback(
assignedKey => keydownEvent => {
const loweredKey = assignedKey.toLowerCase();
if (keydownEvent.repeat) return;
if (blacklistedTargets.includes(keydownEvent.target.tagName)) return;
if (loweredKey !== keydownEvent.key.toLowerCase()) return;
if (keys[loweredKey] === undefined) return;
if (overrideSystem) {
keydownEvent.preventDefault();
disabledEventPropagation(keydownEvent);
}
setKeys({ type: 'set-key-down', key: loweredKey });
return false;
},
[keys, overrideSystem],
);
const keyupListener = useCallback(
assignedKey => keyupEvent => {
const raisedKey = assignedKey.toLowerCase();
if (blacklistedTargets.includes(keyupEvent.target.tagName)) return;
if (keyupEvent.key.toLowerCase() !== raisedKey) return;
if (keys[raisedKey] === undefined) return;
if (overrideSystem) {
keyupEvent.preventDefault();
disabledEventPropagation(keyupEvent);
}
setKeys({ type: 'set-key-up', key: raisedKey });
return false;
},
[keys, overrideSystem],
);
useEffect(() => {
if (!Object.values(keys).filter(value => !value).length) {
callback(keys);
const newKeyMapping = initalKeyMapping;
newKeyMapping.control = true;
setKeys({ type: 'reset-keys', data: initalKeyMapping });
} else setKeys({ type: null });
}, [callback, keys]);
useEffect(() => {
shortcutKeys.forEach(k => window.addEventListener('keydown', keydownListener(k)));
return () => shortcutKeys.forEach(k => window.removeEventListener('keydown', keydownListener(k)));
}, []);
useEffect(() => {
shortcutKeys.forEach(k => window.addEventListener('keyup', keyupListener(k)));
return () => shortcutKeys.forEach(k => window.removeEventListener('keyup', keyupListener(k)));
}, []);
};
export default useKeyboardShortcut;
因此,我使用以下行来创建和处理快捷方式:
const shortcutUndo = ['Control', 'Z'];
useKeyboardShortcut(shortcutUndo, handleUndoShortcut);
问题是我每次按 Ctrl+Z+[任何其他键] 时,ctr+z 快捷键仍然是 运行,但它不应该是 运行ning。我想不出任何办法来防止这种情况,有人知道吗?
您可以通过重新考虑快捷方式的触发方式来解决此问题。更准确地说,您的钩子可以执行以下操作:
let sequence: string[] = [];
function onKeyDown(key: string) {
// Push key to sequence
sequence.push(key);
}
function onKeyUp(key: string) {
// Store the sequence before we pop from it
const seq = [...sequence];
const popped = sequence.pop(); // last pressed-down key
if (popped === undefined) return; // Sequence was already empty (dropped)
if (popped !== key) {
// User didn't release the last key, drop the whole sequence
sequence = [];
}
// Check whether the sequence matches
if (arrayEquals(seq, shortcutKeys)) {
// Shortcut got triggered, do whatever
}
}
}
这只是按键逻辑,我省略了所有 React 和 hook 相关的东西
这个想法是,当用户不断按下按键时,您会存储按下的按键序列。然后当用户释放最后按下的键时,我们检查是否按下了正确的序列。如果用户释放了一个完全不同的键,我们将重置整个序列以确保安全 (尽管您可能只想删除未按下的键).
这会导致(取消)按下以下键时发生以下情况:
let sequence = [];
const shortcutKeys = ['Control', 'Z'];
function onKeyDown(key) {
// Push key to sequence
sequence.push(key);
console.log('onKeyDown', key, '-> [' + sequence.join('+') + ']');
}
function onKeyUp(key) {
console.log('onKeyUp', key);
// Store the sequence before we pop from it
const seq = [...sequence];
const popped = sequence.pop(); // last pressed-down key
if (popped === undefined) return; // Sequence was already empty (dropped)
if (popped !== key) {
// User didn't release the last key, drop the whole sequence
sequence = [];
console.log('\tSequence dropped');
return;
}
console.log('\t->', '[' + sequence.join('+') + ']');
// Check whether the sequence matches
if (seq.join('+') === shortcutKeys.join('+')) {
// Shortcut got triggered, do whatever
console.log('Shortcut triggered!');
}
}
console.log('== Example 1 ==');
onKeyDown('Control');
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyUp('Control');
// Shortcut triggered once
console.log('== Example 2 ==');
onKeyDown('Control');
onKeyDown('Shift');
onKeyUp('Shift');
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyUp('Control');
console.log('== Example 3 ==');
onKeyDown('Control');
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyDown('Z');
onKeyUp('Z'); // Shortcut triggered
onKeyUp('Control');
console.log('== Example 4 ==');
onKeyDown('Control');
onKeyDown('Z');
onKeyDown('S');
onKeyDown('S');
onKeyUp('Z');
onKeyUp('Control');
// Shortcut never triggered
console.log('== Example 5 ==');
onKeyDown('Control');
onKeyDown('Z');
onKeyUp('Control');
onKeyUp('Z');
// Shortcut never triggered
这解决了您的问题,同时还允许用户“发送垃圾邮件”快捷方式,而无需重复整个序列。
您可以做的一个小改动是将 arrayEquals
替换为仅检查组合是否存在但不关心顺序的内容。例如。这将使 Ctrl+Shift+Z
和 Shift+Ctrl+Z
(还有 Ctrl+Z+Shift
)都相等。