// Pressing `Escape` should close the last opened “element” and only the last // one. Components can register themselves using a label a condition, and an // action. This is used by Popup or inlinedForm for instance. When we press // escape we execute the action which have a valid condition and his the highest // in the label hierarchy. EscapeActions = { _nextclickPrevented: false, _actions: [], // Executed in order hierarchy: [ 'textcomplete', 'popup-back', 'popup-close', 'modalWindow', 'inlinedForm', 'detailsPane', 'multiselection', 'sidebarView', ], register(label, action, condition = () => true, options = {}) { const priority = this.hierarchy.indexOf(label); if (priority === -1) { throw Error('You must define the label in the EscapeActions hierarchy'); } let enabledOnClick = options.enabledOnClick; if (_.isUndefined(enabledOnClick)) { enabledOnClick = true; } const noClickEscapeOn = options.noClickEscapeOn; this._actions = _.sortBy( [ ...this._actions, { priority, condition, action, noClickEscapeOn, enabledOnClick, }, ], action => action.priority, ); }, executeLowest() { return this._execute({ multipleActions: false, }); }, executeAll() { return this._execute({ multipleActions: true, }); }, executeUpTo(maxLabel) { return this._execute({ maxLabel, multipleActions: true, }); }, clickExecute(target, maxLabel) { if (this._nextclickPrevented) { this._nextclickPrevented = false; return false; } else { return this._execute({ maxLabel, multipleActions: false, isClick: true, clickTarget: target, }); } }, preventNextClick() { this._nextclickPrevented = true; }, _stopClick(action, clickTarget) { if (!_.isString(action.noClickEscapeOn)) return false; else return $(clickTarget).closest(action.noClickEscapeOn).length > 0; }, _execute(options) { const maxLabel = options.maxLabel; const multipleActions = options.multipleActions; const isClick = Boolean(options.isClick); const clickTarget = options.clickTarget; let executedAtLeastOne = false; let maxPriority; if (!maxLabel) maxPriority = Infinity; else maxPriority = this.hierarchy.indexOf(maxLabel); for (const currentAction of this._actions) { if (currentAction.priority > maxPriority) return executedAtLeastOne; if (isClick && this._stopClick(currentAction, clickTarget)) return executedAtLeastOne; const isEnabled = currentAction.enabledOnClick || !isClick; if (isEnabled && currentAction.condition()) { currentAction.action(); executedAtLeastOne = true; if (!multipleActions) return executedAtLeastOne; } } return executedAtLeastOne; }, }; // Pressing escape to execute one escape action. We use `bindGloabal` vecause // the shortcut sould work on textarea and inputs as well. Mousetrap.bindGlobal('esc', () => { EscapeActions.executeLowest(); }); // On a left click on the document, we try to exectute one escape action (eg, // close the popup). We don't execute any action if the user has clicked on a // link or a button. $(document).on('click', evt => { if ( evt.button === 0 && $(evt.target).closest('a,button,.is-editable').length === 0 ) { EscapeActions.clickExecute(evt.target, 'multiselection'); } }); $(document).on('click', 'a[href=\\#]', evt => { evt.preventDefault(); });