1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
// 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({
multipleAction: false,
});
},
executeAll() {
return this._execute({
multipleActions: true,
});
},
executeUpTo(maxLabel) {
return this._execute({
maxLabel,
multipleActions: true,
});
},
clickExecute(target, maxLabel) {
if (this._nextclickPrevented) {
this._nextclickPrevented = 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');
}
});
|