124 lines
2.9 KiB
JavaScript
124 lines
2.9 KiB
JavaScript
import * as I from 'immutable';
|
|
|
|
import {compose} from '@avocado/core';
|
|
import {EventEmitter} from '@avocado/mixins';
|
|
|
|
const decorate = compose(
|
|
EventEmitter,
|
|
);
|
|
|
|
class ActionRegistryBase {
|
|
|
|
static normalizeKey(key) {
|
|
return key.toLowerCase();
|
|
}
|
|
|
|
constructor() {
|
|
// Track events.
|
|
this.target = undefined;
|
|
this.mapActionToKey = {};
|
|
this.mapKeyToAction = {};
|
|
// Track actions.
|
|
this._state = I.Set();
|
|
// Handle lame OS input event behavior. See: https://mzl.la/2Ob0WQE
|
|
this.keysDown = {};
|
|
this.keyUpDelays = {};
|
|
// Bind event handlers.
|
|
this.onBlur = this.onBlur.bind(this);
|
|
this.onKeyDown = this.onKeyDown.bind(this);
|
|
this.onKeyUp = this.onKeyUp.bind(this);
|
|
}
|
|
|
|
actionForKey(key) {
|
|
return this.mapKeyToAction[key];
|
|
}
|
|
|
|
listen(target = window.document) {
|
|
// Only listen once.
|
|
if (this.target) {
|
|
return;
|
|
}
|
|
this.target = target;
|
|
this.target.addEventListener('blur', this.onBlur);
|
|
this.target.addEventListener('keydown', this.onKeyDown);
|
|
this.target.addEventListener('keyup', this.onKeyUp);
|
|
}
|
|
|
|
keyForAction(action) {
|
|
return this.mapActionToKey[action];
|
|
}
|
|
|
|
mapKeysToActions(map) {
|
|
for (const key in map) {
|
|
const action = map[key];
|
|
this.mapKeyToAction[key] = action;
|
|
this.mapActionToKey[action] = key;
|
|
}
|
|
}
|
|
|
|
onBlur(event) {
|
|
event = event || window.event;
|
|
this.setAllKeysUp();
|
|
}
|
|
|
|
onKeyDown(event) {
|
|
event = event || window.event;
|
|
const key = this.constructor.normalizeKey(event.key);
|
|
if (this.keysDown[key]) {
|
|
if (this.keyUpDelays[key]) {
|
|
clearTimeout(this.keyUpDelays[key]);
|
|
delete this.keyUpDelays[key];
|
|
}
|
|
return;
|
|
}
|
|
this.keysDown[key] = true;
|
|
if (this.mapKeyToAction[key]) {
|
|
const action = this.mapKeyToAction[key];
|
|
this._state = this._state.add(action);
|
|
this.emit('actionStart', action);
|
|
}
|
|
}
|
|
|
|
onKeyUp(event) {
|
|
event = event || window.event;
|
|
const key = this.constructor.normalizeKey(event.key);
|
|
this.keyUpDelays[key] = setTimeout(() => {
|
|
delete this.keyUpDelays[key];
|
|
delete this.keysDown[key];
|
|
if (this.mapKeyToAction[key]) {
|
|
const action = this.mapKeyToAction[key];
|
|
this._state = this._state.delete(action);
|
|
this.emit('actionStop', action);
|
|
}
|
|
}, 20);
|
|
}
|
|
|
|
setAllKeysUp() {
|
|
this.keysDown = {};
|
|
for (const key in this.keyUpDelays) {
|
|
const handle = this.keyUpDelays[key];
|
|
clearTimeout(handle);
|
|
}
|
|
this.keyUpDelays = {};
|
|
this._state = I.Set();
|
|
}
|
|
|
|
state() {
|
|
return this._state;
|
|
}
|
|
|
|
stopListening() {
|
|
this.setAllKeysUp();
|
|
if (!this.target) {
|
|
return;
|
|
}
|
|
this.target.removeEventListener('blur', this.onBlur);
|
|
this.target.removeEventListener('keydown', this.onKeyDown);
|
|
this.target.removeEventListener('keyup', this.onKeyUp);
|
|
this.target = undefined;
|
|
}
|
|
|
|
}
|
|
|
|
export class ActionRegistry extends decorate(ActionRegistryBase) {};
|