escapeActions.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // Pressing `Escape` should close the last opened “element” and only the last
  2. // one. Components can register themselves using a label a condition, and an
  3. // action. This is used by Popup or inlinedForm for instance. When we press
  4. // escape we execute the action which have a valid condition and his the highest
  5. // in the label hierarchy.
  6. EscapeActions = {
  7. _actions: [],
  8. // Executed in order
  9. hierarchy: [
  10. 'textcomplete',
  11. 'popup-back',
  12. 'popup-close',
  13. 'inlinedForm',
  14. 'detailsPane',
  15. 'multiselection',
  16. 'sidebarView'
  17. ],
  18. register: function(label, action, condition = () => true, options = {}) {
  19. var priority = this.hierarchy.indexOf(label);
  20. if (priority === -1) {
  21. throw Error('You must define the label in the EscapeActions hierarchy');
  22. }
  23. this._actions.push({
  24. priority,
  25. condition,
  26. action,
  27. noClickEscapeOn: options.noClickEscapeOn,
  28. enabledOnClick: !! options.enabledOnClick
  29. });
  30. this._actions = _.sortBy(this._actions, (a) => { return a.priority; });
  31. },
  32. executeLowest: function() {
  33. return this._execute({
  34. multipleAction: false
  35. });
  36. },
  37. executeAll: function() {
  38. return this._execute({
  39. multipleActions: true
  40. });
  41. },
  42. executeUpTo: function(maxLabel) {
  43. return this._execute({
  44. maxLabel: maxLabel,
  45. multipleActions: true
  46. });
  47. },
  48. clickExecute: function(target, maxLabel) {
  49. return this._execute({
  50. maxLabel: maxLabel,
  51. multipleActions: false,
  52. isClick: true,
  53. clickTarget: target
  54. });
  55. },
  56. _stopClick: function(action, clickTarget) {
  57. if (! _.isString(action.noClickEscapeOn))
  58. return false;
  59. else
  60. return $(clickTarget).closest(action.noClickEscapeOn).length > 0;
  61. },
  62. _execute: function(options) {
  63. var maxLabel = options.maxLabel;
  64. var multipleActions = options.multipleActions;
  65. var isClick = !! options.isClick;
  66. var clickTarget = options.clickTarget;
  67. var maxPriority, currentAction;
  68. var executedAtLeastOne = false;
  69. if (! maxLabel)
  70. maxPriority = Infinity;
  71. else
  72. maxPriority = this.hierarchy.indexOf(maxLabel);
  73. for (var i = 0; i < this._actions.length; i++) {
  74. currentAction = this._actions[i];
  75. if (currentAction.priority > maxPriority)
  76. return executedAtLeastOne;
  77. if (isClick && this._stopClick(currentAction, clickTarget))
  78. return executedAtLeastOne;
  79. var isEnabled = currentAction.enabledOnClick || ! isClick;
  80. if (isEnabled && currentAction.condition()) {
  81. currentAction.action();
  82. executedAtLeastOne = true;
  83. if (! multipleActions)
  84. return executedAtLeastOne;
  85. }
  86. }
  87. return executedAtLeastOne;
  88. }
  89. };
  90. // MouseTrap plugin bindGlobal plugin. Adds a bindGlobal method to Mousetrap
  91. // that allows you to bind specific keyboard shortcuts that will still work
  92. // inside a text input field.
  93. //
  94. // usage:
  95. // Mousetrap.bindGlobal('ctrl+s', _saveChanges);
  96. //
  97. // source:
  98. // https://github.com/ccampbell/mousetrap/tree/master/plugins/global-bind
  99. var _globalCallbacks = {};
  100. var _originalStopCallback = Mousetrap.stopCallback;
  101. Mousetrap.stopCallback = function(e, element, combo, sequence) {
  102. var self = this;
  103. if (self.paused) {
  104. return true;
  105. }
  106. if (_globalCallbacks[combo] || _globalCallbacks[sequence]) {
  107. return false;
  108. }
  109. return _originalStopCallback.call(self, e, element, combo);
  110. };
  111. Mousetrap.bindGlobal = function(keys, callback, action) {
  112. var self = this;
  113. self.bind(keys, callback, action);
  114. if (keys instanceof Array) {
  115. for (var i = 0; i < keys.length; i++) {
  116. _globalCallbacks[keys[i]] = true;
  117. }
  118. return;
  119. }
  120. _globalCallbacks[keys] = true;
  121. };
  122. // Pressing escape to execute one escape action. We use `bindGloabal` vecause
  123. // the shortcut sould work on textarea and inputs as well.
  124. Mousetrap.bindGlobal('esc', function() {
  125. EscapeActions.executeLowest();
  126. });
  127. // On a left click on the document, we try to exectute one escape action (eg,
  128. // close the popup). We don't execute any action if the user has clicked on a
  129. // link or a button.
  130. $(document).on('click', function(evt) {
  131. if (evt.which === 1 &&
  132. $(evt.target).closest('a,button,.is-editable').length === 0) {
  133. EscapeActions.clickExecute(evt.target, 'multiselection');
  134. }
  135. });