| 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 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 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 | 30 |
| 31 /** | 31 UI.Popover = class extends UI.GlassPane { |
| 32 * @unrestricted | |
| 33 */ | |
| 34 UI.Popover = class extends UI.Widget { | |
| 35 /** | 32 /** |
| 36 * @param {!UI.PopoverHelper=} popoverHelper | 33 * @param {!UI.PopoverHelper=} popoverHelper |
| 37 */ | 34 */ |
| 38 constructor(popoverHelper) { | 35 constructor(popoverHelper) { |
| 39 super(true); | 36 super(); |
| 40 this.markAsRoot(); | |
| 41 this.registerRequiredCSS('ui/popover.css'); | 37 this.registerRequiredCSS('ui/popover.css'); |
| 42 this._containerElement = createElementWithClass('div', 'fill popover-contain
er'); | 38 this.setBlockPointerEvents(false); |
| 43 this._popupArrowElement = this.contentElement.createChild('div', 'arrow'); | 39 this.setSizeBehavior(UI.GlassPane.SizeBehavior.MeasureContent); |
| 44 this._contentDiv = this.contentElement.createChild('div', 'popover-content')
; | 40 this.setShowArrow(true); |
| 45 this._popoverHelper = popoverHelper; | 41 this._popoverHelper = popoverHelper; |
| 46 this._hideBound = this.hide.bind(this); | |
| 47 } | 42 } |
| 48 | 43 |
| 49 /** | 44 /** |
| 50 * @param {!Element} element | 45 * @param {!Element} element |
| 51 * @param {!Element|!AnchorBox} anchor | 46 * @param {!Element|!AnchorBox} anchor |
| 52 * @param {?number=} preferredWidth | |
| 53 * @param {?number=} preferredHeight | |
| 54 */ | 47 */ |
| 55 showForAnchor(element, anchor, preferredWidth, preferredHeight) { | 48 showForAnchor(element, anchor) { |
| 56 this._innerShow(null, element, anchor, preferredWidth, preferredHeight); | 49 this._innerShow(null, element, anchor); |
| 57 } | 50 } |
| 58 | 51 |
| 59 /** | 52 /** |
| 60 * @param {!UI.Widget} view | 53 * @param {!UI.Widget} view |
| 61 * @param {!Element|!AnchorBox} anchor | 54 * @param {!Element|!AnchorBox} anchor |
| 62 */ | 55 */ |
| 63 showView(view, anchor) { | 56 showView(view, anchor) { |
| 64 this._innerShow(view, view.element, anchor); | 57 this._innerShow(view, view.element, anchor); |
| 65 } | 58 } |
| 66 | 59 |
| 67 /** | 60 /** |
| 68 * @param {?UI.Widget} view | 61 * @param {?UI.Widget} widget |
| 69 * @param {!Element} contentElement | 62 * @param {!Element} contentElement |
| 70 * @param {!Element|!AnchorBox} anchor | 63 * @param {!Element|!AnchorBox} anchor |
| 71 * @param {?number=} preferredWidth | |
| 72 * @param {?number=} preferredHeight | |
| 73 */ | 64 */ |
| 74 _innerShow(view, contentElement, anchor, preferredWidth, preferredHeight) { | 65 _innerShow(widget, contentElement, anchor) { |
| 75 this._contentElement = contentElement; | |
| 76 | |
| 77 // This should not happen, but we hide previous popup to be on the safe side
. | 66 // This should not happen, but we hide previous popup to be on the safe side
. |
| 78 if (UI.Popover._popover) | 67 if (UI.Popover._popover) |
| 79 UI.Popover._popover.hide(); | 68 UI.Popover._popover.hide(); |
| 80 UI.Popover._popover = this; | 69 UI.Popover._popover = this; |
| 81 | 70 |
| 82 var document = anchor instanceof Element ? anchor.ownerDocument : contentEle
ment.ownerDocument; | 71 var document = |
| 83 var window = document.defaultView; | 72 /** @type {!Document} */ (anchor instanceof Element ? anchor.ownerDocume
nt : contentElement.ownerDocument); |
| 73 var anchorBox = anchor instanceof AnchorBox ? anchor : anchor.boxInWindow(); |
| 74 this.setContentAnchorBox(anchorBox); |
| 84 | 75 |
| 85 // Temporarily attach in order to measure preferred dimensions. | 76 if (widget) |
| 86 var preferredSize = view ? view.measurePreferredSize() : UI.measurePreferred
Size(this._contentElement); | 77 widget.show(this.contentElement); |
| 87 this._preferredWidth = preferredWidth || preferredSize.width; | 78 else |
| 88 this._preferredHeight = preferredHeight || preferredSize.height; | 79 this.contentElement.appendChild(contentElement); |
| 89 | 80 |
| 90 window.addEventListener('resize', this._hideBound, false); | 81 super.show(document); |
| 91 document.body.appendChild(this._containerElement); | |
| 92 super.show(this._containerElement); | |
| 93 | |
| 94 if (view) | |
| 95 view.show(this._contentDiv); | |
| 96 else | |
| 97 this._contentDiv.appendChild(this._contentElement); | |
| 98 | |
| 99 this.positionElement(anchor, this._preferredWidth, this._preferredHeight); | |
| 100 | 82 |
| 101 if (this._popoverHelper) { | 83 if (this._popoverHelper) { |
| 102 this._contentDiv.addEventListener( | 84 this.contentElement.addEventListener( |
| 103 'mousemove', this._popoverHelper._killHidePopoverTimer.bind(this._popo
verHelper), true); | 85 'mousemove', this._popoverHelper._killHidePopoverTimer.bind(this._popo
verHelper), true); |
| 104 this.element.addEventListener('mouseout', this._popoverHelper._popoverMous
eOut.bind(this._popoverHelper), true); | 86 this.contentElement.addEventListener( |
| 87 'mouseout', this._popoverHelper._popoverMouseOut.bind(this._popoverHel
per), true); |
| 105 } | 88 } |
| 106 } | 89 } |
| 107 | 90 |
| 91 /** |
| 92 * @override |
| 93 */ |
| 108 hide() { | 94 hide() { |
| 109 this._containerElement.ownerDocument.defaultView.removeEventListener('resize
', this._hideBound, false); | 95 super.hide(); |
| 110 this.detach(); | |
| 111 this._containerElement.remove(); | |
| 112 delete UI.Popover._popover; | 96 delete UI.Popover._popover; |
| 113 } | 97 } |
| 114 | 98 |
| 115 /** | 99 /** |
| 116 * @param {boolean} canShrink | |
| 117 */ | |
| 118 setCanShrink(canShrink) { | |
| 119 this._hasFixedHeight = !canShrink; | |
| 120 } | |
| 121 | |
| 122 /** | |
| 123 * @param {boolean} noPadding | 100 * @param {boolean} noPadding |
| 124 */ | 101 */ |
| 125 setNoPadding(noPadding) { | 102 setNoPadding(noPadding) { |
| 126 this._hasNoPadding = noPadding; | 103 // TODO(dgozman): remove this. Clients should add padding themselves. |
| 127 this._contentDiv.classList.toggle('no-padding', this._hasNoPadding); | 104 this.contentElement.classList.toggle('no-padding', noPadding); |
| 128 } | |
| 129 | |
| 130 /** | |
| 131 * @param {!Element|!AnchorBox} anchorElement | |
| 132 * @param {number=} preferredWidth | |
| 133 * @param {number=} preferredHeight | |
| 134 */ | |
| 135 positionElement(anchorElement, preferredWidth, preferredHeight) { | |
| 136 const borderWidth = this._hasNoPadding ? 0 : 8; | |
| 137 const scrollerWidth = this._hasFixedHeight ? 0 : 14; | |
| 138 const arrowHeight = this._hasNoPadding ? 8 : 15; | |
| 139 const arrowOffset = 10; | |
| 140 const borderRadius = 4; | |
| 141 const arrowRadius = 6; | |
| 142 preferredWidth = preferredWidth || this._preferredWidth; | |
| 143 preferredHeight = preferredHeight || this._preferredHeight; | |
| 144 | |
| 145 // Skinny tooltips are not pretty, their arrow location is not nice. | |
| 146 preferredWidth = Math.max(preferredWidth, 50); | |
| 147 // Position relative to main DevTools element. | |
| 148 const container = UI.GlassPane.container(/** @type {!Document} */ (this._con
tainerElement.ownerDocument)); | |
| 149 const totalWidth = container.offsetWidth; | |
| 150 const totalHeight = container.offsetHeight; | |
| 151 | |
| 152 var anchorBox = anchorElement instanceof AnchorBox ? anchorElement : anchorE
lement.boxInWindow(window); | |
| 153 anchorBox = anchorBox.relativeToElement(container); | |
| 154 var newElementPosition = {x: 0, y: 0, width: preferredWidth + scrollerWidth,
height: preferredHeight}; | |
| 155 | |
| 156 var arrowAtBottom; | |
| 157 var roomAbove = anchorBox.y; | |
| 158 var roomBelow = totalHeight - anchorBox.y - anchorBox.height; | |
| 159 this._popupArrowElement.hidden = false; | |
| 160 | |
| 161 if (roomAbove > roomBelow) { | |
| 162 // Positioning above the anchor. | |
| 163 if (anchorBox.y > newElementPosition.height + arrowHeight + borderRadius)
{ | |
| 164 newElementPosition.y = anchorBox.y - newElementPosition.height - arrowHe
ight; | |
| 165 } else { | |
| 166 this._popupArrowElement.hidden = true; | |
| 167 newElementPosition.y = borderRadius; | |
| 168 newElementPosition.height = anchorBox.y - borderRadius * 2 - arrowHeight
; | |
| 169 if (this._hasFixedHeight && newElementPosition.height < preferredHeight)
{ | |
| 170 newElementPosition.y = borderRadius; | |
| 171 newElementPosition.height = preferredHeight; | |
| 172 } | |
| 173 } | |
| 174 arrowAtBottom = true; | |
| 175 } else { | |
| 176 // Positioning below the anchor. | |
| 177 newElementPosition.y = anchorBox.y + anchorBox.height + arrowHeight; | |
| 178 if (newElementPosition.y + newElementPosition.height + borderRadius >= tot
alHeight) { | |
| 179 this._popupArrowElement.hidden = true; | |
| 180 newElementPosition.height = totalHeight - borderRadius - newElementPosit
ion.y; | |
| 181 if (this._hasFixedHeight && newElementPosition.height < preferredHeight)
{ | |
| 182 newElementPosition.y = totalHeight - preferredHeight - borderRadius; | |
| 183 newElementPosition.height = preferredHeight; | |
| 184 } | |
| 185 } | |
| 186 // Align arrow. | |
| 187 arrowAtBottom = false; | |
| 188 } | |
| 189 | |
| 190 var arrowAtLeft; | |
| 191 this._popupArrowElement.removeAttribute('style'); | |
| 192 if (anchorBox.x + newElementPosition.width < totalWidth) { | |
| 193 newElementPosition.x = Math.max(borderRadius, anchorBox.x - borderRadius -
arrowOffset); | |
| 194 arrowAtLeft = true; | |
| 195 this._popupArrowElement.style.left = arrowOffset + 'px'; | |
| 196 } else if (newElementPosition.width + borderRadius * 2 < totalWidth) { | |
| 197 newElementPosition.x = totalWidth - newElementPosition.width - borderRadiu
s - 2 * borderWidth; | |
| 198 arrowAtLeft = false; | |
| 199 // Position arrow accurately. | |
| 200 var arrowRightPosition = Math.max(0, totalWidth - anchorBox.x - anchorBox.
width - borderRadius - arrowOffset); | |
| 201 arrowRightPosition += anchorBox.width / 2; | |
| 202 arrowRightPosition = Math.min(arrowRightPosition, newElementPosition.width
- borderRadius - arrowOffset); | |
| 203 this._popupArrowElement.style.right = arrowRightPosition + 'px'; | |
| 204 } else { | |
| 205 newElementPosition.x = borderRadius; | |
| 206 newElementPosition.width = totalWidth - borderRadius * 2; | |
| 207 newElementPosition.height += scrollerWidth; | |
| 208 arrowAtLeft = true; | |
| 209 if (arrowAtBottom) | |
| 210 newElementPosition.y -= scrollerWidth; | |
| 211 // Position arrow accurately. | |
| 212 this._popupArrowElement.style.left = | |
| 213 Math.max(0, anchorBox.x - newElementPosition.x - borderRadius - arrowR
adius + anchorBox.width / 2) + 'px'; | |
| 214 } | |
| 215 | |
| 216 this._popupArrowElement.className = | |
| 217 `arrow ${(arrowAtBottom ? 'bottom' : 'top')}-${(arrowAtLeft ? 'left' : '
right')}-arrow`; | |
| 218 this.element.positionAt(newElementPosition.x, newElementPosition.y - borderW
idth, container); | |
| 219 this.element.style.width = newElementPosition.width + borderWidth * 2 + 'px'
; | |
| 220 this.element.style.height = newElementPosition.height + borderWidth * 2 + 'p
x'; | |
| 221 } | 105 } |
| 222 }; | 106 }; |
| 223 | 107 |
| 224 /** | 108 /** |
| 225 * @unrestricted | 109 * @unrestricted |
| 226 */ | 110 */ |
| 227 UI.PopoverHelper = class { | 111 UI.PopoverHelper = class { |
| 228 /** | 112 /** |
| 229 * @param {!Element} panelElement | 113 * @param {!Element} panelElement |
| 230 * @param {boolean=} disableOnClick | 114 * @param {boolean=} disableOnClick |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 287 if (this._eventInHoverElement(event)) | 171 if (this._eventInHoverElement(event)) |
| 288 return; | 172 return; |
| 289 | 173 |
| 290 this._startHidePopoverTimer(); | 174 this._startHidePopoverTimer(); |
| 291 this._handleMouseAction(event, false); | 175 this._handleMouseAction(event, false); |
| 292 } | 176 } |
| 293 | 177 |
| 294 _popoverMouseOut(event) { | 178 _popoverMouseOut(event) { |
| 295 if (!this.isPopoverVisible()) | 179 if (!this.isPopoverVisible()) |
| 296 return; | 180 return; |
| 297 if (event.relatedTarget && !event.relatedTarget.isSelfOrDescendant(this._pop
over._contentDiv)) | 181 if (event.relatedTarget && !event.relatedTarget.isSelfOrDescendant(this._pop
over.contentElement)) |
| 298 this._startHidePopoverTimer(); | 182 this._startHidePopoverTimer(); |
| 299 } | 183 } |
| 300 | 184 |
| 301 _mouseOut(event) { | 185 _mouseOut(event) { |
| 302 if (!this.isPopoverVisible()) | 186 if (!this.isPopoverVisible()) |
| 303 return; | 187 return; |
| 304 if (!this._eventInHoverElement(event)) | 188 if (!this._eventInHoverElement(event)) |
| 305 this._startHidePopoverTimer(); | 189 this._startHidePopoverTimer(); |
| 306 } | 190 } |
| 307 | 191 |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 375 if (this._hidePopoverTimer) { | 259 if (this._hidePopoverTimer) { |
| 376 clearTimeout(this._hidePopoverTimer); | 260 clearTimeout(this._hidePopoverTimer); |
| 377 delete this._hidePopoverTimer; | 261 delete this._hidePopoverTimer; |
| 378 | 262 |
| 379 // We know that we reached the popup, but we might have moved over other e
lements. | 263 // We know that we reached the popup, but we might have moved over other e
lements. |
| 380 // Discard pending command. | 264 // Discard pending command. |
| 381 this._resetHoverTimer(); | 265 this._resetHoverTimer(); |
| 382 } | 266 } |
| 383 } | 267 } |
| 384 }; | 268 }; |
| OLD | NEW |