210 lines
4.8 KiB
TypeScript
210 lines
4.8 KiB
TypeScript
/*
|
|
* Copyright 2025 coze-dev Authors
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
const isAppleDevice = /(mac|iphone|ipod|ipad)/i.test(
|
|
typeof navigator !== 'undefined' ? navigator?.platform : '',
|
|
);
|
|
// Keyboard Event keyCode Alias
|
|
const aliasKeyCodeMap = {
|
|
'0': 48,
|
|
'1': 49,
|
|
'2': 50,
|
|
'3': 51,
|
|
'4': 52,
|
|
'5': 53,
|
|
'6': 54,
|
|
'7': 55,
|
|
'8': 56,
|
|
'9': 57,
|
|
backspace: 8,
|
|
tab: 9,
|
|
enter: 13,
|
|
shift: 16,
|
|
ctrl: 17,
|
|
alt: 18,
|
|
pausebreak: 19,
|
|
capslock: 20,
|
|
esc: 27,
|
|
space: 32,
|
|
pageup: 33,
|
|
pagedown: 34,
|
|
end: 35,
|
|
home: 36,
|
|
leftarrow: 37,
|
|
uparrow: 38,
|
|
rightarrow: 39,
|
|
downarrow: 40,
|
|
insert: 45,
|
|
delete: 46,
|
|
a: 65,
|
|
b: 66,
|
|
c: 67,
|
|
d: 68,
|
|
e: 69,
|
|
f: 70,
|
|
g: 71,
|
|
h: 72,
|
|
i: 73,
|
|
j: 74,
|
|
k: 75,
|
|
l: 76,
|
|
m: 77,
|
|
n: 78,
|
|
o: 79,
|
|
p: 80,
|
|
q: 81,
|
|
r: 82,
|
|
s: 83,
|
|
t: 84,
|
|
u: 85,
|
|
v: 86,
|
|
w: 87,
|
|
x: 88,
|
|
y: 89,
|
|
z: 90,
|
|
leftwindowkey: 91,
|
|
rightwindowkey: 92,
|
|
meta: isAppleDevice ? [91, 93] : [91, 92],
|
|
selectkey: 93,
|
|
numpad0: 96,
|
|
numpad1: 97,
|
|
numpad2: 98,
|
|
numpad3: 99,
|
|
numpad4: 100,
|
|
numpad5: 101,
|
|
numpad6: 102,
|
|
numpad7: 103,
|
|
numpad8: 104,
|
|
numpad9: 105,
|
|
multiply: 106,
|
|
add: 107,
|
|
subtract: 109,
|
|
decimalpoint: 110,
|
|
divide: 111,
|
|
f1: 112,
|
|
f2: 113,
|
|
f3: 114,
|
|
f4: 115,
|
|
f5: 116,
|
|
f6: 117,
|
|
f7: 118,
|
|
f8: 119,
|
|
f9: 120,
|
|
f10: 121,
|
|
f11: 122,
|
|
f12: 123,
|
|
numlock: 144,
|
|
scrolllock: 145,
|
|
semicolon: 186,
|
|
equalsign: 187,
|
|
'=': 187,
|
|
comma: 188,
|
|
dash: 189,
|
|
'-': 189,
|
|
period: 190,
|
|
forwardslash: 191,
|
|
graveaccent: 192,
|
|
openbracket: 219,
|
|
backslash: 220,
|
|
closebracket: 221,
|
|
singlequote: 222,
|
|
};
|
|
|
|
const modifierKey = {
|
|
ctrl: (event: KeyboardEvent) => event.ctrlKey,
|
|
shift: (event: KeyboardEvent) => event.shiftKey,
|
|
alt: (event: KeyboardEvent) => event.altKey,
|
|
meta: (event: KeyboardEvent) => {
|
|
if (event.type === 'keyup') {
|
|
return aliasKeyCodeMap.meta.includes(event.keyCode);
|
|
}
|
|
return event.metaKey;
|
|
},
|
|
};
|
|
|
|
// Number of activation keys based on event
|
|
function countKeyByEvent(event: KeyboardEvent): number {
|
|
const countOfModifier = Object.keys(modifierKey).reduce((total, key) => {
|
|
if (modifierKey[key](event)) {
|
|
return total + 1;
|
|
}
|
|
|
|
return total;
|
|
}, 0);
|
|
|
|
// 16 17 18 91 92 is the keyCode of the modifier key. If keyCode is a modifier key, then the number of activations is the number of modifier keys. If not, then + 1 is required.
|
|
return [16, 17, 18, 91, 92].includes(event.keyCode)
|
|
? countOfModifier
|
|
: countOfModifier + 1;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param event
|
|
* @param keyString 'ctrl.s' 'meta.s'
|
|
* @param exactMatch
|
|
*/
|
|
function isKeyStringMatch(
|
|
event: KeyboardEvent,
|
|
keyString: string,
|
|
exactMatch = true,
|
|
): boolean {
|
|
// When the browser automatically completes the input, keyDown and keyUp events will be triggered, but at this time event.key, etc. are empty
|
|
if (!event.key || !keyString) {
|
|
return false;
|
|
}
|
|
// String in turn determines whether there is a key combination
|
|
const genArr = keyString.split(/\s+/);
|
|
let genLen = 0;
|
|
|
|
for (const key of genArr) {
|
|
// key combination
|
|
const genModifier = modifierKey[key];
|
|
// keyCode alias
|
|
const aliasKeyCode: number | number[] = aliasKeyCodeMap[key.toLowerCase()];
|
|
|
|
if (
|
|
(genModifier && genModifier(event)) ||
|
|
(aliasKeyCode && aliasKeyCode === event.keyCode)
|
|
) {
|
|
genLen++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* It is necessary to determine that the triggered key is exactly the same as the monitored key. The judgment method is that the triggered key is equal to the monitored key.
|
|
* genLen == genArr.length can determine that there is a listening key among the triggered keys
|
|
* countKeyByEvent (event) === genArr.length The number of keys triggered by the judgment is equal to the number of keys listened to
|
|
* It is mainly used to prevent the situation that pressing a subset of the key combination will also trigger, such as listening to the event that ctrl + a will trigger listening to the two keys ctrl and a.
|
|
*/
|
|
if (exactMatch) {
|
|
return genLen === genArr.length && countKeyByEvent(event) === genArr.length;
|
|
}
|
|
return genLen === genArr.length;
|
|
}
|
|
|
|
/**
|
|
* Matches the specified shortcut
|
|
* @param event
|
|
* @param shortcuts
|
|
*/
|
|
export function isShortcutsMatch(
|
|
event: KeyboardEvent,
|
|
shortcuts: string[],
|
|
): boolean {
|
|
return shortcuts.some(keyString => isKeyStringMatch(event, keyString));
|
|
}
|