diff --git a/.nojekyll b/.nojekyll
new file mode 100644
index 0000000..e2ac661
--- /dev/null
+++ b/.nojekyll
@@ -0,0 +1 @@
+TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false.
\ No newline at end of file
diff --git a/app.js b/app.js
new file mode 100644
index 0000000..1150ef8
--- /dev/null
+++ b/app.js
@@ -0,0 +1,98 @@
+(function (vue) {
+ 'use strict';
+
+ vue.createApp({
+ setup() {
+ const shortcuts = vue.ref(`\
+g g
+c-a c-b
+a-a a-b
+ArrowUp ArrowUp ArrowDown ArrowDown ArrowLeft ArrowRight ArrowLeft ArrowRight B A`);
+ const keyExpCS = vue.ref('');
+ const keyExpCI = vue.ref('');
+ const keyExpCICode = vue.ref('');
+ const keyTriggered = vue.ref('');
+ const sequence = vue.ref('');
+ const caseSensitive = vue.ref(false);
+ let disposeList = [];
+ const onFocus = () => {
+ VM.shortcut.getService().setContext('input', true);
+ };
+ const onBlur = () => {
+ VM.shortcut.getService().setContext('input', false);
+ };
+ vue.watch([shortcuts, caseSensitive], () => {
+ disposeList.forEach(dispose => dispose());
+ disposeList = shortcuts.value.split('\n').map(row => row.trim()).filter(Boolean).map(key => {
+ console.log('register', key, caseSensitive.value);
+ return VM.shortcut.register(key, () => {
+ vue.nextTick(() => {
+ keyTriggered.value = key;
+ });
+ }, {
+ caseSensitive: caseSensitive.value,
+ condition: '!input'
+ });
+ });
+ }, {
+ immediate: true
+ });
+ vue.watch(sequence, () => {
+ keyTriggered.value = '';
+ });
+ vue.onMounted(() => {
+ VM.shortcut.disable();
+ VM.shortcut.getService().sequence.subscribe(seq => {
+ sequence.value = seq.join(' ');
+ });
+ window.addEventListener('keydown', e => {
+ if (!VM.shortcut.modifiers[e.key.toLowerCase()]) {
+ keyExpCS.value = VM.shortcut.buildKey({
+ base: e.key,
+ modifierState: {
+ c: e.ctrlKey,
+ m: e.metaKey
+ },
+ caseSensitive: true
+ });
+ keyExpCICode.value = VM.shortcut.buildKey({
+ base: e.code,
+ modifierState: {
+ c: e.ctrlKey,
+ s: e.shiftKey,
+ a: e.altKey,
+ m: e.metaKey
+ },
+ caseSensitive: false
+ }).replace(/^i:/, '');
+ keyExpCI.value = VM.shortcut.buildKey({
+ base: e.key,
+ modifierState: {
+ c: e.ctrlKey,
+ s: e.shiftKey,
+ a: e.altKey,
+ m: e.metaKey
+ },
+ caseSensitive: false
+ }).replace(/^i:/, '');
+ }
+ VM.shortcut.handleKey(e);
+ });
+ });
+ return {
+ version: VM.shortcut.version,
+ commit: "a73ae92",
+ shortcuts,
+ sequence,
+ keyTriggered,
+ keyExpCS,
+ keyExpCI,
+ keyExpCICode,
+ caseSensitive,
+ onFocus,
+ onBlur
+ };
+ }
+ }).mount('#app');
+
+})(Vue);
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..3bcc9af
--- /dev/null
+++ b/index.html
@@ -0,0 +1,146 @@
+
+
+
+
+
+ @violentmonkey/shortcut Playground
+
+
+
+
+
+
+
+
+
+
+
@violentmonkey/shortcut Playground
+
+ Version: {{version}}
+
+
+
Triggering a Shortcut
+
+
+ Declare your shortcuts here: (one sequence in a line)
+
+
+
+
+
+ Current sequence:
+
+
+
+
+ You just triggered
+ !
+
+
Try to trigger a shortcut sequence declared above.
+
+
Inspecting a Key
+
Press a key to see how it is represented.
+
+
Note:
+
+ -
+ Alt/Opt/Shift
+ do not work in case-sensitive mode.
+
+ -
+ Some keys might not work because they are occupied by the browser.
+
+
+
+
+
+
Case Insensitive:
+
+ -
+ (
event.key
)
+
+ -
+ (
event.code
)
+
+
+
+
+
+
+VM.shortcut.register({{ JSON.stringify(keyExpCI) }}, callback);
+// or
+VM.shortcut.register({{ JSON.stringify(keyExpCICode) }}, callback);
+
+
+
+
+VM.shortcut.register({{ JSON.stringify(keyExpCS) }}, callback, {
+ caseSensitive: true,
+});
+
+
+
+
+
+
+
+
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..b637681
--- /dev/null
+++ b/index.js
@@ -0,0 +1,443 @@
+/*! @violentmonkey/shortcut v1.4.4 | ISC License */
+this.VM = this.VM || {};
+(function (exports) {
+ 'use strict';
+
+ function _extends() {
+ _extends = Object.assign ? Object.assign.bind() : function (target) {
+ for (var i = 1; i < arguments.length; i++) {
+ var source = arguments[i];
+ for (var key in source) {
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
+ target[key] = source[key];
+ }
+ }
+ }
+ return target;
+ };
+ return _extends.apply(this, arguments);
+ }
+
+ const isMacintosh = navigator.userAgent.includes('Macintosh');
+ const modifierList = ['m', 'c', 's', 'a'];
+ const modifiers = {
+ ctrl: 'c',
+ control: 'c',
+ // macOS
+ shift: 's',
+ alt: 'a',
+ meta: 'm',
+ cmd: 'm'
+ };
+ const modifierAliases = _extends({}, modifiers, {
+ c: 'c',
+ s: 's',
+ a: 'a',
+ m: 'm',
+ cm: isMacintosh ? 'm' : 'c',
+ ctrlcmd: isMacintosh ? 'm' : 'c'
+ });
+ const modifierSymbols = {
+ c: '^',
+ s: '⇧',
+ a: '⌥',
+ m: '⌘'
+ };
+ const aliases = {
+ arrowup: 'up',
+ arrowdown: 'down',
+ arrowleft: 'left',
+ arrowright: 'right',
+ cr: 'enter',
+ escape: 'esc',
+ ' ': 'space'
+ };
+
+ function createKeyNode() {
+ return {
+ children: new Map(),
+ shortcuts: new Set()
+ };
+ }
+ function addKeyNode(root, sequence, shortcut) {
+ let node = root;
+ for (const key of sequence) {
+ let child = node.children.get(key);
+ if (!child) {
+ child = createKeyNode();
+ node.children.set(key, child);
+ }
+ node = child;
+ }
+ node.shortcuts.add(shortcut);
+ }
+ function getKeyNode(root, sequence) {
+ let node = root;
+ for (const key of sequence) {
+ node = node.children.get(key);
+ if (!node) break;
+ }
+ return node;
+ }
+ function removeKeyNode(root, sequence, shortcut) {
+ let node = root;
+ const ancestors = [node];
+ for (const key of sequence) {
+ node = node.children.get(key);
+ if (!node) return;
+ ancestors.push(node);
+ }
+ if (shortcut) node.shortcuts.delete(shortcut);else node.shortcuts.clear();
+ let i = ancestors.length - 1;
+ while (i > 0) {
+ node = ancestors[i];
+ if (node.shortcuts.size || node.children.size) break;
+ const last = ancestors[i - 1];
+ last.children.delete(sequence[i - 1]);
+ i -= 1;
+ }
+ }
+ function reprNodeTree(root) {
+ const result = [];
+ const reprChildren = (node, level = 0) => {
+ for (const [key, child] of node.children.entries()) {
+ result.push([' '.repeat(level), key, child.shortcuts.size ? ` (${child.shortcuts.size})` : ''].join(''));
+ reprChildren(child, level + 1);
+ }
+ };
+ reprChildren(root);
+ return result.join('\n');
+ }
+
+ class Subject {
+ constructor(value) {
+ this.listeners = [];
+ this.value = value;
+ }
+ get() {
+ return this.value;
+ }
+ set(value) {
+ this.value = value;
+ this.listeners.forEach(listener => listener(value));
+ }
+ subscribe(callback) {
+ this.listeners.push(callback);
+ callback(this.value);
+ return () => this.unsubscribe(callback);
+ }
+ unsubscribe(callback) {
+ const i = this.listeners.indexOf(callback);
+ if (i >= 0) this.listeners.splice(i, 1);
+ }
+ }
+
+ function buildKey(key) {
+ const {
+ caseSensitive,
+ modifierState
+ } = key;
+ let {
+ base
+ } = key;
+ if (!caseSensitive || base.length > 1) base = base.toLowerCase();
+ base = aliases[base] || base;
+ const keyExp = [...modifierList.filter(m => modifierState[m]), base].filter(Boolean).join('-');
+ return `${caseSensitive ? '' : 'i:'}${keyExp}`;
+ }
+ function breakKey(shortcut) {
+ const pieces = shortcut.split(/-(.)/);
+ const parts = [pieces[0]];
+ for (let i = 1; i < pieces.length; i += 2) {
+ parts.push(pieces[i] + pieces[i + 1]);
+ }
+ return parts;
+ }
+ function parseKey(shortcut, caseSensitive) {
+ const parts = breakKey(shortcut);
+ const base = parts.pop();
+ const modifierState = {};
+ for (const part of parts) {
+ const key = modifierAliases[part.toLowerCase()];
+ if (!key) throw new Error(`Unknown modifier key: ${part}`);
+ modifierState[key] = true;
+ }
+ // Alt/Shift modifies the character.
+ // In case sensitive mode, we only need to check the modified character: = Ctrl+Shift+KeyA
+ // In case insensitive mode, we check the keyCode as well as modifiers: = Ctrl+Shift+KeyA
+ // So if Alt/Shift appears in the shortcut, we must switch to case insensitive mode.
+ caseSensitive && (caseSensitive = !(modifierState.a || modifierState.s));
+ return {
+ base,
+ modifierState,
+ caseSensitive
+ };
+ }
+ function getSequence(input) {
+ return Array.isArray(input) ? input : input.split(/\s+/);
+ }
+ function normalizeSequence(input, caseSensitive) {
+ return getSequence(input).map(key => parseKey(key, caseSensitive));
+ }
+ function parseCondition(condition) {
+ return condition.split('&&').map(key => {
+ key = key.trim();
+ if (!key) return;
+ if (key[0] === '!') {
+ return {
+ not: true,
+ field: key.slice(1).trim()
+ };
+ }
+ return {
+ not: false,
+ field: key
+ };
+ }).filter(Boolean);
+ }
+ function reprKey(key) {
+ const {
+ modifierState,
+ caseSensitive
+ } = key;
+ let {
+ base
+ } = key;
+ if (!caseSensitive || base.length > 1) {
+ base = base[0].toUpperCase() + base.slice(1);
+ }
+ const modifiers = modifierList.filter(m => modifierState[m]).map(m => modifierSymbols[m]);
+ return [...modifiers, base].join('');
+ }
+ function reprShortcut(input, caseSensitive = false) {
+ return getSequence(input).map(key => parseKey(key, caseSensitive)).map(key => reprKey(key)).join(' ');
+ }
+
+ const version = "1.4.4";
+ class KeyboardService {
+ constructor(options) {
+ this._context = {};
+ this._conditionData = {};
+ this._data = [];
+ this._root = createKeyNode();
+ this.sequence = new Subject([]);
+ this._timer = 0;
+ this._reset = () => {
+ this._cur = undefined;
+ this.sequence.set([]);
+ this._resetTimer();
+ };
+ this.handleKey = e => {
+ // Chrome sends a trusted keydown event with no key when choosing from autofill
+ if (!e.key || modifiers[e.key.toLowerCase()]) return;
+ this._resetTimer();
+ const keyExps = [
+ // case sensitive mode, `e.key` is the character considering Alt/Shift
+ buildKey({
+ base: e.key,
+ modifierState: {
+ c: e.ctrlKey,
+ m: e.metaKey
+ },
+ caseSensitive: true
+ }),
+ // case insensitive mode, using `e.code` with modifiers including Alt/Shift
+ buildKey({
+ base: e.code,
+ modifierState: {
+ c: e.ctrlKey,
+ s: e.shiftKey,
+ a: e.altKey,
+ m: e.metaKey
+ },
+ caseSensitive: false
+ }),
+ // case insensitive mode, using `e.key` with modifiers
+ buildKey({
+ // Note: `e.key` might be different from what you expect because of Alt Graph
+ // ref: https://en.wikipedia.org/wiki/AltGr_key
+ base: e.key,
+ modifierState: {
+ c: e.ctrlKey,
+ s: e.shiftKey,
+ a: e.altKey,
+ m: e.metaKey
+ },
+ caseSensitive: false
+ })];
+ const state = this._handleKeyOnce(keyExps, false);
+ if (state) {
+ e.preventDefault();
+ if (state === 2) this._reset();
+ }
+ this._timer = window.setTimeout(this._reset, this.options.sequenceTimeout);
+ };
+ this.options = _extends({}, KeyboardService.defaultOptions, options);
+ }
+ _resetTimer() {
+ if (this._timer) {
+ window.clearTimeout(this._timer);
+ this._timer = 0;
+ }
+ }
+ _addCondition(condition) {
+ let cache = this._conditionData[condition];
+ if (!cache) {
+ const value = parseCondition(condition);
+ cache = {
+ count: 0,
+ value,
+ result: this._evalCondition(value)
+ };
+ this._conditionData[condition] = cache;
+ }
+ cache.count += 1;
+ }
+ _removeCondition(condition) {
+ const cache = this._conditionData[condition];
+ if (cache) {
+ cache.count -= 1;
+ if (!cache.count) {
+ delete this._conditionData[condition];
+ }
+ }
+ }
+ _evalCondition(conditions) {
+ return conditions.every(cond => {
+ let value = this._context[cond.field];
+ if (cond.not) value = !value;
+ return value;
+ });
+ }
+ _checkShortcut(item) {
+ const cache = item.condition && this._conditionData[item.condition];
+ const enabled = !cache || cache.result;
+ if (item.enabled !== enabled) {
+ item.enabled = enabled;
+ this._enableShortcut(item);
+ }
+ }
+ _enableShortcut(item) {
+ (item.enabled ? addKeyNode : removeKeyNode)(this._root, item.sequence, item);
+ }
+ enable() {
+ this.disable();
+ document.addEventListener('keydown', this.handleKey);
+ }
+ disable() {
+ document.removeEventListener('keydown', this.handleKey);
+ }
+ register(key, callback, options) {
+ const {
+ caseSensitive,
+ condition
+ } = _extends({
+ caseSensitive: false
+ }, options);
+ const sequence = normalizeSequence(key, caseSensitive).map(key => buildKey(key));
+ const item = {
+ sequence,
+ condition,
+ callback,
+ enabled: false,
+ caseSensitive
+ };
+ if (condition) this._addCondition(condition);
+ this._checkShortcut(item);
+ this._data.push(item);
+ return () => {
+ const index = this._data.indexOf(item);
+ if (index >= 0) {
+ this._data.splice(index, 1);
+ if (condition) this._removeCondition(condition);
+ item.enabled = false;
+ this._enableShortcut(item);
+ }
+ };
+ }
+ setContext(key, value) {
+ this._context[key] = value;
+ for (const cache of Object.values(this._conditionData)) {
+ cache.result = this._evalCondition(cache.value);
+ }
+ for (const item of this._data) {
+ this._checkShortcut(item);
+ }
+ }
+ _handleKeyOnce(keyExps, fromRoot) {
+ var _cur, _cur2;
+ let cur = this._cur;
+ if (fromRoot || !cur) {
+ // set fromRoot to true to avoid another retry
+ fromRoot = true;
+ cur = this._root;
+ }
+ if (cur) {
+ let next;
+ for (const key of keyExps) {
+ next = getKeyNode(cur, [key]);
+ if (next) {
+ this.sequence.set([...this.sequence.get(), key]);
+ break;
+ }
+ }
+ cur = next;
+ }
+ this._cur = cur;
+ const [shortcut] = [...(((_cur = cur) == null ? void 0 : _cur.shortcuts) || [])];
+ if (!fromRoot && !shortcut && !((_cur2 = cur) != null && _cur2.children.size)) {
+ // Nothing is matched with the last key, rematch from root
+ this._reset();
+ return this._handleKeyOnce(keyExps, true);
+ }
+ if (shortcut) {
+ try {
+ shortcut.callback();
+ } catch (_unused) {
+ // ignore
+ }
+ return 2;
+ }
+ return this._cur ? 1 : 0;
+ }
+ repr() {
+ return reprNodeTree(this._root);
+ }
+ }
+ KeyboardService.defaultOptions = {
+ sequenceTimeout: 500
+ };
+ let service;
+ function getService() {
+ if (!service) {
+ service = new KeyboardService();
+ service.enable();
+ }
+ return service;
+ }
+ const register = (...args) => getService().register(...args);
+ const enable = () => getService().enable();
+ const disable = () => getService().disable();
+ const handleKey = (...args) => getService().handleKey(...args);
+
+ exports.KeyboardService = KeyboardService;
+ exports.aliases = aliases;
+ exports.buildKey = buildKey;
+ exports.disable = disable;
+ exports.enable = enable;
+ exports.getService = getService;
+ exports.handleKey = handleKey;
+ exports.isMacintosh = isMacintosh;
+ exports.modifierAliases = modifierAliases;
+ exports.modifierList = modifierList;
+ exports.modifierSymbols = modifierSymbols;
+ exports.modifiers = modifiers;
+ exports.normalizeSequence = normalizeSequence;
+ exports.parseCondition = parseCondition;
+ exports.parseKey = parseKey;
+ exports.register = register;
+ exports.reprKey = reprKey;
+ exports.reprShortcut = reprShortcut;
+ exports.version = version;
+
+})(this.VM.shortcut = this.VM.shortcut || {});
diff --git a/index.mjs b/index.mjs
new file mode 100644
index 0000000..77a8158
--- /dev/null
+++ b/index.mjs
@@ -0,0 +1,406 @@
+/*! @violentmonkey/shortcut v1.4.4 | ISC License */
+import _extends from '@babel/runtime/helpers/esm/extends';
+
+const isMacintosh = navigator.userAgent.includes('Macintosh');
+const modifierList = ['m', 'c', 's', 'a'];
+const modifiers = {
+ ctrl: 'c',
+ control: 'c',
+ // macOS
+ shift: 's',
+ alt: 'a',
+ meta: 'm',
+ cmd: 'm'
+};
+const modifierAliases = _extends({}, modifiers, {
+ c: 'c',
+ s: 's',
+ a: 'a',
+ m: 'm',
+ cm: isMacintosh ? 'm' : 'c',
+ ctrlcmd: isMacintosh ? 'm' : 'c'
+});
+const modifierSymbols = {
+ c: '^',
+ s: '⇧',
+ a: '⌥',
+ m: '⌘'
+};
+const aliases = {
+ arrowup: 'up',
+ arrowdown: 'down',
+ arrowleft: 'left',
+ arrowright: 'right',
+ cr: 'enter',
+ escape: 'esc',
+ ' ': 'space'
+};
+
+function createKeyNode() {
+ return {
+ children: new Map(),
+ shortcuts: new Set()
+ };
+}
+function addKeyNode(root, sequence, shortcut) {
+ let node = root;
+ for (const key of sequence) {
+ let child = node.children.get(key);
+ if (!child) {
+ child = createKeyNode();
+ node.children.set(key, child);
+ }
+ node = child;
+ }
+ node.shortcuts.add(shortcut);
+}
+function getKeyNode(root, sequence) {
+ let node = root;
+ for (const key of sequence) {
+ node = node.children.get(key);
+ if (!node) break;
+ }
+ return node;
+}
+function removeKeyNode(root, sequence, shortcut) {
+ let node = root;
+ const ancestors = [node];
+ for (const key of sequence) {
+ node = node.children.get(key);
+ if (!node) return;
+ ancestors.push(node);
+ }
+ if (shortcut) node.shortcuts.delete(shortcut);else node.shortcuts.clear();
+ let i = ancestors.length - 1;
+ while (i > 0) {
+ node = ancestors[i];
+ if (node.shortcuts.size || node.children.size) break;
+ const last = ancestors[i - 1];
+ last.children.delete(sequence[i - 1]);
+ i -= 1;
+ }
+}
+function reprNodeTree(root) {
+ const result = [];
+ const reprChildren = (node, level = 0) => {
+ for (const [key, child] of node.children.entries()) {
+ result.push([' '.repeat(level), key, child.shortcuts.size ? ` (${child.shortcuts.size})` : ''].join(''));
+ reprChildren(child, level + 1);
+ }
+ };
+ reprChildren(root);
+ return result.join('\n');
+}
+
+class Subject {
+ constructor(value) {
+ this.listeners = [];
+ this.value = value;
+ }
+ get() {
+ return this.value;
+ }
+ set(value) {
+ this.value = value;
+ this.listeners.forEach(listener => listener(value));
+ }
+ subscribe(callback) {
+ this.listeners.push(callback);
+ callback(this.value);
+ return () => this.unsubscribe(callback);
+ }
+ unsubscribe(callback) {
+ const i = this.listeners.indexOf(callback);
+ if (i >= 0) this.listeners.splice(i, 1);
+ }
+}
+
+function buildKey(key) {
+ const {
+ caseSensitive,
+ modifierState
+ } = key;
+ let {
+ base
+ } = key;
+ if (!caseSensitive || base.length > 1) base = base.toLowerCase();
+ base = aliases[base] || base;
+ const keyExp = [...modifierList.filter(m => modifierState[m]), base].filter(Boolean).join('-');
+ return `${caseSensitive ? '' : 'i:'}${keyExp}`;
+}
+function breakKey(shortcut) {
+ const pieces = shortcut.split(/-(.)/);
+ const parts = [pieces[0]];
+ for (let i = 1; i < pieces.length; i += 2) {
+ parts.push(pieces[i] + pieces[i + 1]);
+ }
+ return parts;
+}
+function parseKey(shortcut, caseSensitive) {
+ const parts = breakKey(shortcut);
+ const base = parts.pop();
+ const modifierState = {};
+ for (const part of parts) {
+ const key = modifierAliases[part.toLowerCase()];
+ if (!key) throw new Error(`Unknown modifier key: ${part}`);
+ modifierState[key] = true;
+ }
+ // Alt/Shift modifies the character.
+ // In case sensitive mode, we only need to check the modified character: = Ctrl+Shift+KeyA
+ // In case insensitive mode, we check the keyCode as well as modifiers: = Ctrl+Shift+KeyA
+ // So if Alt/Shift appears in the shortcut, we must switch to case insensitive mode.
+ caseSensitive && (caseSensitive = !(modifierState.a || modifierState.s));
+ return {
+ base,
+ modifierState,
+ caseSensitive
+ };
+}
+function getSequence(input) {
+ return Array.isArray(input) ? input : input.split(/\s+/);
+}
+function normalizeSequence(input, caseSensitive) {
+ return getSequence(input).map(key => parseKey(key, caseSensitive));
+}
+function parseCondition(condition) {
+ return condition.split('&&').map(key => {
+ key = key.trim();
+ if (!key) return;
+ if (key[0] === '!') {
+ return {
+ not: true,
+ field: key.slice(1).trim()
+ };
+ }
+ return {
+ not: false,
+ field: key
+ };
+ }).filter(Boolean);
+}
+function reprKey(key) {
+ const {
+ modifierState,
+ caseSensitive
+ } = key;
+ let {
+ base
+ } = key;
+ if (!caseSensitive || base.length > 1) {
+ base = base[0].toUpperCase() + base.slice(1);
+ }
+ const modifiers = modifierList.filter(m => modifierState[m]).map(m => modifierSymbols[m]);
+ return [...modifiers, base].join('');
+}
+function reprShortcut(input, caseSensitive = false) {
+ return getSequence(input).map(key => parseKey(key, caseSensitive)).map(key => reprKey(key)).join(' ');
+}
+
+const version = "1.4.4";
+class KeyboardService {
+ constructor(options) {
+ this._context = {};
+ this._conditionData = {};
+ this._data = [];
+ this._root = createKeyNode();
+ this.sequence = new Subject([]);
+ this._timer = 0;
+ this._reset = () => {
+ this._cur = undefined;
+ this.sequence.set([]);
+ this._resetTimer();
+ };
+ this.handleKey = e => {
+ // Chrome sends a trusted keydown event with no key when choosing from autofill
+ if (!e.key || modifiers[e.key.toLowerCase()]) return;
+ this._resetTimer();
+ const keyExps = [
+ // case sensitive mode, `e.key` is the character considering Alt/Shift
+ buildKey({
+ base: e.key,
+ modifierState: {
+ c: e.ctrlKey,
+ m: e.metaKey
+ },
+ caseSensitive: true
+ }),
+ // case insensitive mode, using `e.code` with modifiers including Alt/Shift
+ buildKey({
+ base: e.code,
+ modifierState: {
+ c: e.ctrlKey,
+ s: e.shiftKey,
+ a: e.altKey,
+ m: e.metaKey
+ },
+ caseSensitive: false
+ }),
+ // case insensitive mode, using `e.key` with modifiers
+ buildKey({
+ // Note: `e.key` might be different from what you expect because of Alt Graph
+ // ref: https://en.wikipedia.org/wiki/AltGr_key
+ base: e.key,
+ modifierState: {
+ c: e.ctrlKey,
+ s: e.shiftKey,
+ a: e.altKey,
+ m: e.metaKey
+ },
+ caseSensitive: false
+ })];
+ const state = this._handleKeyOnce(keyExps, false);
+ if (state) {
+ e.preventDefault();
+ if (state === 2) this._reset();
+ }
+ this._timer = window.setTimeout(this._reset, this.options.sequenceTimeout);
+ };
+ this.options = _extends({}, KeyboardService.defaultOptions, options);
+ }
+ _resetTimer() {
+ if (this._timer) {
+ window.clearTimeout(this._timer);
+ this._timer = 0;
+ }
+ }
+ _addCondition(condition) {
+ let cache = this._conditionData[condition];
+ if (!cache) {
+ const value = parseCondition(condition);
+ cache = {
+ count: 0,
+ value,
+ result: this._evalCondition(value)
+ };
+ this._conditionData[condition] = cache;
+ }
+ cache.count += 1;
+ }
+ _removeCondition(condition) {
+ const cache = this._conditionData[condition];
+ if (cache) {
+ cache.count -= 1;
+ if (!cache.count) {
+ delete this._conditionData[condition];
+ }
+ }
+ }
+ _evalCondition(conditions) {
+ return conditions.every(cond => {
+ let value = this._context[cond.field];
+ if (cond.not) value = !value;
+ return value;
+ });
+ }
+ _checkShortcut(item) {
+ const cache = item.condition && this._conditionData[item.condition];
+ const enabled = !cache || cache.result;
+ if (item.enabled !== enabled) {
+ item.enabled = enabled;
+ this._enableShortcut(item);
+ }
+ }
+ _enableShortcut(item) {
+ (item.enabled ? addKeyNode : removeKeyNode)(this._root, item.sequence, item);
+ }
+ enable() {
+ this.disable();
+ document.addEventListener('keydown', this.handleKey);
+ }
+ disable() {
+ document.removeEventListener('keydown', this.handleKey);
+ }
+ register(key, callback, options) {
+ const {
+ caseSensitive,
+ condition
+ } = _extends({
+ caseSensitive: false
+ }, options);
+ const sequence = normalizeSequence(key, caseSensitive).map(key => buildKey(key));
+ const item = {
+ sequence,
+ condition,
+ callback,
+ enabled: false,
+ caseSensitive
+ };
+ if (condition) this._addCondition(condition);
+ this._checkShortcut(item);
+ this._data.push(item);
+ return () => {
+ const index = this._data.indexOf(item);
+ if (index >= 0) {
+ this._data.splice(index, 1);
+ if (condition) this._removeCondition(condition);
+ item.enabled = false;
+ this._enableShortcut(item);
+ }
+ };
+ }
+ setContext(key, value) {
+ this._context[key] = value;
+ for (const cache of Object.values(this._conditionData)) {
+ cache.result = this._evalCondition(cache.value);
+ }
+ for (const item of this._data) {
+ this._checkShortcut(item);
+ }
+ }
+ _handleKeyOnce(keyExps, fromRoot) {
+ var _cur, _cur2;
+ let cur = this._cur;
+ if (fromRoot || !cur) {
+ // set fromRoot to true to avoid another retry
+ fromRoot = true;
+ cur = this._root;
+ }
+ if (cur) {
+ let next;
+ for (const key of keyExps) {
+ next = getKeyNode(cur, [key]);
+ if (next) {
+ this.sequence.set([...this.sequence.get(), key]);
+ break;
+ }
+ }
+ cur = next;
+ }
+ this._cur = cur;
+ const [shortcut] = [...(((_cur = cur) == null ? void 0 : _cur.shortcuts) || [])];
+ if (!fromRoot && !shortcut && !((_cur2 = cur) != null && _cur2.children.size)) {
+ // Nothing is matched with the last key, rematch from root
+ this._reset();
+ return this._handleKeyOnce(keyExps, true);
+ }
+ if (shortcut) {
+ try {
+ shortcut.callback();
+ } catch (_unused) {
+ // ignore
+ }
+ return 2;
+ }
+ return this._cur ? 1 : 0;
+ }
+ repr() {
+ return reprNodeTree(this._root);
+ }
+}
+KeyboardService.defaultOptions = {
+ sequenceTimeout: 500
+};
+let service;
+function getService() {
+ if (!service) {
+ service = new KeyboardService();
+ service.enable();
+ }
+ return service;
+}
+const register = (...args) => getService().register(...args);
+const enable = () => getService().enable();
+const disable = () => getService().disable();
+const handleKey = (...args) => getService().handleKey(...args);
+
+export { KeyboardService, aliases, buildKey, disable, enable, getService, handleKey, isMacintosh, modifierAliases, modifierList, modifierSymbols, modifiers, normalizeSequence, parseCondition, parseKey, register, reprKey, reprShortcut, version };