react-hotkeys
Version:
A declarative library for handling hotkeys and focus within a React application
456 lines (389 loc) • 15.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _KeyEventSequenceIndex = _interopRequireDefault(require("../../const/KeyEventSequenceIndex"));
var _KeyEventType = _interopRequireDefault(require("../../const/KeyEventType"));
var _KeyCombinationSerializer = _interopRequireDefault(require("../shared/KeyCombinationSerializer"));
var _resolveKeyAlias = _interopRequireDefault(require("../../helpers/resolving-handlers/resolveKeyAlias"));
var _applicableAliasFunctions = _interopRequireDefault(require("../../helpers/resolving-handlers/applicableAliasFunctions"));
var _KeyEventStateArrayManager = _interopRequireDefault(require("../shared/KeyEventStateArrayManager"));
var _isEmpty = _interopRequireDefault(require("../../utils/collection/isEmpty"));
var _size = _interopRequireDefault(require("../../utils/collection/size"));
var _KeyEventState = _interopRequireDefault(require("../../const/KeyEventState"));
var _dictionaryFrom = _interopRequireDefault(require("../../utils/object/dictionaryFrom"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
/**
* Record of one or more keys pressed together, in a combination
* @class
*/
var KeyCombination =
/*#__PURE__*/
function () {
/**
* Creates a new KeyCombination instance
* @param {Object.<ReactKeyName, Array.<KeyEventState[]>>} keys Dictionary
* of keys
* @returns {KeyCombination}
*/
function KeyCombination() {
var keys = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
_classCallCheck(this, KeyCombination);
this._keys = keys;
this._includesKeyUp = false;
this._update();
}
/********************************************************************************
* Getters
*********************************************************************************/
/**
* List of ids (serialized representations) for the keys involved in the combination
* @returns {KeySequence[]} List of combination ids
*/
_createClass(KeyCombination, [{
key: "getIds",
value: function getIds() {
return this._ids;
}
/**
* Dictionary mapping keys to their acceptable aliases. This includes "shifted" or
* "alted" key characters.
* @returns {Object.<ReactKeyName, ReactKeyName[]>}
*/
}, {
key: "getKeyAliases",
value: function getKeyAliases() {
return this._keyAliases;
}
/**
* A normalized version of the key, achieved by comparing it to the list of known
* aliases for the keys in the combination
* @param {ReactKeyName} keyName Name of the key to normalize
* @returns {ReactKeyName} Normalized key name
*/
}, {
key: "getNormalizedKeyName",
value: function getNormalizedKeyName(keyName) {
var keyState = this._keys[keyName];
if (keyState) {
return keyName;
} else {
var keyAlias = this._keyAliases[keyName];
if (keyAlias) {
return keyAlias;
} else {
return keyName;
}
}
}
/********************************************************************************
* Query attributes of entire combination
*********************************************************************************/
/**
* Number of keys involved in the combination
* @returns {number} Number of keys
*/
}, {
key: "getNumberOfKeys",
value: function getNumberOfKeys() {
return (0, _size.default)(this._keys);
}
/**
* Whether there are any keys in the combination
* @returns {boolean} true if there is 1 or more keys involved in the combination,
* else false.
*/
}, {
key: "any",
value: function any() {
return Object.keys(this._getKeyStates()).length > 0;
}
/**
* Whether any of the keys in the combination have been released
* @returns {boolean} true if at least 1 key has been released in the combination
*/
}, {
key: "isEnding",
value: function isEnding() {
return this._includesKeyUp;
}
/**
* Whether there are any keys in the current combination still being pressed
* @returns {boolean} True if all keys in the current combination are released
*/
}, {
key: "hasEnded",
value: function hasEnded() {
return (0, _isEmpty.default)(this.keysStillPressedDict());
}
/********************************************************************************
* Adding & modifying key states
*********************************************************************************/
/**
* Add a new key to the combination (starting with a state of keydown)
* @param {ReactKeyName} keyName Name of key
* @param {KeyEventState} keyEventState State key is in
* @returns {void}
*/
}, {
key: "addKey",
value: function addKey(keyName, keyEventState) {
this._setKeyState(keyName, [_KeyEventStateArrayManager.default.newRecord(), _KeyEventStateArrayManager.default.newRecord(_KeyEventType.default.keydown, keyEventState)]);
}
/**
* Adds a key event to the current key combination (as opposed to starting a new
* keyboard combination).
* @param {ReactKeyName} keyName - Name of the key to add to the current combination
* @param {KeyEventType} recordIndex - Index in record to set to true
* @param {KeyEventState} keyEventState The state to set the key event to
*/
}, {
key: "setKeyState",
value: function setKeyState(keyName, recordIndex, keyEventState) {
var existingRecord = this._getKeyState(keyName);
if (this.isKeyIncluded(keyName)) {
var previous = _KeyEventStateArrayManager.default.clone(existingRecord[1]);
var current = _KeyEventStateArrayManager.default.clone(previous);
_KeyEventStateArrayManager.default.setBit(current, recordIndex, keyEventState);
this._setKeyState(keyName, [previous, current]);
} else {
this.addKey(keyName, keyEventState);
}
if (recordIndex === _KeyEventType.default.keyup) {
this._includesKeyUp = true;
}
}
/********************************************************************************
* Iteration and subsets
*********************************************************************************/
/**
* @callback forEachHandler
* @param {ReactKeyName} keyName Name of a key in the combination
* @returns {void}
*/
/**
* Iterates over every key in the combination, calling an function with each
* key name
* @param {forEachHandler} handler Function to call with the name of each key
* in the combination
* @returns {void}
*/
}, {
key: "forEachKey",
value: function forEachKey(handler) {
return Object.keys(this._keys).forEach(handler);
}
/**
* @callback evaluator
* @param {ReactKeyName} keyName Name of a key in the combination
* @returns {boolean}
*/
/**
* Whether at least one of the keys causes a evaluator function to return true
* @callback {evaluator} evaluator Function to evaluate each key
* @returns {boolean} Whether at least one key satisfies the evaluator
*/
}, {
key: "some",
value: function some(evaluator) {
return Object.keys(this._keys).some(evaluator);
}
/**
* Dictionary of keys included in the combination record
* @returns {Object.<ReactKeyName, boolean>}
*/
}, {
key: "getKeyDictionary",
value: function getKeyDictionary() {
return (0, _dictionaryFrom.default)(Object.keys(this._getKeyStates()), true);
}
/**
* Returns a new KeyCombination without the keys that have been
* released (had the keyup event recorded). Essentially, the keys that are
* currently still pressed down at the time a key event is being handled.
* @returns {KeyCombination} New KeyCombination with all of the
* keys with keyup events omitted
*/
}, {
key: "keysStillPressedDict",
value: function keysStillPressedDict() {
var _this = this;
return Object.keys(this._keys).reduce(function (memo, keyName) {
if (_this.isKeyStillPressed(keyName)) {
memo[keyName] = _this._getKeyState(keyName);
}
return memo;
}, {});
}
/********************************************************************************
* Query individual keys
*********************************************************************************/
/**
* Whether key is in the combination
* @param {ReactKeyName} keyName Name of key
* @returns {boolean} true if the key is in the combination
*/
}, {
key: "isKeyIncluded",
value: function isKeyIncluded(keyName) {
return !!this._getKeyState(keyName);
}
/**
* Whether key is in the combination and has yet to be released
* @param {ReactKeyName} keyName Name of key
* @returns {boolean} true if the key is in the combination and yet to be released
*/
}, {
key: "isKeyStillPressed",
value: function isKeyStillPressed(keyName) {
return this.isEventTriggered(keyName, _KeyEventType.default.keypress) && !this.isKeyReleased(keyName);
}
/**
* Whether key is in the combination and been released
* @param {ReactKeyName} keyName Name of key
* @returns {boolean} true if the key is in the combination and has been released
*/
}, {
key: "isKeyReleased",
value: function isKeyReleased(keyName) {
return this.isEventTriggered(keyName, _KeyEventType.default.keyup);
}
/**
* Whether an event has been recorded for a key yet
* @param {ReactKeyName} keyName Name of the key
* @param {KeyEventType} keyEventType Index of the event type
* @returns {boolean} true if the event has been recorded for the key
*/
}, {
key: "isEventTriggered",
value: function isEventTriggered(keyName, keyEventType) {
return this._getKeyStateType(keyName, _KeyEventSequenceIndex.default.current, keyEventType);
}
/**
* Whether an event has been previously recorded for a key (the second most recent
* event to occur for the key)
* @param {ReactKeyName} keyName Name of the key
* @param {KeyEventType} keyEventType Index of the event type
* @returns {boolean} true if the event has been previously recorded for the key
*/
}, {
key: "wasEventPreviouslyTriggered",
value: function wasEventPreviouslyTriggered(keyName, keyEventType) {
return this._getKeyStateType(keyName, _KeyEventSequenceIndex.default.previous, keyEventType);
}
/**
* Whether a keypress event is currently being simulated
* @param {ReactKeyName} keyName Name of the key
* @returns {boolean} true if the keypress event is currently being simulated for the
* key
*/
}, {
key: "isKeyPressSimulated",
value: function isKeyPressSimulated(keyName) {
return this._isKeyEventSimulated(keyName, _KeyEventType.default.keypress);
}
/**
* Whether a keyup event is currently being simulated
* @param {ReactKeyName} keyName Name of the key
* @returns {boolean} true if the keyup event is currently being simulated for the
* key
*/
}, {
key: "isKeyUpSimulated",
value: function isKeyUpSimulated(keyName) {
return this._isKeyEventSimulated(keyName, _KeyEventType.default.keyup);
}
/********************************************************************************
* Presentation
*********************************************************************************/
/**
* Return a serialized description of the keys in the combination
* @returns {KeySequence}
*/
}, {
key: "describe",
value: function describe() {
return this.getIds()[0];
}
/**
* A plain JavaScript representation of the key combination record, useful for
* serialization or debugging
* @returns {Object} Serialized representation of the combination record
*/
}, {
key: "toJSON",
value: function toJSON() {
return {
keys: this._getKeyStates(),
ids: this.getIds(),
keyAliases: this.getKeyAliases()
};
}
/********************************************************************************
* Private methods
*********************************************************************************/
}, {
key: "_getKeyStateType",
value: function _getKeyStateType(keyName, keyStage, keyEventType) {
var keyState = this._getKeyState(keyName);
return keyState && keyState[keyStage][keyEventType];
}
}, {
key: "_update",
value: function _update() {
this._ids = _KeyCombinationSerializer.default.serialize(this._keys);
this._keyAliases = buildKeyAliases(this._keys);
}
}, {
key: "_isKeyEventSimulated",
value: function _isKeyEventSimulated(keyName, keyEventType) {
return this.isEventTriggered(keyName, keyEventType) === _KeyEventState.default.simulated;
}
}, {
key: "_getKeyStates",
value: function _getKeyStates() {
return this._keys;
}
}, {
key: "_getKeyState",
value: function _getKeyState(keyName) {
var keyState = this._keys[keyName];
if (keyState) {
return keyState;
} else {
var keyAlias = this._keyAliases[keyName];
if (keyAlias) {
return this._keys[keyAlias];
}
}
}
}, {
key: "_setKeyState",
value: function _setKeyState(keyName, keyState) {
var keyAlias = this.getNormalizedKeyName(keyName);
this._keys[keyAlias] = keyState;
this._update();
}
}]);
return KeyCombination;
}();
function buildKeyAliases(keyDictionary) {
return Object.keys(keyDictionary).reduce(function (memo, keyName) {
(0, _resolveKeyAlias.default)(keyName).forEach(function (normalizedKey) {
(0, _applicableAliasFunctions.default)(keyDictionary).forEach(function (aliasFunction) {
aliasFunction(normalizedKey).forEach(function (keyAlias) {
if (keyAlias !== keyName || keyName !== normalizedKey) {
memo[keyAlias] = keyName;
}
});
});
});
return memo;
}, {});
}
var _default = KeyCombination;
exports.default = _default;