1 Star 0 Fork 0

Flycran/Flysel

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
main.js 20.68 KB
一键复制 编辑 原始数据 按行查看 历史
Flycran 提交于 2022-02-15 15:18 . init
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
"use strict";
// 异常处理
const erreoMap = {
'001': '未知的按键',
};
function CommandError(code, ...ages) {
const s = erreoMap[code];
return typeof s === 'string' ? s : s(ages);
}
//阻止浏览器默认行为
const preventDefaultReg = new Set;
function defaultBehavior(event, keyCode) {
for (const reg of preventDefaultReg) {
if (reg.test(keyCode)) {
return event.preventDefault();
}
}
}
/**
* 阻止浏览器默认行为
* @param {DefaultBehavior | string} ages 描述阻止按键的字符串列表,可多填
*/
function preventDefault(...ages) {
for (const v of ages) {
switch (v) {
case 'menu':
document.addEventListener('contextmenu', event => {
event.preventDefault();
});
break;
case 'wheel':
document.addEventListener('wheel', event => {
event.preventDefault();
}, {
passive: false
});
break;
case 'F*':
let a = [];
for (let i = 1; i < 13; i++) {
a.push(String.fromCharCode(111 + i));
}
preventDefaultReg.add(new RegExp('^(' + a.join('|') + ')$'));
break;
default:
if (v instanceof RegExp) {
preventDefaultReg.add(v);
}
else {
const s = v.split(config.shortcutKeySeparator)
.map(a => a === '*' ? '.+' : KEYMAP.wordToCode(a))
.join('');
preventDefaultReg.add(new RegExp('^' + s + '$'));
}
}
}
}
//事件与快捷键依赖的全局变量
/**事件载体 */
let carrier = document;
/**快捷键字符串: Command对象集合*/
const commandMap = new Map;
/**command字符串: Command对象*/
const commandSet = new Map;
/**功能键映射 */
const FUNCTION_KEY_MAP = {
ctrlKey: '\u0011',
shiftKey: '\u0010',
altKey: '\u0012'
};
/**屏蔽键列表 */
const SHIELD_KEY_LIST = [
'ControlLeft',
'ControlRight',
'ShiftLeft',
'ShiftRight',
'AltLeft',
'AltRight',
'CapsLock',
'NumLock',
'NumpadDivide',
'NumpadMultiply',
'NumpadDecimal'
];
const MOUSEPRESS = [];
const SEPARATOR = '#.[]:';
const KEYMAP = {
wordToCodes: new Map,
codeToWords: new Map,
keyToCodes: new Map,
length: 0,
/**
* 单词转键值码
* @param {string} word
* @returns
*/
wordToCode(word) {
const c = this.wordToCodes.get(word);
if (!c)
throw CommandError('001');
return c;
},
/**
* 键值码转单词
* @param {string} code
* @returns
*/
codeToWord(code) {
return this.codeToWords.get(code);
},
/**
* event.code转键值码
* @param {string} key
* @returns
*/
keyToCode(key) {
const r = this.keyToCodes.get(key);
if (!r)
return this.addCode({ word: key });
return r;
},
/**
* event.code转单词
* @param {string} key
* @returns
*/
keyToWord(key) {
const r = this.keyToCodes.get(key);
const word = key.slice(0, 2) === 'M.' ? 'Mouse.' + key.slice(2) : key;
if (!r)
return this.addCode({ word });
return this.codeToWord(r);
},
/**
* 添加键映射
* @param {string} word
* @param {string[]} key
* @param {string} code
* @returns {string} 键值码
*/
addCode({ word, key }, code) {
if (!code) {
code = String.fromCharCode(2000 + this.length);
this.length++;
}
this.wordToCodes.set(word, code);
this.codeToWords.set(code, word);
if (key) {
for (const v of key) {
this.keyToCodes.set(v, code);
}
}
else {
this.keyToCodes.set(word, code);
}
return code;
}
};
//初始化键值表
{
const INIT_KEYMAP = {
'\u0008': { word: 'Backspace' },
'\u0009': { word: 'Tab' },
'\u000d': { word: 'Enter', key: ['Enter', 'NumpadEnter'] },
'\u0010': { word: 'Shift' },
'\u0011': { word: 'Ctrl' },
'\u0012': { word: 'Alt' },
'\u001b': { word: 'Esc', key: ['Escape'] },
'\u0020': { word: 'Space' },
'\u0021': { word: 'PageUp' },
'\u0022': { word: 'PageDown ' },
'\u0023': { word: 'End' },
'\u0024': { word: 'Home' },
'\u0025': { word: 'Left', key: ['ArrowLeft'] },
'\u0026': { word: 'Up', key: ['ArrowUp'] },
'\u0027': { word: 'Right', key: ['ArrowRight'] },
'\u0028': { word: 'Down', key: ['ArrowDown'] },
'\u006a': { word: 'NumpadMultiply' },
'\u006b': { word: 'NumpadAdd' },
'\u006d': { word: 'NumpadSubtract' },
'\u006e': { word: 'NumpadDecimal' },
'\u006f': { word: 'NumpadDivide' },
'\u00ba': { word: ';', key: ['Semicolon'] },
'\u00bb': { word: '=', key: ['Equal'] },
'\u00bc': { word: ',', key: ['Comma'] },
'\u00bd': { word: '-', key: ['Minus'] },
'\u00be': { word: '.', key: ['Period'] },
'\u00bf': { word: '/', key: ['Slash'] },
'\u00c0': { word: '~', key: ['Backquote'] },
'\u00db': { word: '[', key: ['BracketLeft '] },
'\u00dc': { word: '\\', key: ['Backslash'] },
'\u00dd': { word: ']', key: ['BracketRight'] },
'\u00de': { word: '\'', key: ['Quote'] },
'\u03e8': { word: 'LeftMouse', key: ['M.0'] },
'\u03e9': { word: 'MiddleMouse', key: ['M.1'] },
'\u03ea': { word: 'RightMouse', key: ['M.2'] },
'\u03eb': { word: 'Mouse3', key: ['M.3'] },
'\u03ec': { word: 'Mouse4', key: ['M.4'] },
'\u07d0': { word: 'Wheel' }
};
for (const k in INIT_KEYMAP) {
KEYMAP.addCode(INIT_KEYMAP[k], k);
}
for (let i = 0; i < 10; i++) {
const s = i.toString();
KEYMAP.addCode({ word: s, key: ['Digit' + s] }, String.fromCharCode(48 + i));
KEYMAP.addCode({ word: 'num' + s, key: ['Numpad' + s] }, String.fromCharCode(96 + i));
}
for (let i = 1; i < 13; i++) {
const s = i.toString();
KEYMAP.addCode({ word: 'F' + s }, String.fromCharCode(111 + i));
}
const KEY = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (let i = 0; i < 26; i++) {
const v = KEY[i];
KEYMAP.addCode({ word: v, key: ['Key' + v] }, String.fromCharCode(65 + i));
}
}
/**条件 */
const condition = {};
/**模式约束对象 */
const ConstraintModeMap = {
'key': { mode: ['keydown', 'keyup'] },
'mouse': { mode: ['mousedown', 'mouseup', 'mousemove'], key: /(LeftMouse|MiddleMouse|RightMouse|Mouse[1-9])$/ },
'wheel': { mode: ['wheel'], key: /Wheel$/ }
};
/**addEventListener事件默认选项 */
const listenerOption = {
capture: true,
passive: false
};
const findKeyWordCache = new Map;
const customKeyCode = [];
/**配置对象 */
const config = {
/**快捷键字符串分隔符 */
shortcutKeySeparator: '+',
/**条件字符串分隔符 */
conditionSeparator: '&',
/**迭代查找的顶层元素 */
topElement: document,
/**
* 导入自定义键值码
* @param {string[]} data 配置项
*/
importCustomKeyCode(data) {
for (const v of data) {
KEYMAP.addCode(v.initKey, v.code.toString());
}
},
exportCustomKeyCode() {
return customKeyCode;
},
customKeyCode(code, initKey) {
if (code < 5000 || code > 6000)
throw '自定义键值码必须在5000-6000之间';
const keyCode = String.fromCharCode(code);
customKeyCode.push({ code, initKey });
KEYMAP.addCode(initKey, keyCode);
},
/**Command默认缺省配置 */
get CommandDefaultOption() {
return CommandDefaultOption;
}
};
function callBack(d) {
if (d.sep === '[') {
d.word = d.word.split('=');
}
}
/**
* 迭代查找元素
* @param {string} findKeyWord 查找关键词
* @param {HTMLElement} element 源元素
* @returns {HTMLElement | undefined} 返回查到到的第一个元素
*/
function findElement(findKeyWord, element) {
let ele = element;
let data = findKeyWordCache.get(findKeyWord);
if (!data) {
data = analyseKeyWord(findKeyWord, SEPARATOR, callBack);
findKeyWordCache.set(findKeyWord, data);
}
while (!config.topElement.isEqualNode(ele)) {
if (matchedElement(data, ele))
return ele;
ele = ele.parentNode;
}
}
/**
* 解析查询字符串
* @param {string} keyWord 查询字符串
* @param {string} separator 分隔符
* @param {Function} callBack 回调函数
* @returns {Object[]} 解析结果
*/
function analyseKeyWord(keyWord, separator, callBack) {
const data = [];
let word = '', sep;
for (let i = 0; i < keyWord.length; i++) {
const e = keyWord[i], x = separator.indexOf(e);
if (x === -1) {
word += e;
}
else {
if (word) {
const d = { word, sep };
callBack(d);
data.push(d);
word = '';
}
sep = separator[x];
}
}
const d = { word, sep };
callBack(d);
data.push(d);
return data;
}
/**
* 匹配解析结果
* @param {Object[]} data 解析结果
* @param {HTMLElement} element 元素
* @returns {boolean}
*/
function matchedElement(data, element) {
for (const v of data) {
switch (v.sep) {
case undefined:
if (element.tagName !== v.word.toUpperCase())
return false;
break;
case '#':
if (element.id !== v.word)
return false;
break;
case '.':
if (!element.classList.contains(v.word))
return false;
break;
case '[':
if (v.word.length === 1) {
if (!element.hasAttribute(v.word[0]))
return false;
}
else {
if (element.getAttribute(v.word[0]) !== v.word[1])
return false;
}
break;
case ':':
if (v.word === 'header') {
if (!/H[1-6]/.test(element.tagName))
return false;
}
else {
if (!element[v.word])
return false;
}
break;
}
}
return true;
}
/**
* 获取快捷键字符串
* @param {KeyboardEvent} event
* @returns {string}
*/
function getKeyCode(event, key) {
let keystr = '';
for (const k in FUNCTION_KEY_MAP) {
if (event[k]) {
keystr += FUNCTION_KEY_MAP[k];
}
}
keystr += key;
return keystr;
}
/**
* 获取命令
* @param {string} cmd 命令名
* @returns {Command|undefined} 命令
*/
function getCommand(cmd) {
return commandSet.get(cmd);
}
/**
* 搜索按键命令
* @param {string} key 按键字符串
* @returns {Command[]} 命令数组(只读)
*/
function searchKey(key) {
const code = key.split(config.shortcutKeySeparator).map(a => KEYMAP.wordToCode(a)).join('');
const set = commandMap.get(code);
if (!set)
return [];
return [...set];
}
carrier.addEventListener('keydown', event => {
if (!SHIELD_KEY_LIST.includes(event.code)) {
const key = getKeyCode(event, String.fromCharCode(event.keyCode));
const shortcutKey = commandMap.get(key);
shortcutKey && shortcutKey.forEach(a => {
a.trigger(event);
});
defaultBehavior(event, key);
}
}, listenerOption);
carrier.addEventListener('keyup', event => {
if (!SHIELD_KEY_LIST.includes(event.code)) {
const shortcutKey = commandMap.get(getKeyCode(event, String.fromCharCode(event.keyCode)));
shortcutKey && shortcutKey.forEach(a => {
a.trigger(event, 'keyup');
});
}
}, listenerOption);
carrier.addEventListener('mousedown', event => {
const key = KEYMAP.keyToCode('M.' + event.button);
const shortcutKey = commandMap.get(getKeyCode(event, key));
shortcutKey && shortcutKey.forEach(a => {
a.trigger(event, 'mousedown');
});
MOUSEPRESS.push(key);
condition[KEYMAP.codeToWord(key)] = true;
}, listenerOption);
carrier.addEventListener('mouseup', (event) => {
const key = KEYMAP.keyToCode('M.' + event.button);
const shortcutKey = commandMap.get(getKeyCode(event, key));
shortcutKey && shortcutKey.forEach(a => {
a.trigger(event, 'mouseup');
});
for (let i = 0; i < MOUSEPRESS.length;) {
if (MOUSEPRESS[i] === key) {
MOUSEPRESS.splice(i, 1);
}
else {
i++;
}
}
condition[KEYMAP.codeToWord(key)] = false;
}, listenerOption);
carrier.addEventListener('mousemove', (event) => {
let key = MOUSEPRESS[MOUSEPRESS.length - 1];
if (!key)
return;
const shortcutKey = commandMap.get(getKeyCode(event, key));
shortcutKey && shortcutKey.forEach(a => {
a.trigger(event, 'mousemove');
});
}, listenerOption);
carrier.addEventListener('wheel', event => {
const key = getKeyCode(event, KEYMAP.keyToCode('Wheel'));
const shortcutKey = commandMap.get(key);
shortcutKey && shortcutKey.forEach(a => {
a.trigger(event, 'wheel');
});
defaultBehavior(event, key);
}, listenerOption);
condition['@'] = function (event, word) {
return findElement(word, event.target);
};
/**Command默认缺省配置 */
const CommandDefaultOption = {
key: '',
constraintMode: '',
preventDefault: false,
disableModify: false
};
/**
* @callback Listener
* @param {Event} event
* @returns {any}
*/
/**命令类 */
class Command {
constructor(command, option = {}) {
const { key, constraintMode, preventDefault, disableModify } = Object.assign({}, CommandDefaultOption, option);
this.#command = command;
if (!disableModify)
commandSet.set(command, this);
if (constraintMode) {
if (!ConstraintModeMap[constraintMode])
throw '没有该模式:' + constraintMode;
this.#constraintMode = constraintMode;
}
if (preventDefault)
this.#preventDefault = true;
key && this.bind(key);
}
/**事件侦听器列表 */
#listenerList = {
keydown: new Map,
keyup: new Map,
mousedown: new Map,
mouseup: new Map,
mousemove: new Map,
wheel: new Map
};
/**是否阻止默认事件 */
#preventDefault = false;
/**条件列表 */
#condition = new Set;
/**快捷键码 */
#keyCode = '';
/**命令名 */
#command;
/**委托描述 */
#entrust = '';
#constraintMode;
/**快捷键码(只读) */
get keyCode() {
return this.#keyCode;
}
/**
* 设置快捷键
* @param {string} key 快捷键
*/
bind(key) {
if (this.#constraintMode) {
switch (this.#constraintMode) {
case 'key':
if (ConstraintModeMap['mouse'].key.test(key) || ConstraintModeMap['wheel'].key.test(key)) {
throw '模式约束禁止的操作';
}
break;
case 'mouse':
if (!ConstraintModeMap['mouse'].key.test(key)) {
throw '模式约束禁止的操作';
}
break;
case 'wheel':
if (!ConstraintModeMap['wheel'].key.test(key)) {
throw '模式约束禁止的操作';
}
break;
}
}
const oldKey = this.#keyCode;
const keyCode = key.split(config.shortcutKeySeparator).map(a => KEYMAP.wordToCode(a)).join('');
this.#keyCode = keyCode;
const keySet = commandMap.get(oldKey);
if (keySet) {
keySet.delete(this);
if (!keySet.size) {
commandMap.delete(oldKey);
}
}
const newKeySet = commandMap.get(key);
if (newKeySet) {
newKeySet.add(this);
}
else {
commandMap.set(keyCode, new Set([this]));
}
return this;
}
/**
* 事件委托
* @param word 选择器字符串
*/
entrust(word) {
this.#entrust = word;
}
on(age1, age2) {
let listener, mode;
if (age2) {
mode = age1;
listener = age2;
}
else {
if (typeof age1 === 'object') {
for (const v in age1) {
this.on(v, age1[v]);
}
return this;
}
listener = age1;
mode = 'keydown';
}
if (this.#constraintMode) {
if (!ConstraintModeMap[this.#constraintMode].mode.includes(mode)) {
throw '模式约束禁止的操作';
}
}
const key = Symbol('key');
if (this.#listenerList[mode] === undefined)
throw '模式不存在:' + mode;
this.#listenerList[mode].set(key, listener);
return this;
}
/**
* 移除指定侦听器
* @param {Symbol} key 键名
*/
del(key, mode = 'keydown') {
if (this.#listenerList[mode] === undefined)
throw mode + ' 模式不存在';
this.#listenerList[mode].delete(key);
return this;
}
adopt(e) {
for (let ctn of this.#condition) {
if (typeof ctn === 'function') {
if (!ctn(e))
return false;
}
else {
let no, as = [];
if (ctn[0] === '!') {
no = true;
ctn = ctn.slice(1);
}
const c = ctn.indexOf(':');
if (c) {
as = ctn.slice(c + 1).split(/, |,/);
ctn = ctn.slice(0, c);
}
const cn = condition[ctn];
if (typeof cn === 'function') {
if (no) {
if (cn(e, ...as))
return false;
}
else {
if (!cn(e, ...as))
return false;
}
}
else {
if (no) {
if (cn)
return false;
}
else {
if (!cn)
return false;
}
}
}
}
return true;
}
/**
* 执行事件
* @param {boolean} [skipCondition] 为true则跳过条件判断直接执行
* @returns {boolean} 为true表示触发成功
*/
trigger(event, mode = 'keydown') {
let ele = carrier;
if (this.#entrust) {
ele = findElement(this.#entrust, event.target);
if (!ele) {
return false;
}
}
if (this.adopt(event)) {
this.#listenerList[mode].forEach(listener => {
listener.call(ele, event, condition);
});
}
if (this.#preventDefault) {
event.preventDefault();
}
return true;
}
/** * 快捷键 */
set key(v) {
this.bind(v);
}
/** * 快捷键 */
get key() {
return this.#keyCode.split('').map(a => KEYMAP.codeToWord(a)).join(config.shortcutKeySeparator);
}
/**条件字符串 */
getConditionArray() {
return [...this.#condition];
}
/**条件 */
get condition() {
return [...this.#condition].join(config.conditionSeparator);
}
/**
* 添加条件
* @param {string} condition 条件字符串
*/
addCondition(...condition) {
for (const v of condition) {
this.#condition.add(v);
}
return this;
}
/**
* 删除条件
* @param {string} condition 条件字符串
*/
delCondition(...condition) {
for (const v of condition) {
this.#condition.delete(v);
}
return this;
}
/**
* 是否存在指定条件
* @param condition 条件字符串
* @returns {boolean} 为true表示存在
*/
hasCondition(condition) {
return this.#condition.has(condition);
}
}
//导出模块
module.exports = {
Command,
getCommand,
searchKey,
preventDefault,
condition,
config,
};
//# sourceMappingURL=main.js.map
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/flycran/flysel.git
git@gitee.com:flycran/flysel.git
flycran
flysel
Flysel
master

搜索帮助

23e8dbc6 1850385 7e0993f3 1850385