@phosphor/commands
Version:
PhosphorJS - Commands
917 lines (916 loc) • 33.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/*-----------------------------------------------------------------------------
| Copyright (c) 2014-2017, PhosphorJS Contributors
|
| Distributed under the terms of the BSD 3-Clause License.
|
| The full license is in the file LICENSE, distributed with this software.
|----------------------------------------------------------------------------*/
var algorithm_1 = require("@phosphor/algorithm");
var coreutils_1 = require("@phosphor/coreutils");
var disposable_1 = require("@phosphor/disposable");
var domutils_1 = require("@phosphor/domutils");
var keyboard_1 = require("@phosphor/keyboard");
var signaling_1 = require("@phosphor/signaling");
/**
* An object which manages a collection of commands.
*
* #### Notes
* A command registry can be used to populate a variety of action-based
* widgets, such as command palettes, menus, and toolbars.
*/
var CommandRegistry = /** @class */ (function () {
/**
* Construct a new command registry.
*/
function CommandRegistry() {
this._timerID = 0;
this._replaying = false;
this._keystrokes = [];
this._keydownEvents = [];
this._keyBindings = [];
this._exactKeyMatch = null;
this._commands = Object.create(null);
this._commandChanged = new signaling_1.Signal(this);
this._commandExecuted = new signaling_1.Signal(this);
this._keyBindingChanged = new signaling_1.Signal(this);
}
Object.defineProperty(CommandRegistry.prototype, "commandChanged", {
/**
* A signal emitted when a command has changed.
*
* #### Notes
* This signal is useful for visual representations of commands which
* need to refresh when the state of a relevant command has changed.
*/
get: function () {
return this._commandChanged;
},
enumerable: true,
configurable: true
});
Object.defineProperty(CommandRegistry.prototype, "commandExecuted", {
/**
* A signal emitted when a command has executed.
*
* #### Notes
* Care should be taken when consuming this signal. It is intended to
* be used largely for debugging and logging purposes. It should not
* be (ab)used for general purpose spying on command execution.
*/
get: function () {
return this._commandExecuted;
},
enumerable: true,
configurable: true
});
Object.defineProperty(CommandRegistry.prototype, "keyBindingChanged", {
/**
* A signal emitted when a key binding is changed.
*/
get: function () {
return this._keyBindingChanged;
},
enumerable: true,
configurable: true
});
Object.defineProperty(CommandRegistry.prototype, "keyBindings", {
/**
* A read-only array of the key bindings in the registry.
*/
get: function () {
return this._keyBindings;
},
enumerable: true,
configurable: true
});
/**
* List the ids of the registered commands.
*
* @returns A new array of the registered command ids.
*/
CommandRegistry.prototype.listCommands = function () {
return Object.keys(this._commands);
};
/**
* Test whether a specific command is registered.
*
* @param id - The id of the command of interest.
*
* @returns `true` if the command is registered, `false` otherwise.
*/
CommandRegistry.prototype.hasCommand = function (id) {
return id in this._commands;
};
/**
* Add a command to the registry.
*
* @param id - The unique id of the command.
*
* @param options - The options for the command.
*
* @returns A disposable which will remove the command.
*
* @throws An error if the given `id` is already registered.
*/
CommandRegistry.prototype.addCommand = function (id, options) {
var _this = this;
// Throw an error if the id is already registered.
if (id in this._commands) {
throw new Error("Command '" + id + "' already registered.");
}
// Add the command to the registry.
this._commands[id] = Private.createCommand(options);
// Emit the `commandChanged` signal.
this._commandChanged.emit({ id: id, type: 'added' });
// Return a disposable which will remove the command.
return new disposable_1.DisposableDelegate(function () {
// Remove the command from the registry.
delete _this._commands[id];
// Emit the `commandChanged` signal.
_this._commandChanged.emit({ id: id, type: 'removed' });
});
};
/**
* Notify listeners that the state of a command has changed.
*
* @param id - The id of the command which has changed. If more than
* one command has changed, this argument should be omitted.
*
* @throws An error if the given `id` is not registered.
*
* #### Notes
* This method should be called by the command author whenever the
* application state changes such that the results of the command
* metadata functions may have changed.
*
* This will cause the `commandChanged` signal to be emitted.
*/
CommandRegistry.prototype.notifyCommandChanged = function (id) {
if (id !== undefined && !(id in this._commands)) {
throw new Error("Command '" + id + "' is not registered.");
}
this._commandChanged.emit({ id: id, type: id ? 'changed' : 'many-changed' });
};
/**
* Get the display label for a specific command.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns The display label for the command, or an empty string
* if the command is not registered.
*/
CommandRegistry.prototype.label = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
var cmd = this._commands[id];
return cmd ? cmd.label.call(undefined, args) : '';
};
/**
* Get the mnemonic index for a specific command.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns The mnemonic index for the command, or `-1` if the
* command is not registered.
*/
CommandRegistry.prototype.mnemonic = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
var cmd = this._commands[id];
return cmd ? cmd.mnemonic.call(undefined, args) : -1;
};
/**
* @deprecated Use `iconClass()` instead.
*/
CommandRegistry.prototype.icon = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
return this.iconClass(id, args);
};
/**
* Get the icon class for a specific command.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns The icon class for the command, or an empty string if
* the command is not registered.
*/
CommandRegistry.prototype.iconClass = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
var cmd = this._commands[id];
return cmd ? cmd.iconClass.call(undefined, args) : '';
};
/**
* Get the icon label for a specific command.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns The icon label for the command, or an empty string if
* the command is not registered.
*/
CommandRegistry.prototype.iconLabel = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
var cmd = this._commands[id];
return cmd ? cmd.iconLabel.call(undefined, args) : '';
};
/**
* Get the short form caption for a specific command.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns The caption for the command, or an empty string if the
* command is not registered.
*/
CommandRegistry.prototype.caption = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
var cmd = this._commands[id];
return cmd ? cmd.caption.call(undefined, args) : '';
};
/**
* Get the usage help text for a specific command.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns The usage text for the command, or an empty string if
* the command is not registered.
*/
CommandRegistry.prototype.usage = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
var cmd = this._commands[id];
return cmd ? cmd.usage.call(undefined, args) : '';
};
/**
* Get the extra class name for a specific command.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns The class name for the command, or an empty string if
* the command is not registered.
*/
CommandRegistry.prototype.className = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
var cmd = this._commands[id];
return cmd ? cmd.className.call(undefined, args) : '';
};
/**
* Get the dataset for a specific command.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns The dataset for the command, or an empty dataset if
* the command is not registered.
*/
CommandRegistry.prototype.dataset = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
var cmd = this._commands[id];
return cmd ? cmd.dataset.call(undefined, args) : {};
};
/**
* Test whether a specific command is enabled.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns A boolean indicating whether the command is enabled,
* or `false` if the command is not registered.
*/
CommandRegistry.prototype.isEnabled = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
var cmd = this._commands[id];
return cmd ? cmd.isEnabled.call(undefined, args) : false;
};
/**
* Test whether a specific command is toggled.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns A boolean indicating whether the command is toggled,
* or `false` if the command is not registered.
*/
CommandRegistry.prototype.isToggled = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
var cmd = this._commands[id];
return cmd ? cmd.isToggled.call(undefined, args) : false;
};
/**
* Test whether a specific command is visible.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns A boolean indicating whether the command is visible,
* or `false` if the command is not registered.
*/
CommandRegistry.prototype.isVisible = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
var cmd = this._commands[id];
return cmd ? cmd.isVisible.call(undefined, args) : false;
};
/**
* Execute a specific command.
*
* @param id - The id of the command of interest.
*
* @param args - The arguments for the command.
*
* @returns A promise which resolves with the result of the command.
*
* #### Notes
* The promise will reject if the command throws an exception,
* or if the command is not registered.
*/
CommandRegistry.prototype.execute = function (id, args) {
if (args === void 0) { args = coreutils_1.JSONExt.emptyObject; }
// Reject if the command is not registered.
var cmd = this._commands[id];
if (!cmd) {
return Promise.reject(new Error("Command '" + id + "' not registered."));
}
// Execute the command and reject if an exception is thrown.
var value;
try {
value = cmd.execute.call(undefined, args);
}
catch (err) {
value = Promise.reject(err);
}
// Create the return promise which resolves the result.
var result = Promise.resolve(value);
// Emit the command executed signal.
this._commandExecuted.emit({ id: id, args: args, result: result });
// Return the result promise to the caller.
return result;
};
/**
* Add a key binding to the registry.
*
* @param options - The options for creating the key binding.
*
* @returns A disposable which removes the added key binding.
*
* #### Notes
* If multiple key bindings are registered for the same sequence, the
* binding with the highest selector specificity is executed first. A
* tie is broken by using the most recently added key binding.
*
* Ambiguous key bindings are resolved with a timeout. As an example,
* suppose two key bindings are registered: one with the key sequence
* `['Ctrl D']`, and another with `['Ctrl D', 'Ctrl W']`. If the user
* presses `Ctrl D`, the first binding cannot be immediately executed
* since the user may intend to complete the chord with `Ctrl W`. For
* such cases, a timer is used to allow the chord to be completed. If
* the chord is not completed before the timeout, the first binding
* is executed.
*/
CommandRegistry.prototype.addKeyBinding = function (options) {
var _this = this;
// Create the binding for the given options.
var binding = Private.createKeyBinding(options);
// Add the key binding to the bindings array.
this._keyBindings.push(binding);
// Emit the `bindingChanged` signal.
this._keyBindingChanged.emit({ binding: binding, type: 'added' });
// Return a disposable which will remove the binding.
return new disposable_1.DisposableDelegate(function () {
// Remove the binding from the array.
algorithm_1.ArrayExt.removeFirstOf(_this._keyBindings, binding);
// Emit the `bindingChanged` signal.
_this._keyBindingChanged.emit({ binding: binding, type: 'removed' });
});
};
/**
* Process a `'keydown'` event and invoke a matching key binding.
*
* @param event - The event object for a `'keydown'` event.
*
* #### Notes
* This should be called in response to a `'keydown'` event in order
* to invoke the command for the best matching key binding.
*
* The registry **does not** install its own listener for `'keydown'`
* events. This allows the application full control over the nodes
* and phase for which the registry processes `'keydown'` events.
*/
CommandRegistry.prototype.processKeydownEvent = function (event) {
// Bail immediately if playing back keystrokes.
if (this._replaying) {
return;
}
// Get the normalized keystroke for the event.
var keystroke = CommandRegistry.keystrokeForKeydownEvent(event);
// If the keystroke is not valid for the keyboard layout, replay
// any suppressed events and clear the pending state.
if (!keystroke) {
this._replayKeydownEvents();
this._clearPendingState();
return;
}
// Add the keystroke to the current key sequence.
this._keystrokes.push(keystroke);
// Find the exact and partial matches for the key sequence.
var _a = Private.matchKeyBinding(this._keyBindings, this._keystrokes, event), exact = _a.exact, partial = _a.partial;
// If there is no exact match and no partial match, replay
// any suppressed events and clear the pending state.
if (!exact && !partial) {
this._replayKeydownEvents();
this._clearPendingState();
return;
}
// Stop propagation of the event. If there is only a partial match,
// the event will be replayed if a final exact match never occurs.
event.preventDefault();
event.stopPropagation();
// If there is an exact match but no partial match, the exact match
// can be dispatched immediately. The pending state is cleared so
// the next key press starts from the default state.
if (exact && !partial) {
this._executeKeyBinding(exact);
this._clearPendingState();
return;
}
// If there is both an exact match and a partial match, the exact
// match is stored for future dispatch in case the timer expires
// before a more specific match is triggered.
if (exact) {
this._exactKeyMatch = exact;
}
// Store the event for possible playback in the future.
this._keydownEvents.push(event);
// (Re)start the timer to dispatch the most recent exact match
// in case the partial match fails to result in an exact match.
this._startTimer();
};
/**
* Start or restart the pending timeout.
*/
CommandRegistry.prototype._startTimer = function () {
var _this = this;
this._clearTimer();
this._timerID = window.setTimeout(function () {
_this._onPendingTimeout();
}, Private.CHORD_TIMEOUT);
};
/**
* Clear the pending timeout.
*/
CommandRegistry.prototype._clearTimer = function () {
if (this._timerID !== 0) {
clearTimeout(this._timerID);
this._timerID = 0;
}
};
/**
* Replay the keydown events which were suppressed.
*/
CommandRegistry.prototype._replayKeydownEvents = function () {
if (this._keydownEvents.length === 0) {
return;
}
this._replaying = true;
this._keydownEvents.forEach(Private.replayKeyEvent);
this._replaying = false;
};
/**
* Execute the command for the given key binding.
*
* If the command is missing or disabled, a warning will be logged.
*/
CommandRegistry.prototype._executeKeyBinding = function (binding) {
var command = binding.command, args = binding.args;
if (!this.hasCommand(command) || !this.isEnabled(command, args)) {
var word = this.hasCommand(command) ? 'enabled' : 'registered';
var keys = binding.keys.join(', ');
var msg1 = "Cannot execute key binding '" + keys + "':";
var msg2 = "command '" + command + "' is not " + word + ".";
console.warn(msg1 + " " + msg2);
return;
}
this.execute(command, args);
};
/**
* Clear the internal pending state.
*/
CommandRegistry.prototype._clearPendingState = function () {
this._clearTimer();
this._exactKeyMatch = null;
this._keystrokes.length = 0;
this._keydownEvents.length = 0;
};
/**
* Handle the partial match timeout.
*/
CommandRegistry.prototype._onPendingTimeout = function () {
this._timerID = 0;
if (this._exactKeyMatch) {
this._executeKeyBinding(this._exactKeyMatch);
}
else {
this._replayKeydownEvents();
}
this._clearPendingState();
};
return CommandRegistry;
}());
exports.CommandRegistry = CommandRegistry;
/**
* The namespace for the `CommandRegistry` class statics.
*/
(function (CommandRegistry) {
/**
* Parse a keystroke into its constituent components.
*
* @param keystroke - The keystroke of interest.
*
* @returns The parsed components of the keystroke.
*
* #### Notes
* The keystroke should be of the form:
* `[<modifier 1> [<modifier 2> [<modifier N> ]]]<primary key>`
*
* The supported modifiers are: `Accel`, `Alt`, `Cmd`, `Ctrl`, and
* `Shift`. The `Accel` modifier is translated to `Cmd` on Mac and
* `Ctrl` on all other platforms.
*
* The parsing is tolerant and will not throw exceptions. Notably:
* - Duplicate modifiers are ignored.
* - Extra primary keys are ignored.
* - The order of modifiers and primary key is irrelevant.
* - The keystroke parts should be separated by whitespace.
* - The keystroke is case sensitive.
*/
function parseKeystroke(keystroke) {
var key = '';
var alt = false;
var cmd = false;
var ctrl = false;
var shift = false;
for (var _i = 0, _a = keystroke.split(/\s+/); _i < _a.length; _i++) {
var token = _a[_i];
if (token === 'Accel') {
if (domutils_1.Platform.IS_MAC) {
cmd = true;
}
else {
ctrl = true;
}
}
else if (token === 'Alt') {
alt = true;
}
else if (token === 'Cmd') {
cmd = true;
}
else if (token === 'Ctrl') {
ctrl = true;
}
else if (token === 'Shift') {
shift = true;
}
else if (token.length > 0) {
key = token;
}
}
return { cmd: cmd, ctrl: ctrl, alt: alt, shift: shift, key: key };
}
CommandRegistry.parseKeystroke = parseKeystroke;
/**
* Normalize a keystroke into a canonical representation.
*
* @param keystroke - The keystroke of interest.
*
* @returns The normalized representation of the keystroke.
*
* #### Notes
* This normalizes the keystroke by removing duplicate modifiers and
* extra primary keys, and assembling the parts in a canonical order.
*
* The `Cmd` modifier is ignored on non-Mac platforms.
*/
function normalizeKeystroke(keystroke) {
var mods = '';
var parts = parseKeystroke(keystroke);
if (parts.ctrl) {
mods += 'Ctrl ';
}
if (parts.alt) {
mods += 'Alt ';
}
if (parts.shift) {
mods += 'Shift ';
}
if (parts.cmd && domutils_1.Platform.IS_MAC) {
mods += 'Cmd ';
}
return mods + parts.key;
}
CommandRegistry.normalizeKeystroke = normalizeKeystroke;
/**
* Format a keystroke for display on the local system.
*/
function formatKeystroke(keystroke) {
var mods = '';
var parts = parseKeystroke(keystroke);
if (domutils_1.Platform.IS_MAC) {
if (parts.ctrl) {
mods += '\u2303 ';
}
if (parts.alt) {
mods += '\u2325 ';
}
if (parts.shift) {
mods += '\u21E7 ';
}
if (parts.cmd) {
mods += '\u2318 ';
}
}
else {
if (parts.ctrl) {
mods += 'Ctrl+';
}
if (parts.alt) {
mods += 'Alt+';
}
if (parts.shift) {
mods += 'Shift+';
}
}
return mods + parts.key;
}
CommandRegistry.formatKeystroke = formatKeystroke;
/**
* Create a normalized keystroke for a `'keydown'` event.
*
* @param event - The event object for a `'keydown'` event.
*
* @returns A normalized keystroke, or an empty string if the event
* does not represent a valid keystroke for the given layout.
*/
function keystrokeForKeydownEvent(event) {
var key = keyboard_1.getKeyboardLayout().keyForKeydownEvent(event);
if (!key) {
return '';
}
var mods = '';
if (event.ctrlKey) {
mods += 'Ctrl ';
}
if (event.altKey) {
mods += 'Alt ';
}
if (event.shiftKey) {
mods += 'Shift ';
}
if (event.metaKey && domutils_1.Platform.IS_MAC) {
mods += 'Cmd ';
}
return mods + key;
}
CommandRegistry.keystrokeForKeydownEvent = keystrokeForKeydownEvent;
})(CommandRegistry = exports.CommandRegistry || (exports.CommandRegistry = {}));
exports.CommandRegistry = CommandRegistry;
/**
* The namespace for the module implementation details.
*/
var Private;
(function (Private) {
/**
* The timeout in ms for triggering a key binding chord.
*/
Private.CHORD_TIMEOUT = 1000;
/**
* Create a normalized command from an options object.
*/
function createCommand(options) {
return {
execute: options.execute,
label: asFunc(options.label, emptyStringFunc),
mnemonic: asFunc(options.mnemonic, negativeOneFunc),
iconClass: asFunc(options.iconClass || options.icon, emptyStringFunc),
iconLabel: asFunc(options.iconLabel, emptyStringFunc),
caption: asFunc(options.caption, emptyStringFunc),
usage: asFunc(options.usage, emptyStringFunc),
className: asFunc(options.className, emptyStringFunc),
dataset: asFunc(options.dataset, emptyDatasetFunc),
isEnabled: options.isEnabled || trueFunc,
isToggled: options.isToggled || falseFunc,
isVisible: options.isVisible || trueFunc
};
}
Private.createCommand = createCommand;
/**
* Create a key binding object from key binding options.
*/
function createKeyBinding(options) {
return {
keys: normalizeKeys(options),
selector: validateSelector(options),
command: options.command,
args: options.args || coreutils_1.JSONExt.emptyObject
};
}
Private.createKeyBinding = createKeyBinding;
/**
* Find the key bindings which match a key sequence.
*
* This returns a match result which contains the best exact matching
* binding, and a flag which indicates if there are partial matches.
*/
function matchKeyBinding(bindings, keys, event) {
// The current best exact match.
var exact = null;
// Whether a partial match has been found.
var partial = false;
// The match distance for the exact match.
var distance = Infinity;
// The specificity for the exact match.
var specificity = 0;
// Iterate over the bindings and search for the best match.
for (var i = 0, n = bindings.length; i < n; ++i) {
// Lookup the current binding.
var binding = bindings[i];
// Check whether the key binding sequence is a match.
var sqm = matchSequence(binding.keys, keys);
// If there is no match, the binding is ignored.
if (sqm === 0 /* None */) {
continue;
}
// If it is a partial match and no other partial match has been
// found, ensure the selector matches and set the partial flag.
if (sqm === 2 /* Partial */) {
if (!partial && targetDistance(binding.selector, event) !== -1) {
partial = true;
}
continue;
}
// Ignore the match if the selector doesn't match, or if the
// matched node is farther away than the current best match.
var td = targetDistance(binding.selector, event);
if (td === -1 || td > distance) {
continue;
}
// Get the specificity for the selector.
var sp = domutils_1.Selector.calculateSpecificity(binding.selector);
// Update the best match if this match is stronger.
if (!exact || td < distance || sp >= specificity) {
exact = binding;
distance = td;
specificity = sp;
}
}
// Return the match result.
return { exact: exact, partial: partial };
}
Private.matchKeyBinding = matchKeyBinding;
/**
* Replay a keyboard event.
*
* This synthetically dispatches a clone of the keyboard event.
*/
function replayKeyEvent(event) {
event.target.dispatchEvent(cloneKeyboardEvent(event));
}
Private.replayKeyEvent = replayKeyEvent;
/**
* A singleton empty string function.
*/
var emptyStringFunc = function () { return ''; };
/**
* A singleton `-1` number function
*/
var negativeOneFunc = function () { return -1; };
/**
* A singleton true boolean function.
*/
var trueFunc = function () { return true; };
/**
* A singleton false boolean function.
*/
var falseFunc = function () { return false; };
/**
* A singleton empty dataset function.
*/
var emptyDatasetFunc = function () { return ({}); };
/**
* Cast a value or command func to a command func.
*/
function asFunc(value, dfault) {
if (value === undefined) {
return dfault;
}
if (typeof value === 'function') {
return value;
}
return function () { return value; };
}
/**
* Get the platform-specific normalized keys for an options object.
*/
function normalizeKeys(options) {
var keys;
if (domutils_1.Platform.IS_WIN) {
keys = options.winKeys || options.keys;
}
else if (domutils_1.Platform.IS_MAC) {
keys = options.macKeys || options.keys;
}
else {
keys = options.linuxKeys || options.keys;
}
return keys.map(CommandRegistry.normalizeKeystroke);
}
/**
* Validate the selector for an options object.
*
* This returns the validated selector, or throws if the selector is
* invalid or contains commas.
*/
function validateSelector(options) {
if (options.selector.indexOf(',') !== -1) {
throw new Error("Selector cannot contain commas: " + options.selector);
}
if (!domutils_1.Selector.isValid(options.selector)) {
throw new Error("Invalid selector: " + options.selector);
}
return options.selector;
}
;
/**
* Test whether a key binding sequence matches a key sequence.
*
* Returns a `SequenceMatch` value indicating the type of match.
*/
function matchSequence(bindKeys, userKeys) {
if (bindKeys.length < userKeys.length) {
return 0 /* None */;
}
for (var i = 0, n = userKeys.length; i < n; ++i) {
if (bindKeys[i] !== userKeys[i]) {
return 0 /* None */;
}
}
if (bindKeys.length > userKeys.length) {
return 2 /* Partial */;
}
return 1 /* Exact */;
}
/**
* Find the distance from the target node to the first matching node.
*
* This traverses the event path from `target` to `currentTarget` and
* computes the distance from `target` to the first node which matches
* the CSS selector. If no match is found, `-1` is returned.
*/
function targetDistance(selector, event) {
var targ = event.target;
var curr = event.currentTarget;
for (var dist = 0; targ !== null; targ = targ.parentElement, ++dist) {
if (domutils_1.Selector.matches(targ, selector)) {
return dist;
}
if (targ === curr) {
return -1;
}
}
return -1;
}
/**
* Clone a keyboard event.
*/
function cloneKeyboardEvent(event) {
// A custom event is required because Chrome nulls out the
// `keyCode` field in user-generated `KeyboardEvent` types.
var clone = document.createEvent('Event');
var bubbles = event.bubbles || true;
var cancelable = event.cancelable || true;
clone.initEvent(event.type || 'keydown', bubbles, cancelable);
clone.key = event.key || '';
clone.keyCode = event.keyCode || 0;
clone.which = event.keyCode || 0;
clone.ctrlKey = event.ctrlKey || false;
clone.altKey = event.altKey || false;
clone.shiftKey = event.shiftKey || false;
clone.metaKey = event.metaKey || false;
clone.view = event.view || window;
return clone;
}
})(Private || (Private = {}));