OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2009 Google Inc. All rights reserved. | 2 * Copyright (C) 2009 Google Inc. All rights reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions are | 5 * modification, are permitted provided that the following conditions are |
6 * met: | 6 * met: |
7 * | 7 * |
8 * * Redistributions of source code must retain the above copyright | 8 * * Redistributions of source code must retain the above copyright |
9 * notice, this list of conditions and the following disclaimer. | 9 * notice, this list of conditions and the following disclaimer. |
10 * * Redistributions in binary form must reproduce the above | 10 * * Redistributions in binary form must reproduce the above |
11 * copyright notice, this list of conditions and the following disclaimer | 11 * copyright notice, this list of conditions and the following disclaimer |
12 * in the documentation and/or other materials provided with the | 12 * in the documentation and/or other materials provided with the |
13 * distribution. | 13 * distribution. |
14 * * Neither the name of Google Inc. nor the names of its | 14 * * Neither the name of Google Inc. nor the names of its |
15 * contributors may be used to endorse or promote products derived from | 15 * contributors may be used to endorse or promote products derived from |
16 * this software without specific prior written permission. | 16 * this software without specific prior written permission. |
17 * | 17 * |
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
29 */ | 29 */ |
30 | |
31 /** | 30 /** |
32 * @constructor | 31 * @unrestricted |
33 * @extends {WebInspector.Widget} | |
34 * @param {!WebInspector.PopoverHelper=} popoverHelper | |
35 */ | 32 */ |
36 WebInspector.Popover = function(popoverHelper) | 33 WebInspector.Popover = class extends WebInspector.Widget { |
37 { | 34 /** |
38 WebInspector.Widget.call(this); | 35 * @param {!WebInspector.PopoverHelper=} popoverHelper |
| 36 */ |
| 37 constructor(popoverHelper) { |
| 38 super(); |
39 this.markAsRoot(); | 39 this.markAsRoot(); |
40 this.element.className = WebInspector.Popover._classNamePrefix; // Override | 40 this.element.className = WebInspector.Popover._classNamePrefix; // Override |
41 this._containerElement = createElementWithClass("div", "fill popover-contain
er"); | 41 this._containerElement = createElementWithClass('div', 'fill popover-contain
er'); |
42 | 42 |
43 this._popupArrowElement = this.element.createChild("div", "arrow"); | 43 this._popupArrowElement = this.element.createChild('div', 'arrow'); |
44 this._contentDiv = this.element.createChild("div", "content"); | 44 this._contentDiv = this.element.createChild('div', 'content'); |
45 | 45 |
46 this._popoverHelper = popoverHelper; | 46 this._popoverHelper = popoverHelper; |
47 this._hideBound = this.hide.bind(this); | 47 this._hideBound = this.hide.bind(this); |
| 48 } |
| 49 |
| 50 /** |
| 51 * @param {!Element} element |
| 52 * @param {!Element|!AnchorBox} anchor |
| 53 * @param {?number=} preferredWidth |
| 54 * @param {?number=} preferredHeight |
| 55 * @param {?WebInspector.Popover.Orientation=} arrowDirection |
| 56 */ |
| 57 showForAnchor(element, anchor, preferredWidth, preferredHeight, arrowDirection
) { |
| 58 this._innerShow(null, element, anchor, preferredWidth, preferredHeight, arro
wDirection); |
| 59 } |
| 60 |
| 61 /** |
| 62 * @param {!WebInspector.Widget} view |
| 63 * @param {!Element|!AnchorBox} anchor |
| 64 * @param {?number=} preferredWidth |
| 65 * @param {?number=} preferredHeight |
| 66 */ |
| 67 showView(view, anchor, preferredWidth, preferredHeight) { |
| 68 this._innerShow(view, view.element, anchor, preferredWidth, preferredHeight)
; |
| 69 } |
| 70 |
| 71 /** |
| 72 * @param {?WebInspector.Widget} view |
| 73 * @param {!Element} contentElement |
| 74 * @param {!Element|!AnchorBox} anchor |
| 75 * @param {?number=} preferredWidth |
| 76 * @param {?number=} preferredHeight |
| 77 * @param {?WebInspector.Popover.Orientation=} arrowDirection |
| 78 */ |
| 79 _innerShow(view, contentElement, anchor, preferredWidth, preferredHeight, arro
wDirection) { |
| 80 if (this._disposed) |
| 81 return; |
| 82 this._contentElement = contentElement; |
| 83 |
| 84 // This should not happen, but we hide previous popup to be on the safe side
. |
| 85 if (WebInspector.Popover._popover) |
| 86 WebInspector.Popover._popover.hide(); |
| 87 WebInspector.Popover._popover = this; |
| 88 |
| 89 var document = anchor instanceof Element ? anchor.ownerDocument : contentEle
ment.ownerDocument; |
| 90 var window = document.defaultView; |
| 91 |
| 92 // Temporarily attach in order to measure preferred dimensions. |
| 93 var preferredSize = view ? view.measurePreferredSize() : WebInspector.measur
ePreferredSize(this._contentElement); |
| 94 this._preferredWidth = preferredWidth || preferredSize.width; |
| 95 this._preferredHeight = preferredHeight || preferredSize.height; |
| 96 |
| 97 window.addEventListener('resize', this._hideBound, false); |
| 98 document.body.appendChild(this._containerElement); |
| 99 super.show(this._containerElement); |
| 100 |
| 101 if (view) |
| 102 view.show(this._contentDiv); |
| 103 else |
| 104 this._contentDiv.appendChild(this._contentElement); |
| 105 |
| 106 this.positionElement(anchor, this._preferredWidth, this._preferredHeight, ar
rowDirection); |
| 107 |
| 108 if (this._popoverHelper) { |
| 109 this._contentDiv.addEventListener( |
| 110 'mousemove', this._popoverHelper._killHidePopoverTimer.bind(this._popo
verHelper), true); |
| 111 this.element.addEventListener('mouseout', this._popoverHelper._popoverMous
eOut.bind(this._popoverHelper), true); |
| 112 } |
| 113 } |
| 114 |
| 115 hide() { |
| 116 this._containerElement.ownerDocument.defaultView.removeEventListener('resize
', this._hideBound, false); |
| 117 this.detach(); |
| 118 this._containerElement.remove(); |
| 119 delete WebInspector.Popover._popover; |
| 120 } |
| 121 |
| 122 get disposed() { |
| 123 return this._disposed; |
| 124 } |
| 125 |
| 126 dispose() { |
| 127 if (this.isShowing()) |
| 128 this.hide(); |
| 129 this._disposed = true; |
| 130 } |
| 131 |
| 132 /** |
| 133 * @param {boolean} canShrink |
| 134 */ |
| 135 setCanShrink(canShrink) { |
| 136 this._hasFixedHeight = !canShrink; |
| 137 this._contentDiv.classList.toggle('fixed-height', this._hasFixedHeight); |
| 138 } |
| 139 |
| 140 /** |
| 141 * @param {boolean} noPadding |
| 142 */ |
| 143 setNoPadding(noPadding) { |
| 144 this._hasNoPadding = noPadding; |
| 145 this._contentDiv.classList.toggle('no-padding', this._hasNoPadding); |
| 146 } |
| 147 |
| 148 /** |
| 149 * @param {!Element|!AnchorBox} anchorElement |
| 150 * @param {number=} preferredWidth |
| 151 * @param {number=} preferredHeight |
| 152 * @param {?WebInspector.Popover.Orientation=} arrowDirection |
| 153 */ |
| 154 positionElement(anchorElement, preferredWidth, preferredHeight, arrowDirection
) { |
| 155 const borderWidth = this._hasNoPadding ? 0 : 8; |
| 156 const scrollerWidth = this._hasFixedHeight ? 0 : 14; |
| 157 const arrowHeight = this._hasNoPadding ? 8 : 15; |
| 158 const arrowOffset = 10; |
| 159 const borderRadius = 4; |
| 160 const arrowRadius = 6; |
| 161 preferredWidth = preferredWidth || this._preferredWidth; |
| 162 preferredHeight = preferredHeight || this._preferredHeight; |
| 163 |
| 164 // Skinny tooltips are not pretty, their arrow location is not nice. |
| 165 preferredWidth = Math.max(preferredWidth, 50); |
| 166 // Position relative to main DevTools element. |
| 167 const container = WebInspector.Dialog.modalHostView().element; |
| 168 const totalWidth = container.offsetWidth; |
| 169 const totalHeight = container.offsetHeight; |
| 170 |
| 171 var anchorBox = anchorElement instanceof AnchorBox ? anchorElement : anchorE
lement.boxInWindow(window); |
| 172 anchorBox = anchorBox.relativeToElement(container); |
| 173 var newElementPosition = {x: 0, y: 0, width: preferredWidth + scrollerWidth,
height: preferredHeight}; |
| 174 |
| 175 var verticalAlignment; |
| 176 var roomAbove = anchorBox.y; |
| 177 var roomBelow = totalHeight - anchorBox.y - anchorBox.height; |
| 178 this._popupArrowElement.hidden = false; |
| 179 |
| 180 if ((roomAbove > roomBelow) || (arrowDirection === WebInspector.Popover.Orie
ntation.Bottom)) { |
| 181 // Positioning above the anchor. |
| 182 if ((anchorBox.y > newElementPosition.height + arrowHeight + borderRadius)
|| |
| 183 (arrowDirection === WebInspector.Popover.Orientation.Bottom)) |
| 184 newElementPosition.y = anchorBox.y - newElementPosition.height - arrowHe
ight; |
| 185 else { |
| 186 this._popupArrowElement.hidden = true; |
| 187 newElementPosition.y = borderRadius; |
| 188 newElementPosition.height = anchorBox.y - borderRadius * 2 - arrowHeight
; |
| 189 if (this._hasFixedHeight && newElementPosition.height < preferredHeight)
{ |
| 190 newElementPosition.y = borderRadius; |
| 191 newElementPosition.height = preferredHeight; |
| 192 } |
| 193 } |
| 194 verticalAlignment = WebInspector.Popover.Orientation.Bottom; |
| 195 } else { |
| 196 // Positioning below the anchor. |
| 197 newElementPosition.y = anchorBox.y + anchorBox.height + arrowHeight; |
| 198 if ((newElementPosition.y + newElementPosition.height + borderRadius >= to
talHeight) && |
| 199 (arrowDirection !== WebInspector.Popover.Orientation.Top)) { |
| 200 this._popupArrowElement.hidden = true; |
| 201 newElementPosition.height = totalHeight - borderRadius - newElementPosit
ion.y; |
| 202 if (this._hasFixedHeight && newElementPosition.height < preferredHeight)
{ |
| 203 newElementPosition.y = totalHeight - preferredHeight - borderRadius; |
| 204 newElementPosition.height = preferredHeight; |
| 205 } |
| 206 } |
| 207 // Align arrow. |
| 208 verticalAlignment = WebInspector.Popover.Orientation.Top; |
| 209 } |
| 210 |
| 211 var horizontalAlignment; |
| 212 this._popupArrowElement.removeAttribute('style'); |
| 213 if (anchorBox.x + newElementPosition.width < totalWidth) { |
| 214 newElementPosition.x = Math.max(borderRadius, anchorBox.x - borderRadius -
arrowOffset); |
| 215 horizontalAlignment = 'left'; |
| 216 this._popupArrowElement.style.left = arrowOffset + 'px'; |
| 217 } else if (newElementPosition.width + borderRadius * 2 < totalWidth) { |
| 218 newElementPosition.x = totalWidth - newElementPosition.width - borderRadiu
s - 2 * borderWidth; |
| 219 horizontalAlignment = 'right'; |
| 220 // Position arrow accurately. |
| 221 var arrowRightPosition = Math.max(0, totalWidth - anchorBox.x - anchorBox.
width - borderRadius - arrowOffset); |
| 222 arrowRightPosition += anchorBox.width / 2; |
| 223 arrowRightPosition = Math.min(arrowRightPosition, newElementPosition.width
- borderRadius - arrowOffset); |
| 224 this._popupArrowElement.style.right = arrowRightPosition + 'px'; |
| 225 } else { |
| 226 newElementPosition.x = borderRadius; |
| 227 newElementPosition.width = totalWidth - borderRadius * 2; |
| 228 newElementPosition.height += scrollerWidth; |
| 229 horizontalAlignment = 'left'; |
| 230 if (verticalAlignment === WebInspector.Popover.Orientation.Bottom) |
| 231 newElementPosition.y -= scrollerWidth; |
| 232 // Position arrow accurately. |
| 233 this._popupArrowElement.style.left = |
| 234 Math.max(0, anchorBox.x - newElementPosition.x - borderRadius - arrowR
adius + anchorBox.width / 2) + 'px'; |
| 235 } |
| 236 |
| 237 this.element.className = |
| 238 WebInspector.Popover._classNamePrefix + ' ' + verticalAlignment + '-' +
horizontalAlignment + '-arrow'; |
| 239 this.element.positionAt(newElementPosition.x, newElementPosition.y - borderW
idth, container); |
| 240 this.element.style.width = newElementPosition.width + borderWidth * 2 + 'px'
; |
| 241 this.element.style.height = newElementPosition.height + borderWidth * 2 + 'p
x'; |
| 242 } |
48 }; | 243 }; |
49 | 244 |
50 WebInspector.Popover._classNamePrefix = "popover"; | 245 WebInspector.Popover._classNamePrefix = 'popover'; |
51 | 246 |
52 WebInspector.Popover.prototype = { | 247 /** |
| 248 * @unrestricted |
| 249 */ |
| 250 WebInspector.PopoverHelper = class { |
| 251 /** |
| 252 * @param {!Element} panelElement |
| 253 * @param {boolean=} disableOnClick |
| 254 */ |
| 255 constructor(panelElement, disableOnClick) { |
| 256 this._disableOnClick = !!disableOnClick; |
| 257 panelElement.addEventListener('mousedown', this._mouseDown.bind(this), false
); |
| 258 panelElement.addEventListener('mousemove', this._mouseMove.bind(this), false
); |
| 259 panelElement.addEventListener('mouseout', this._mouseOut.bind(this), false); |
| 260 this.setTimeout(1000, 500); |
| 261 } |
| 262 |
| 263 /** |
| 264 * @param {function(!Element, !Event):(!Element|!AnchorBox|undefined)} getAnch
or |
| 265 * @param {function(!Element, !WebInspector.Popover):undefined} showPopover |
| 266 * @param {function()=} onHide |
| 267 */ |
| 268 initializeCallbacks(getAnchor, showPopover, onHide) { |
| 269 this._getAnchor = getAnchor; |
| 270 this._showPopover = showPopover; |
| 271 this._onHide = onHide; |
| 272 } |
| 273 |
| 274 /** |
| 275 * @param {number} timeout |
| 276 * @param {number=} hideTimeout |
| 277 */ |
| 278 setTimeout(timeout, hideTimeout) { |
| 279 this._timeout = timeout; |
| 280 if (typeof hideTimeout === 'number') |
| 281 this._hideTimeout = hideTimeout; |
| 282 else |
| 283 this._hideTimeout = timeout / 2; |
| 284 } |
| 285 |
| 286 /** |
| 287 * @param {!MouseEvent} event |
| 288 * @return {boolean} |
| 289 */ |
| 290 _eventInHoverElement(event) { |
| 291 if (!this._hoverElement) |
| 292 return false; |
| 293 var box = this._hoverElement instanceof AnchorBox ? this._hoverElement : thi
s._hoverElement.boxInWindow(); |
| 294 return ( |
| 295 box.x <= event.clientX && event.clientX <= box.x + box.width && box.y <=
event.clientY && |
| 296 event.clientY <= box.y + box.height); |
| 297 } |
| 298 |
| 299 _mouseDown(event) { |
| 300 if (this._disableOnClick || !this._eventInHoverElement(event)) |
| 301 this.hidePopover(); |
| 302 else { |
| 303 this._killHidePopoverTimer(); |
| 304 this._handleMouseAction(event, true); |
| 305 } |
| 306 } |
| 307 |
| 308 _mouseMove(event) { |
| 309 // Pretend that nothing has happened. |
| 310 if (this._eventInHoverElement(event)) |
| 311 return; |
| 312 |
| 313 this._startHidePopoverTimer(); |
| 314 this._handleMouseAction(event, false); |
| 315 } |
| 316 |
| 317 _popoverMouseOut(event) { |
| 318 if (!this.isPopoverVisible()) |
| 319 return; |
| 320 if (event.relatedTarget && !event.relatedTarget.isSelfOrDescendant(this._pop
over._contentDiv)) |
| 321 this._startHidePopoverTimer(); |
| 322 } |
| 323 |
| 324 _mouseOut(event) { |
| 325 if (!this.isPopoverVisible()) |
| 326 return; |
| 327 if (!this._eventInHoverElement(event)) |
| 328 this._startHidePopoverTimer(); |
| 329 } |
| 330 |
| 331 _startHidePopoverTimer() { |
| 332 // User has 500ms (this._hideTimeout) to reach the popup. |
| 333 if (!this._popover || this._hidePopoverTimer) |
| 334 return; |
| 335 |
53 /** | 336 /** |
54 * @param {!Element} element | 337 * @this {WebInspector.PopoverHelper} |
55 * @param {!Element|!AnchorBox} anchor | |
56 * @param {?number=} preferredWidth | |
57 * @param {?number=} preferredHeight | |
58 * @param {?WebInspector.Popover.Orientation=} arrowDirection | |
59 */ | 338 */ |
60 showForAnchor: function(element, anchor, preferredWidth, preferredHeight, ar
rowDirection) | 339 function doHide() { |
61 { | 340 this._hidePopover(); |
62 this._innerShow(null, element, anchor, preferredWidth, preferredHeight,
arrowDirection); | 341 delete this._hidePopoverTimer; |
63 }, | 342 } |
64 | 343 this._hidePopoverTimer = setTimeout(doHide.bind(this), this._hideTimeout); |
65 /** | 344 } |
66 * @param {!WebInspector.Widget} view | 345 |
67 * @param {!Element|!AnchorBox} anchor | 346 _handleMouseAction(event, isMouseDown) { |
68 * @param {?number=} preferredWidth | 347 this._resetHoverTimer(); |
69 * @param {?number=} preferredHeight | 348 if (event.which && this._disableOnClick) |
70 */ | 349 return; |
71 showView: function(view, anchor, preferredWidth, preferredHeight) | 350 this._hoverElement = this._getAnchor(event.target, event); |
72 { | 351 if (!this._hoverElement) |
73 this._innerShow(view, view.element, anchor, preferredWidth, preferredHei
ght); | 352 return; |
74 }, | 353 const toolTipDelay = isMouseDown ? 0 : (this._popup ? this._timeout * 0.6 :
this._timeout); |
75 | 354 this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverElement
), toolTipDelay); |
76 /** | 355 } |
77 * @param {?WebInspector.Widget} view | 356 |
78 * @param {!Element} contentElement | 357 _resetHoverTimer() { |
79 * @param {!Element|!AnchorBox} anchor | 358 if (this._hoverTimer) { |
80 * @param {?number=} preferredWidth | 359 clearTimeout(this._hoverTimer); |
81 * @param {?number=} preferredHeight | 360 delete this._hoverTimer; |
82 * @param {?WebInspector.Popover.Orientation=} arrowDirection | 361 } |
83 */ | 362 } |
84 _innerShow: function(view, contentElement, anchor, preferredWidth, preferred
Height, arrowDirection) | 363 |
85 { | 364 /** |
86 if (this._disposed) | 365 * @return {boolean} |
87 return; | 366 */ |
88 this._contentElement = contentElement; | 367 isPopoverVisible() { |
89 | 368 return !!this._popover; |
90 // This should not happen, but we hide previous popup to be on the safe
side. | 369 } |
91 if (WebInspector.Popover._popover) | 370 |
92 WebInspector.Popover._popover.hide(); | 371 hidePopover() { |
93 WebInspector.Popover._popover = this; | 372 this._resetHoverTimer(); |
94 | 373 this._hidePopover(); |
95 var document = anchor instanceof Element ? anchor.ownerDocument : conten
tElement.ownerDocument; | 374 } |
96 var window = document.defaultView; | 375 |
97 | 376 _hidePopover() { |
98 // Temporarily attach in order to measure preferred dimensions. | 377 if (!this._popover) |
99 var preferredSize = view ? view.measurePreferredSize() : WebInspector.me
asurePreferredSize(this._contentElement); | 378 return; |
100 this._preferredWidth = preferredWidth || preferredSize.width; | 379 |
101 this._preferredHeight = preferredHeight || preferredSize.height; | 380 if (this._onHide) |
102 | 381 this._onHide(); |
103 window.addEventListener("resize", this._hideBound, false); | 382 |
104 document.body.appendChild(this._containerElement); | 383 this._popover.dispose(); |
105 WebInspector.Widget.prototype.show.call(this, this._containerElement); | 384 delete this._popover; |
106 | 385 this._hoverElement = null; |
107 if (view) | 386 } |
108 view.show(this._contentDiv); | 387 |
109 else | 388 _mouseHover(element) { |
110 this._contentDiv.appendChild(this._contentElement); | 389 delete this._hoverTimer; |
111 | 390 this._hoverElement = element; |
112 this.positionElement(anchor, this._preferredWidth, this._preferredHeight
, arrowDirection); | 391 this._hidePopover(); |
113 | 392 this._popover = new WebInspector.Popover(this); |
114 if (this._popoverHelper) { | 393 this._showPopover(element, this._popover); |
115 this._contentDiv.addEventListener("mousemove", this._popoverHelper._
killHidePopoverTimer.bind(this._popoverHelper), true); | 394 } |
116 this.element.addEventListener("mouseout", this._popoverHelper._popov
erMouseOut.bind(this._popoverHelper), true); | 395 |
117 } | 396 _killHidePopoverTimer() { |
118 }, | 397 if (this._hidePopoverTimer) { |
119 | 398 clearTimeout(this._hidePopoverTimer); |
120 hide: function() | 399 delete this._hidePopoverTimer; |
121 { | 400 |
122 this._containerElement.ownerDocument.defaultView.removeEventListener("re
size", this._hideBound, false); | 401 // We know that we reached the popup, but we might have moved over other e
lements. |
123 this.detach(); | 402 // Discard pending command. |
124 this._containerElement.remove(); | 403 this._resetHoverTimer(); |
125 delete WebInspector.Popover._popover; | 404 } |
126 }, | 405 } |
127 | |
128 get disposed() | |
129 { | |
130 return this._disposed; | |
131 }, | |
132 | |
133 dispose: function() | |
134 { | |
135 if (this.isShowing()) | |
136 this.hide(); | |
137 this._disposed = true; | |
138 }, | |
139 | |
140 /** | |
141 * @param {boolean} canShrink | |
142 */ | |
143 setCanShrink: function(canShrink) | |
144 { | |
145 this._hasFixedHeight = !canShrink; | |
146 this._contentDiv.classList.toggle("fixed-height", this._hasFixedHeight); | |
147 }, | |
148 | |
149 /** | |
150 * @param {boolean} noPadding | |
151 */ | |
152 setNoPadding: function(noPadding) | |
153 { | |
154 this._hasNoPadding = noPadding; | |
155 this._contentDiv.classList.toggle("no-padding", this._hasNoPadding); | |
156 }, | |
157 | |
158 /** | |
159 * @param {!Element|!AnchorBox} anchorElement | |
160 * @param {number=} preferredWidth | |
161 * @param {number=} preferredHeight | |
162 * @param {?WebInspector.Popover.Orientation=} arrowDirection | |
163 */ | |
164 positionElement: function(anchorElement, preferredWidth, preferredHeight, ar
rowDirection) | |
165 { | |
166 const borderWidth = this._hasNoPadding ? 0 : 8; | |
167 const scrollerWidth = this._hasFixedHeight ? 0 : 14; | |
168 const arrowHeight = this._hasNoPadding ? 8 : 15; | |
169 const arrowOffset = 10; | |
170 const borderRadius = 4; | |
171 const arrowRadius = 6; | |
172 preferredWidth = preferredWidth || this._preferredWidth; | |
173 preferredHeight = preferredHeight || this._preferredHeight; | |
174 | |
175 // Skinny tooltips are not pretty, their arrow location is not nice. | |
176 preferredWidth = Math.max(preferredWidth, 50); | |
177 // Position relative to main DevTools element. | |
178 const container = WebInspector.Dialog.modalHostView().element; | |
179 const totalWidth = container.offsetWidth; | |
180 const totalHeight = container.offsetHeight; | |
181 | |
182 var anchorBox = anchorElement instanceof AnchorBox ? anchorElement : anc
horElement.boxInWindow(window); | |
183 anchorBox = anchorBox.relativeToElement(container); | |
184 var newElementPosition = { x: 0, y: 0, width: preferredWidth + scrollerW
idth, height: preferredHeight }; | |
185 | |
186 var verticalAlignment; | |
187 var roomAbove = anchorBox.y; | |
188 var roomBelow = totalHeight - anchorBox.y - anchorBox.height; | |
189 this._popupArrowElement.hidden = false; | |
190 | |
191 if ((roomAbove > roomBelow) || (arrowDirection === WebInspector.Popover.
Orientation.Bottom)) { | |
192 // Positioning above the anchor. | |
193 if ((anchorBox.y > newElementPosition.height + arrowHeight + borderR
adius) || (arrowDirection === WebInspector.Popover.Orientation.Bottom)) | |
194 newElementPosition.y = anchorBox.y - newElementPosition.height -
arrowHeight; | |
195 else { | |
196 this._popupArrowElement.hidden = true; | |
197 newElementPosition.y = borderRadius; | |
198 newElementPosition.height = anchorBox.y - borderRadius * 2 - arr
owHeight; | |
199 if (this._hasFixedHeight && newElementPosition.height < preferre
dHeight) { | |
200 newElementPosition.y = borderRadius; | |
201 newElementPosition.height = preferredHeight; | |
202 } | |
203 } | |
204 verticalAlignment = WebInspector.Popover.Orientation.Bottom; | |
205 } else { | |
206 // Positioning below the anchor. | |
207 newElementPosition.y = anchorBox.y + anchorBox.height + arrowHeight; | |
208 if ((newElementPosition.y + newElementPosition.height + borderRadius
>= totalHeight) && (arrowDirection !== WebInspector.Popover.Orientation.Top)) { | |
209 this._popupArrowElement.hidden = true; | |
210 newElementPosition.height = totalHeight - borderRadius - newElem
entPosition.y; | |
211 if (this._hasFixedHeight && newElementPosition.height < preferre
dHeight) { | |
212 newElementPosition.y = totalHeight - preferredHeight - borde
rRadius; | |
213 newElementPosition.height = preferredHeight; | |
214 } | |
215 } | |
216 // Align arrow. | |
217 verticalAlignment = WebInspector.Popover.Orientation.Top; | |
218 } | |
219 | |
220 var horizontalAlignment; | |
221 this._popupArrowElement.removeAttribute("style"); | |
222 if (anchorBox.x + newElementPosition.width < totalWidth) { | |
223 newElementPosition.x = Math.max(borderRadius, anchorBox.x - borderRa
dius - arrowOffset); | |
224 horizontalAlignment = "left"; | |
225 this._popupArrowElement.style.left = arrowOffset + "px"; | |
226 } else if (newElementPosition.width + borderRadius * 2 < totalWidth) { | |
227 newElementPosition.x = totalWidth - newElementPosition.width - borde
rRadius - 2 * borderWidth; | |
228 horizontalAlignment = "right"; | |
229 // Position arrow accurately. | |
230 var arrowRightPosition = Math.max(0, totalWidth - anchorBox.x - anch
orBox.width - borderRadius - arrowOffset); | |
231 arrowRightPosition += anchorBox.width / 2; | |
232 arrowRightPosition = Math.min(arrowRightPosition, newElementPosition
.width - borderRadius - arrowOffset); | |
233 this._popupArrowElement.style.right = arrowRightPosition + "px"; | |
234 } else { | |
235 newElementPosition.x = borderRadius; | |
236 newElementPosition.width = totalWidth - borderRadius * 2; | |
237 newElementPosition.height += scrollerWidth; | |
238 horizontalAlignment = "left"; | |
239 if (verticalAlignment === WebInspector.Popover.Orientation.Bottom) | |
240 newElementPosition.y -= scrollerWidth; | |
241 // Position arrow accurately. | |
242 this._popupArrowElement.style.left = Math.max(0, anchorBox.x - newEl
ementPosition.x - borderRadius - arrowRadius + anchorBox.width / 2) + "px"; | |
243 } | |
244 | |
245 this.element.className = WebInspector.Popover._classNamePrefix + " " + v
erticalAlignment + "-" + horizontalAlignment + "-arrow"; | |
246 this.element.positionAt(newElementPosition.x, newElementPosition.y - bor
derWidth, container); | |
247 this.element.style.width = newElementPosition.width + borderWidth * 2 +
"px"; | |
248 this.element.style.height = newElementPosition.height + borderWidth * 2
+ "px"; | |
249 }, | |
250 | |
251 __proto__: WebInspector.Widget.prototype | |
252 }; | |
253 | |
254 /** | |
255 * @constructor | |
256 * @param {!Element} panelElement | |
257 * @param {boolean=} disableOnClick | |
258 */ | |
259 WebInspector.PopoverHelper = function(panelElement, disableOnClick) | |
260 { | |
261 this._disableOnClick = !!disableOnClick; | |
262 panelElement.addEventListener("mousedown", this._mouseDown.bind(this), false
); | |
263 panelElement.addEventListener("mousemove", this._mouseMove.bind(this), false
); | |
264 panelElement.addEventListener("mouseout", this._mouseOut.bind(this), false); | |
265 this.setTimeout(1000, 500); | |
266 }; | |
267 | |
268 WebInspector.PopoverHelper.prototype = { | |
269 /** | |
270 * @param {function(!Element, !Event):(!Element|!AnchorBox|undefined)} getAn
chor | |
271 * @param {function(!Element, !WebInspector.Popover):undefined} showPopover | |
272 * @param {function()=} onHide | |
273 */ | |
274 initializeCallbacks:function(getAnchor, showPopover, onHide) | |
275 { | |
276 this._getAnchor = getAnchor; | |
277 this._showPopover = showPopover; | |
278 this._onHide = onHide; | |
279 }, | |
280 | |
281 /** | |
282 * @param {number} timeout | |
283 * @param {number=} hideTimeout | |
284 */ | |
285 setTimeout: function(timeout, hideTimeout) | |
286 { | |
287 this._timeout = timeout; | |
288 if (typeof hideTimeout === "number") | |
289 this._hideTimeout = hideTimeout; | |
290 else | |
291 this._hideTimeout = timeout / 2; | |
292 }, | |
293 | |
294 /** | |
295 * @param {!MouseEvent} event | |
296 * @return {boolean} | |
297 */ | |
298 _eventInHoverElement: function(event) | |
299 { | |
300 if (!this._hoverElement) | |
301 return false; | |
302 var box = this._hoverElement instanceof AnchorBox ? this._hoverElement :
this._hoverElement.boxInWindow(); | |
303 return (box.x <= event.clientX && event.clientX <= box.x + box.width && | |
304 box.y <= event.clientY && event.clientY <= box.y + box.height); | |
305 }, | |
306 | |
307 _mouseDown: function(event) | |
308 { | |
309 if (this._disableOnClick || !this._eventInHoverElement(event)) | |
310 this.hidePopover(); | |
311 else { | |
312 this._killHidePopoverTimer(); | |
313 this._handleMouseAction(event, true); | |
314 } | |
315 }, | |
316 | |
317 _mouseMove: function(event) | |
318 { | |
319 // Pretend that nothing has happened. | |
320 if (this._eventInHoverElement(event)) | |
321 return; | |
322 | |
323 this._startHidePopoverTimer(); | |
324 this._handleMouseAction(event, false); | |
325 }, | |
326 | |
327 _popoverMouseOut: function(event) | |
328 { | |
329 if (!this.isPopoverVisible()) | |
330 return; | |
331 if (event.relatedTarget && !event.relatedTarget.isSelfOrDescendant(this.
_popover._contentDiv)) | |
332 this._startHidePopoverTimer(); | |
333 }, | |
334 | |
335 _mouseOut: function(event) | |
336 { | |
337 if (!this.isPopoverVisible()) | |
338 return; | |
339 if (!this._eventInHoverElement(event)) | |
340 this._startHidePopoverTimer(); | |
341 }, | |
342 | |
343 _startHidePopoverTimer: function() | |
344 { | |
345 // User has 500ms (this._hideTimeout) to reach the popup. | |
346 if (!this._popover || this._hidePopoverTimer) | |
347 return; | |
348 | |
349 /** | |
350 * @this {WebInspector.PopoverHelper} | |
351 */ | |
352 function doHide() | |
353 { | |
354 this._hidePopover(); | |
355 delete this._hidePopoverTimer; | |
356 } | |
357 this._hidePopoverTimer = setTimeout(doHide.bind(this), this._hideTimeout
); | |
358 }, | |
359 | |
360 _handleMouseAction: function(event, isMouseDown) | |
361 { | |
362 this._resetHoverTimer(); | |
363 if (event.which && this._disableOnClick) | |
364 return; | |
365 this._hoverElement = this._getAnchor(event.target, event); | |
366 if (!this._hoverElement) | |
367 return; | |
368 const toolTipDelay = isMouseDown ? 0 : (this._popup ? this._timeout * 0.
6 : this._timeout); | |
369 this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverEle
ment), toolTipDelay); | |
370 }, | |
371 | |
372 _resetHoverTimer: function() | |
373 { | |
374 if (this._hoverTimer) { | |
375 clearTimeout(this._hoverTimer); | |
376 delete this._hoverTimer; | |
377 } | |
378 }, | |
379 | |
380 /** | |
381 * @return {boolean} | |
382 */ | |
383 isPopoverVisible: function() | |
384 { | |
385 return !!this._popover; | |
386 }, | |
387 | |
388 hidePopover: function() | |
389 { | |
390 this._resetHoverTimer(); | |
391 this._hidePopover(); | |
392 }, | |
393 | |
394 _hidePopover: function() | |
395 { | |
396 if (!this._popover) | |
397 return; | |
398 | |
399 if (this._onHide) | |
400 this._onHide(); | |
401 | |
402 this._popover.dispose(); | |
403 delete this._popover; | |
404 this._hoverElement = null; | |
405 }, | |
406 | |
407 _mouseHover: function(element) | |
408 { | |
409 delete this._hoverTimer; | |
410 this._hoverElement = element; | |
411 this._hidePopover(); | |
412 this._popover = new WebInspector.Popover(this); | |
413 this._showPopover(element, this._popover); | |
414 }, | |
415 | |
416 _killHidePopoverTimer: function() | |
417 { | |
418 if (this._hidePopoverTimer) { | |
419 clearTimeout(this._hidePopoverTimer); | |
420 delete this._hidePopoverTimer; | |
421 | |
422 // We know that we reached the popup, but we might have moved over o
ther elements. | |
423 // Discard pending command. | |
424 this._resetHoverTimer(); | |
425 } | |
426 } | |
427 }; | 406 }; |
428 | 407 |
429 /** @enum {string} */ | 408 /** @enum {string} */ |
430 WebInspector.Popover.Orientation = { | 409 WebInspector.Popover.Orientation = { |
431 Top: "top", | 410 Top: 'top', |
432 Bottom: "bottom" | 411 Bottom: 'bottom' |
433 }; | 412 }; |
OLD | NEW |