2 * Copyright (C) 2009 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
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.
33 * @extends {WebInspector.View}
34 * @param {!WebInspector.PopoverHelper=} popoverHelper
36 WebInspector.Popover = function(popoverHelper)
38 WebInspector.View.call(this);
40 this.element.className = "popover custom-popup-vertical-scroll custom-popup-horizontal-scroll"; // Override
41 this._containerElement = document.createElementWithClass("div", "fill popover-container");
43 this._popupArrowElement = this.element.createChild("div", "arrow");
44 this._contentDiv = this.element.createChild("div", "content");
46 this._popoverHelper = popoverHelper;
47 this._hideBound = this.hide.bind(this);
50 WebInspector.Popover.prototype = {
52 * @param {!Element} element
53 * @param {!Element|!AnchorBox} anchor
54 * @param {?number=} preferredWidth
55 * @param {?number=} preferredHeight
56 * @param {?WebInspector.Popover.Orientation=} arrowDirection
58 show: function(element, anchor, preferredWidth, preferredHeight, arrowDirection)
60 this._innerShow(null, element, anchor, preferredWidth, preferredHeight, arrowDirection);
64 * @param {!WebInspector.View} view
65 * @param {!Element|!AnchorBox} anchor
66 * @param {?number=} preferredWidth
67 * @param {?number=} preferredHeight
69 showView: function(view, anchor, preferredWidth, preferredHeight)
71 this._innerShow(view, view.element, anchor, preferredWidth, preferredHeight);
75 * @param {?WebInspector.View} view
76 * @param {!Element} contentElement
77 * @param {!Element|!AnchorBox} anchor
78 * @param {?number=} preferredWidth
79 * @param {?number=} preferredHeight
80 * @param {?WebInspector.Popover.Orientation=} arrowDirection
82 _innerShow: function(view, contentElement, anchor, preferredWidth, preferredHeight, arrowDirection)
86 this.contentElement = contentElement;
88 // This should not happen, but we hide previous popup to be on the safe side.
89 if (WebInspector.Popover._popover)
90 WebInspector.Popover._popover.hide();
91 WebInspector.Popover._popover = this;
93 // Temporarily attach in order to measure preferred dimensions.
94 var preferredSize = view ? view.measurePreferredSize() : this.contentElement.measurePreferredSize();
95 preferredWidth = preferredWidth || preferredSize.width;
96 preferredHeight = preferredHeight || preferredSize.height;
98 window.addEventListener("resize", this._hideBound, false);
99 document.body.appendChild(this._containerElement);
100 WebInspector.View.prototype.show.call(this, this._containerElement);
103 view.show(this._contentDiv);
105 this._contentDiv.appendChild(this.contentElement);
107 this._positionElement(anchor, preferredWidth, preferredHeight, arrowDirection);
109 if (this._popoverHelper) {
110 this._contentDiv.addEventListener("mousemove", this._popoverHelper._killHidePopoverTimer.bind(this._popoverHelper), true);
111 this.element.addEventListener("mouseout", this._popoverHelper._popoverMouseOut.bind(this._popoverHelper), true);
117 window.removeEventListener("resize", this._hideBound, false);
119 this._containerElement.remove();
120 delete WebInspector.Popover._popover;
125 return this._disposed;
130 if (this.isShowing())
132 this._disposed = true;
135 setCanShrink: function(canShrink)
137 this._hasFixedHeight = !canShrink;
138 this._contentDiv.classList.add("fixed-height");
142 * @param {!Element|!AnchorBox} anchorElement
143 * @param {number} preferredWidth
144 * @param {number} preferredHeight
145 * @param {?WebInspector.Popover.Orientation=} arrowDirection
147 _positionElement: function(anchorElement, preferredWidth, preferredHeight, arrowDirection)
149 const borderWidth = 25;
150 const scrollerWidth = this._hasFixedHeight ? 0 : 11;
151 const arrowHeight = 15;
152 const arrowOffset = 10;
153 const borderRadius = 10;
155 // Skinny tooltips are not pretty, their arrow location is not nice.
156 preferredWidth = Math.max(preferredWidth, 50);
157 // Position relative to main DevTools element.
158 const container = WebInspector.Dialog.modalHostView().element;
159 const totalWidth = container.offsetWidth;
160 const totalHeight = container.offsetHeight;
162 var anchorBox = anchorElement instanceof AnchorBox ? anchorElement : anchorElement.boxInWindow(window);
163 anchorBox = anchorBox.relativeToElement(container);
164 var newElementPosition = { x: 0, y: 0, width: preferredWidth + scrollerWidth, height: preferredHeight };
166 var verticalAlignment;
167 var roomAbove = anchorBox.y;
168 var roomBelow = totalHeight - anchorBox.y - anchorBox.height;
170 if ((roomAbove > roomBelow) || (arrowDirection === WebInspector.Popover.Orientation.Bottom)) {
171 // Positioning above the anchor.
172 if ((anchorBox.y > newElementPosition.height + arrowHeight + borderRadius) || (arrowDirection === WebInspector.Popover.Orientation.Bottom))
173 newElementPosition.y = anchorBox.y - newElementPosition.height - arrowHeight;
175 newElementPosition.y = borderRadius;
176 newElementPosition.height = anchorBox.y - borderRadius * 2 - arrowHeight;
177 if (this._hasFixedHeight && newElementPosition.height < preferredHeight) {
178 newElementPosition.y = borderRadius;
179 newElementPosition.height = preferredHeight;
182 verticalAlignment = WebInspector.Popover.Orientation.Bottom;
184 // Positioning below the anchor.
185 newElementPosition.y = anchorBox.y + anchorBox.height + arrowHeight;
186 if ((newElementPosition.y + newElementPosition.height + borderRadius >= totalHeight) && (arrowDirection !== WebInspector.Popover.Orientation.Top)) {
187 newElementPosition.height = totalHeight - borderRadius - newElementPosition.y;
188 if (this._hasFixedHeight && newElementPosition.height < preferredHeight) {
189 newElementPosition.y = totalHeight - preferredHeight - borderRadius;
190 newElementPosition.height = preferredHeight;
194 verticalAlignment = WebInspector.Popover.Orientation.Top;
197 var horizontalAlignment;
198 if (anchorBox.x + newElementPosition.width < totalWidth) {
199 newElementPosition.x = Math.max(borderRadius, anchorBox.x - borderRadius - arrowOffset);
200 horizontalAlignment = "left";
201 } else if (newElementPosition.width + borderRadius * 2 < totalWidth) {
202 newElementPosition.x = totalWidth - newElementPosition.width - borderRadius;
203 horizontalAlignment = "right";
204 // Position arrow accurately.
205 var arrowRightPosition = Math.max(0, totalWidth - anchorBox.x - anchorBox.width - borderRadius - arrowOffset);
206 arrowRightPosition += anchorBox.width / 2;
207 arrowRightPosition = Math.min(arrowRightPosition, newElementPosition.width - borderRadius - arrowOffset);
208 this._popupArrowElement.style.right = arrowRightPosition + "px";
210 newElementPosition.x = borderRadius;
211 newElementPosition.width = totalWidth - borderRadius * 2;
212 newElementPosition.height += scrollerWidth;
213 horizontalAlignment = "left";
214 if (verticalAlignment === WebInspector.Popover.Orientation.Bottom)
215 newElementPosition.y -= scrollerWidth;
216 // Position arrow accurately.
217 this._popupArrowElement.style.left = Math.max(0, anchorBox.x - borderRadius * 2 - arrowOffset) + "px";
218 this._popupArrowElement.style.left += anchorBox.width / 2;
221 this.element.className = "popover custom-popup-vertical-scroll custom-popup-horizontal-scroll " + verticalAlignment + "-" + horizontalAlignment + "-arrow";
222 this.element.positionAt(newElementPosition.x - borderWidth, newElementPosition.y - borderWidth, container);
223 this.element.style.width = newElementPosition.width + borderWidth * 2 + "px";
224 this.element.style.height = newElementPosition.height + borderWidth * 2 + "px";
227 __proto__: WebInspector.View.prototype
232 * @param {!Element} panelElement
233 * @param {function(!Element, !Event):(!Element|!AnchorBox|undefined)} getAnchor
234 * @param {function(!Element, !WebInspector.Popover):undefined} showPopover
235 * @param {function()=} onHide
236 * @param {boolean=} disableOnClick
238 WebInspector.PopoverHelper = function(panelElement, getAnchor, showPopover, onHide, disableOnClick)
240 this._panelElement = panelElement;
241 this._getAnchor = getAnchor;
242 this._showPopover = showPopover;
243 this._onHide = onHide;
244 this._disableOnClick = !!disableOnClick;
245 panelElement.addEventListener("mousedown", this._mouseDown.bind(this), false);
246 panelElement.addEventListener("mousemove", this._mouseMove.bind(this), false);
247 panelElement.addEventListener("mouseout", this._mouseOut.bind(this), false);
248 this.setTimeout(1000, 500);
251 WebInspector.PopoverHelper.prototype = {
253 * @param {number} timeout
254 * @param {number=} hideTimeout
256 setTimeout: function(timeout, hideTimeout)
258 this._timeout = timeout;
259 if (typeof hideTimeout === "number")
260 this._hideTimeout = hideTimeout;
262 this._hideTimeout = timeout / 2;
266 * @param {!MouseEvent} event
269 _eventInHoverElement: function(event)
271 if (!this._hoverElement)
273 var box = this._hoverElement instanceof AnchorBox ? this._hoverElement : this._hoverElement.boxInWindow();
274 return (box.x <= event.clientX && event.clientX <= box.x + box.width &&
275 box.y <= event.clientY && event.clientY <= box.y + box.height);
278 _mouseDown: function(event)
280 if (this._disableOnClick || !this._eventInHoverElement(event))
283 this._killHidePopoverTimer();
284 this._handleMouseAction(event, true);
288 _mouseMove: function(event)
290 // Pretend that nothing has happened.
291 if (this._eventInHoverElement(event))
294 this._startHidePopoverTimer();
295 this._handleMouseAction(event, false);
298 _popoverMouseOut: function(event)
300 if (!this.isPopoverVisible())
302 if (event.relatedTarget && !event.relatedTarget.isSelfOrDescendant(this._popover._contentDiv))
303 this._startHidePopoverTimer();
306 _mouseOut: function(event)
308 if (!this.isPopoverVisible())
310 if (!this._eventInHoverElement(event))
311 this._startHidePopoverTimer();
314 _startHidePopoverTimer: function()
316 // User has 500ms (this._hideTimeout) to reach the popup.
317 if (!this._popover || this._hidePopoverTimer)
321 * @this {WebInspector.PopoverHelper}
326 delete this._hidePopoverTimer;
328 this._hidePopoverTimer = setTimeout(doHide.bind(this), this._hideTimeout);
331 _handleMouseAction: function(event, isMouseDown)
333 this._resetHoverTimer();
334 if (event.which && this._disableOnClick)
336 this._hoverElement = this._getAnchor(event.target, event);
337 if (!this._hoverElement)
339 const toolTipDelay = isMouseDown ? 0 : (this._popup ? this._timeout * 0.6 : this._timeout);
340 this._hoverTimer = setTimeout(this._mouseHover.bind(this, this._hoverElement), toolTipDelay);
343 _resetHoverTimer: function()
345 if (this._hoverTimer) {
346 clearTimeout(this._hoverTimer);
347 delete this._hoverTimer;
354 isPopoverVisible: function()
356 return !!this._popover;
359 hidePopover: function()
361 this._resetHoverTimer();
365 _hidePopover: function()
373 this._popover.dispose();
374 delete this._popover;
375 this._hoverElement = null;
378 _mouseHover: function(element)
380 delete this._hoverTimer;
383 this._popover = new WebInspector.Popover(this);
384 this._showPopover(element, this._popover);
387 _killHidePopoverTimer: function()
389 if (this._hidePopoverTimer) {
390 clearTimeout(this._hidePopoverTimer);
391 delete this._hidePopoverTimer;
393 // We know that we reached the popup, but we might have moved over other elements.
394 // Discard pending command.
395 this._resetHoverTimer();
400 /** @enum {string} */
401 WebInspector.Popover.Orientation = {