gristlabs_grist-core/app/client/lib/Mousetrap.js
Dmitry S 90db5020c9 (core) Improve focus and keyboard shortcuts in modals.
Summary:
- Factor out focusing logic from Clipboard to FocusLayer.
- Generalize FocusLayer to support adding a temporary layer while a modal is open.
- Stop Mousetrap shortcuts while a modal is open.
- Refactor how Mousetrap's custom stopCallback is implemented to avoid
  needing to bundle knockout for mousetrap.

Test Plan: Added a test that Enter in a UserManager doesn't open a cell editor from underneath the modal.

Reviewers: paulfitz

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2626
2020-10-03 22:56:00 -04:00

78 lines
2.5 KiB
JavaScript

/**
* This file adds some includes tweaks to the behavior of Mousetrap.js, the keyboard bindings
* library. It exports the mousetrap library itself, so you may use it in mousetrap's place.
*/
/* global document */
if (typeof window === 'undefined') {
// We can't require('mousetrap') in a browserless environment (specifically for unittests)
// because it uses global variables right on require, which are not available with jsdom.
// So to use mousetrap in unittests, we need to stub it out.
module.exports = {
bind: function() {},
unbind: function() {},
};
} else {
var Mousetrap = require('mousetrap');
// Minus is different on Gecko:
// see https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
// and https://github.com/ccampbell/mousetrap/pull/215
Mousetrap.addKeycodes({173: '-'});
var customStopCallbacks = new WeakMap();
var MousetrapProtype = Mousetrap.prototype;
var origStopCallback = MousetrapProtype.stopCallback;
/**
* Enhances Mousetrap's stopCallback filter. Normally, mousetrap ignores key events in input
* fields and textareas. This replacement allows individual CommandGroups to be activated in such
* elements. See also 'attach' method of commands.CommandGroup.
*/
MousetrapProtype.stopCallback = function(e, element, combo, sequence) {
if (mousetrapBindingsPaused) {
return true;
}
// If we have a custom stopCallback, use it now.
const custom = customStopCallbacks.get(element);
if (custom) {
return custom(combo);
}
try {
return origStopCallback.call(this, e, element, combo, sequence);
} catch (err) {
if (!document.body.contains(element)) {
// Mousetrap throws a pointless error in this case, which we ignore. It happens when
// element gets removed by a non-mousetrap keyboard handler.
return;
}
throw err;
}
};
var mousetrapBindingsPaused = false;
/**
* Globally pause or unpause mousetrap bindings. This is useful e.g. while a context menu is being
* shown, which has its own keyboard handling.
*/
Mousetrap.setPaused = function(yesNo) {
mousetrapBindingsPaused = yesNo;
};
/**
* Set a custom stopCallback for an element. When a key combo is pressed for this element,
* callback(combo) is called. If it returns true, Mousetrap should NOT process the combo.
*/
Mousetrap.setCustomStopCallback = function(element, callback) {
customStopCallbacks.set(element, callback);
};
module.exports = Mousetrap;
}