| 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 |