Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/webroot/rsrc/js/core/ToolTip.js
12241 views
1
/**
2
* @requires javelin-install
3
* javelin-util
4
* javelin-dom
5
* javelin-vector
6
* @provides phabricator-tooltip
7
* @javelin
8
*/
9
10
JX.install('Tooltip', {
11
12
statics: {
13
_node: null,
14
_last: null,
15
_lock: 0,
16
17
show : function(root, scale, align, content) {
18
var self = JX.Tooltip;
19
20
if (self._lock) {
21
return;
22
}
23
24
if (content === null) {
25
return;
26
}
27
28
if (__DEV__) {
29
switch (align) {
30
case 'N':
31
case 'E':
32
case 'S':
33
case 'W':
34
break;
35
default:
36
JX.$E(
37
'Only alignments "N" (north), "E" (east), "S" (south), ' +
38
'and "W" (west) are supported.'
39
);
40
break;
41
}
42
}
43
44
var node_inner = JX.$N(
45
'div',
46
{ className: 'jx-tooltip-inner' },
47
[
48
JX.$N('div', { className: 'jx-tooltip' }, content),
49
JX.$N('div', { className: 'jx-tooltip-anchor' })
50
]);
51
52
var node = JX.$N(
53
'div',
54
{ className: 'jx-tooltip-container' },
55
node_inner);
56
57
if (scale == 'auto') {
58
node.style.maxWidth = '';
59
} else {
60
node.style.maxWidth = scale + 'px';
61
}
62
63
JX.Tooltip.hide();
64
self._node = node;
65
66
// Append the tip to the document, but offscreen, so we can measure it.
67
node.style.left = '-10000px';
68
document.body.appendChild(node);
69
70
// Jump through some hoops trying to auto-position the tooltip
71
var pos = self._getSmartPosition(align, root, node);
72
pos.setPos(node);
73
74
// Animate the tip if we haven't shown any tips recently. If we are
75
// already showing a tip (or hid one very recently) just show the tip
76
// immediately. This makes hunting for a particular item by mousing
77
// through tips smoother: you only have to sit through the animation
78
// once, at the beginning.
79
80
var is_recent = false;
81
82
var last_tip = self._last;
83
if (last_tip) {
84
// If we recently hid a tip, compute how many milliseconds ago we
85
// hid it.
86
var last_tip_age = (new Date().getTime() - self._last);
87
if (last_tip_age < 500) {
88
is_recent = true;
89
}
90
}
91
92
if (!is_recent) {
93
JX.DOM.alterClass(node, 'jx-tooltip-appear', true);
94
}
95
},
96
97
_getSmartPosition: function (align, root, node) {
98
var self = JX.Tooltip;
99
100
// Figure out how to position the tooltip on screen. We will try the
101
// configured aligment first.
102
var try_alignments = [align];
103
104
// If the configured alignment does not fit, we'll try the opposite
105
// alignment.
106
var opposites = {
107
N: 'S',
108
S: 'N',
109
E: 'W',
110
W: 'E'
111
};
112
try_alignments.push(opposites[align]);
113
114
// Then we'll try the other alignments, in arbitrary order.
115
for (var k in opposites) {
116
try_alignments.push(k);
117
}
118
119
var use_alignment = null;
120
var use_pos = null;
121
for (var ii = 0; ii < try_alignments.length; ii++) {
122
var try_alignment = try_alignments[ii];
123
124
var pos = self._proposePosition(try_alignment, root, node);
125
if (self.isOnScreen(pos, node)) {
126
use_alignment = try_alignment;
127
use_pos = pos;
128
break;
129
}
130
}
131
132
// If we don't come up with a good answer, default to the configured
133
// alignment.
134
if (use_alignment === null) {
135
use_alignment = align;
136
use_pos = self._proposePosition(use_alignment, root, node);
137
}
138
139
self._setAnchor(use_alignment);
140
141
return pos;
142
},
143
144
_proposePosition: function (align, root, node) {
145
var p = JX.$V(root);
146
var d = JX.Vector.getDim(root);
147
var n = JX.Vector.getDim(node);
148
var l = 0;
149
var t = 0;
150
151
// Caculate the tip so it's nicely aligned.
152
switch (align) {
153
case 'N':
154
l = parseInt(p.x - ((n.x - d.x) / 2), 10);
155
t = parseInt(p.y - n.y, 10);
156
break;
157
case 'E':
158
l = parseInt(p.x + d.x, 10);
159
t = parseInt(p.y - ((n.y - d.y) / 2), 10);
160
break;
161
case 'S':
162
l = parseInt(p.x - ((n.x - d.x) / 2), 10);
163
t = parseInt(p.y + d.y + 5, 10);
164
break;
165
case 'W':
166
l = parseInt(p.x - n.x - 5, 10);
167
t = parseInt(p.y - ((n.y - d.y) / 2), 10);
168
break;
169
}
170
171
return new JX.Vector(l, t);
172
},
173
174
isOnScreen: function (a, node) {
175
var view = this._getViewBoundaries();
176
var corners = this._getNodeCornerPositions(a, node);
177
178
// Check if any of the corners are offscreen.
179
for (var i = 0; i < corners.length; i++) {
180
var corner = corners[i];
181
if (corner.x < view.w ||
182
corner.y < view.n ||
183
corner.x > view.e ||
184
corner.y > view.s) {
185
return false;
186
}
187
}
188
return true;
189
},
190
191
_getNodeCornerPositions: function(pos, node) {
192
// Get positions of all four corners of a node.
193
var n = JX.Vector.getDim(node);
194
return [new JX.Vector(pos.x, pos.y),
195
new JX.Vector(pos.x + n.x, pos.y),
196
new JX.Vector(pos.x, pos.y + n.y),
197
new JX.Vector(pos.x + n.x, pos.y + n.y)];
198
},
199
200
_getViewBoundaries: function() {
201
var s = JX.Vector.getScroll();
202
var v = JX.Vector.getViewport();
203
var max_x = s.x + v.x;
204
var max_y = s.y + v.y;
205
206
// Even if the corner is technically on the screen, don't allow the
207
// tip to display too close to the edge of the screen.
208
var margin = 16;
209
210
return {
211
w: s.x + margin,
212
e: max_x - margin,
213
n: s.y + margin,
214
s: max_y - margin
215
};
216
},
217
218
_setAnchor: function (align) {
219
// Orient the little tail
220
JX.DOM.alterClass(this._node, 'jx-tooltip-align-' + align, true);
221
},
222
223
hide : function() {
224
if (this._node) {
225
JX.DOM.remove(this._node);
226
this._node = null;
227
this._last = new Date().getTime();
228
}
229
},
230
231
lock: function() {
232
var self = JX.Tooltip;
233
self.hide();
234
self._lock++;
235
},
236
237
unlock: function() {
238
var self = JX.Tooltip;
239
self._lock--;
240
}
241
}
242
});
243
244