Source: inputs/KeyboardInput.js

import { ManualInput } from 'virtjs/devices/inputs/ManualInput';

let DEFAULT_KEY_MAP = {

    /* eslint-disable no-magic-numbers */

    37: [ 0, `LEFT` ],   // arrow left
    39: [ 0, `RIGHT` ],  // arrow right
    38: [ 0, `UP` ],     // arrow up
    40: [ 0, `DOWN` ],   // arrow down

    65: [ 0, `A` ],      // 'A'
    81: [ 0, `A` ],      // 'Q'

    90: [ 0, `B` ],      // 'Z'
    87: [ 0, `B` ],      // 'W'
    66: [ 0, `B` ],      // 'B'

    76: [ 0, `L` ],      // 'L'
    82: [ 0, `R` ],      // 'R'

    13: [ 0, `START` ],  // enter

    8: [ 0, `SELECT` ],  // backspace
    32: [ 0, `SELECT` ]  // space

    /* eslint-enable no-magic-numbers */

};

export class KeyboardInput {

    /**
     * A KeyboardInput is an input device that will monitor the keystrokes on a specified DOM element and transmit those actions to the engines.
     *
     * @constructor
     * @implements {Input}
     *
     * @param {object} [options] - The device options.
     * @param {Element} [options.element] - The element on which will be bound the DOM listeners.
     * @param {KeyMap} [options.keyMap] - The initial key map.
     */

    constructor({ element = document.body, keyMap = DEFAULT_KEY_MAP, inputMap = null } = {}) {

        /**
         * This value contains the element on which the DOM listeners have been bound.
         *
         * @member
         * @readonly
         * @type {Element}
         */

        this.element = null;

        /**
         * This value contains the current key map used to filter keys.
         *
         * @member
         * @readonly
         * @type {KeyMap}
         */

        this.keyMap = null;

        this.input = new ManualInput({ inputMap });

        this.onKeyDown = this.onKeyDown.bind(this);
        this.onKeyUp = this.onKeyUp.bind(this);

        this.setKeyMap(keyMap);
        this.setElement(element);

    }

    /**
     * Change the element on which are bound the DOM listeners.
     */

    setElement(element) {

        if (element === this.element)
            return;

        if (this.element !== null)
            this.detachEvents();

        this.element = element;

        if (this.element !== null) {
            this.attachEvents();
        }

    }

    /**
     * Set the key map that will be used to translate key codes into inputs.
     *
     * Any old key that doesn't map to the same input anymore will be automatically released.
     *
     * @param {KeyMap} keyMap - The new key map.
     */

    setKeyMap(keyMap) {

        if (keyMap === this.keyMap)
            return;

        if (this.keyMap) {
            for (let key of Reflect.ownKeys(this.keyMap)) {

                let [ port, code ] = this.keyMap[key];

                if (keyMap && Reflect.has(keyMap, key) && keyMap[key][0] === port && keyMap[key][1] === code)
                    continue;

                this.input.up(port, code);

            }
        }

        this.keyMap = keyMap;

    }

    /**
     * @borrows ManualInput#setCodeMap as KeyboardInput#setCodeMap
     */

    setCodeMap(codeMap) {

        this.input.setCodeMap(codeMap);

    }

    pollInputs() {

        this.input.pollInputs();

    }

    getState(port, code) {

        return this.input.getState(port, code);

    }

    attachEvents() {

        this.element.addEventListener(`keydown`, this.onKeyDown);
        this.element.addEventListener(`keyup`, this.onKeyUp);

    }

    detachEvents() {

        this.element.removeEventListener(`keydown`, this.onKeyDown);
        this.element.removeEventListener(`keyup`, this.onKeyUp);

    }

    onKeyDown(e) {

        if ([ `select`, `input`, `textarea` ].includes(e.target.tagName.toLowerCase()))
            return;

        if (e.keyCode === 8 /* backspace */) // eslint-disable-line no-magic-numbers
            e.preventDefault();

        if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
            return;

        if (!Reflect.has(this.keyMap, e.keyCode))
            return;

        e.preventDefault();

        let [ port, code ] = this.keyMap[e.keyCode];
        this.input.down(port, code);

    }

    onKeyUp(e) {

        if (!Reflect.has(this.keyMap, e.keyCode))
            return;

        let [ port, code ] = this.keyMap[e.keyCode];
        this.input.up(port, code);

    }

}