| 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 15 matching lines...) Expand all Loading... |
| 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 /** |
| 32 * @unrestricted | 32 * @unrestricted |
| 33 */ | 33 */ |
| 34 UI.PopoverHelper = class { | 34 UI.PopoverHelper = class { |
| 35 /** | 35 /** |
| 36 * @param {!Element} panelElement | 36 * @param {!Element} container |
| 37 * @param {boolean=} disableOnClick | 37 * @param {function(!Event):?UI.PopoverRequest} getRequest |
| 38 */ | 38 */ |
| 39 constructor(panelElement, disableOnClick) { | 39 constructor(container, getRequest) { |
| 40 this._disableOnClick = !!disableOnClick; | 40 this._disableOnClick = false; |
| 41 this._hasPadding = false; | 41 this._hasPadding = false; |
| 42 panelElement.addEventListener('mousedown', this._mouseDown.bind(this), false
); | 42 this._getRequest = getRequest; |
| 43 panelElement.addEventListener('mousemove', this._mouseMove.bind(this), false
); | 43 this._scheduledRequest = null; |
| 44 panelElement.addEventListener('mouseout', this._mouseOut.bind(this), false); | 44 /** @type {?function()} */ |
| 45 this._hidePopoverCallback = null; |
| 46 container.addEventListener('mousedown', this._mouseDown.bind(this), false); |
| 47 container.addEventListener('mousemove', this._mouseMove.bind(this), false); |
| 48 container.addEventListener('mouseout', this._mouseOut.bind(this), false); |
| 45 this.setTimeout(1000, 500); | 49 this.setTimeout(1000, 500); |
| 46 } | 50 } |
| 47 | 51 |
| 48 /** | 52 /** |
| 49 * @param {function(!Element, !Event):(!Element|!AnchorBox|undefined)} getAnch
or | |
| 50 * @param {function((!Element|!AnchorBox), !UI.GlassPane):!Promise<boolean>} s
howPopover | |
| 51 * @param {function()=} onHide | |
| 52 */ | |
| 53 initializeCallbacks(getAnchor, showPopover, onHide) { | |
| 54 this._getAnchor = getAnchor; | |
| 55 this._showPopover = showPopover; | |
| 56 this._onHide = onHide; | |
| 57 } | |
| 58 | |
| 59 /** | |
| 60 * @param {number} timeout | 53 * @param {number} timeout |
| 61 * @param {number=} hideTimeout | 54 * @param {number=} hideTimeout |
| 62 */ | 55 */ |
| 63 setTimeout(timeout, hideTimeout) { | 56 setTimeout(timeout, hideTimeout) { |
| 64 this._timeout = timeout; | 57 this._timeout = timeout; |
| 65 if (typeof hideTimeout === 'number') | 58 if (typeof hideTimeout === 'number') |
| 66 this._hideTimeout = hideTimeout; | 59 this._hideTimeout = hideTimeout; |
| 67 else | 60 else |
| 68 this._hideTimeout = timeout / 2; | 61 this._hideTimeout = timeout / 2; |
| 69 } | 62 } |
| 70 | 63 |
| 71 /** | 64 /** |
| 72 * @param {boolean} hasPadding | 65 * @param {boolean} hasPadding |
| 73 */ | 66 */ |
| 74 setHasPadding(hasPadding) { | 67 setHasPadding(hasPadding) { |
| 75 this._hasPadding = hasPadding; | 68 this._hasPadding = hasPadding; |
| 76 } | 69 } |
| 77 | 70 |
| 78 /** | 71 /** |
| 79 * @param {!MouseEvent} event | 72 * @param {boolean} disableOnClick |
| 73 */ |
| 74 setDisableOnClick(disableOnClick) { |
| 75 this._disableOnClick = disableOnClick; |
| 76 } |
| 77 |
| 78 /** |
| 79 * @param {!Event} event |
| 80 * @return {boolean} | 80 * @return {boolean} |
| 81 */ | 81 */ |
| 82 _eventInHoverElement(event) { | 82 _eventInScheduledContent(event) { |
| 83 if (!this._hoverElement) | 83 return this._scheduledRequest ? this._scheduledRequest.box.contains(event.cl
ientX, event.clientY) : false; |
| 84 return false; | |
| 85 var box = this._hoverElement instanceof AnchorBox ? this._hoverElement : thi
s._hoverElement.boxInWindow(); | |
| 86 return ( | |
| 87 box.x <= event.clientX && event.clientX <= box.x + box.width && box.y <=
event.clientY && | |
| 88 event.clientY <= box.y + box.height); | |
| 89 } | 84 } |
| 90 | 85 |
| 86 /** |
| 87 * @param {!Event} event |
| 88 */ |
| 91 _mouseDown(event) { | 89 _mouseDown(event) { |
| 92 if (this._disableOnClick || !this._eventInHoverElement(event)) { | 90 if (this._disableOnClick || !this._eventInScheduledContent(event)) { |
| 93 this.hidePopover(); | 91 this.hidePopover(); |
| 94 } else { | 92 } else { |
| 95 this._killHidePopoverTimer(); | 93 this._stopHidePopoverTimer(); |
| 96 this._handleMouseAction(event, true); | 94 this._stopShowPopoverTimer(); |
| 95 this._startShowPopoverTimer(event, 0); |
| 97 } | 96 } |
| 98 } | 97 } |
| 99 | 98 |
| 99 /** |
| 100 * @param {!Event} event |
| 101 */ |
| 100 _mouseMove(event) { | 102 _mouseMove(event) { |
| 101 // Pretend that nothing has happened. | 103 // Pretend that nothing has happened. |
| 102 if (this._eventInHoverElement(event)) | 104 if (this._eventInScheduledContent(event)) |
| 103 return; | 105 return; |
| 104 | 106 |
| 105 this._startHidePopoverTimer(); | 107 this._startHidePopoverTimer(); |
| 106 this._handleMouseAction(event, false); | 108 this._stopShowPopoverTimer(); |
| 109 if (event.which && this._disableOnClick) |
| 110 return; |
| 111 this._startShowPopoverTimer( |
| 112 event, this.isPopoverVisible() ? Math.max(this._timeout * 0.6, this._hid
eTimeout) : this._timeout); |
| 107 } | 113 } |
| 108 | 114 |
| 109 _popoverMouseOut(event) { | 115 /** |
| 110 if (!this.isPopoverVisible()) | 116 * @param {!Event} event |
| 117 */ |
| 118 _popoverMouseMove(event) { |
| 119 this._stopHidePopoverTimer(); |
| 120 } |
| 121 |
| 122 /** |
| 123 * @param {!UI.GlassPane} popover |
| 124 * @param {!Event} event |
| 125 */ |
| 126 _popoverMouseOut(popover, event) { |
| 127 if (!popover.isShowing()) |
| 111 return; | 128 return; |
| 112 if (event.relatedTarget && !event.relatedTarget.isSelfOrDescendant(this._pop
over.contentElement)) | 129 if (event.relatedTarget && !event.relatedTarget.isSelfOrDescendant(popover.c
ontentElement)) |
| 113 this._startHidePopoverTimer(); | 130 this._startHidePopoverTimer(); |
| 114 } | 131 } |
| 115 | 132 |
| 133 /** |
| 134 * @param {!Event} event |
| 135 */ |
| 116 _mouseOut(event) { | 136 _mouseOut(event) { |
| 117 if (!this.isPopoverVisible()) | 137 if (!this.isPopoverVisible()) |
| 118 return; | 138 return; |
| 119 if (!this._eventInHoverElement(event)) | 139 if (!this._eventInScheduledContent(event)) |
| 120 this._startHidePopoverTimer(); | 140 this._startHidePopoverTimer(); |
| 121 } | 141 } |
| 122 | 142 |
| 123 _startHidePopoverTimer() { | 143 _startHidePopoverTimer() { |
| 124 // User has 500ms (this._hideTimeout) to reach the popup. | 144 // User has this._hideTimeout to reach the popup. |
| 125 if (!this._popover || this._hidePopoverTimer) | 145 if (!this._hidePopoverCallback || this._hidePopoverTimer) |
| 126 return; | 146 return; |
| 127 | 147 |
| 128 /** | 148 this._hidePopoverTimer = setTimeout(() => { |
| 129 * @this {UI.PopoverHelper} | |
| 130 */ | |
| 131 function doHide() { | |
| 132 this._hidePopover(); | 149 this._hidePopover(); |
| 133 delete this._hidePopoverTimer; | 150 delete this._hidePopoverTimer; |
| 134 } | 151 }, this._hideTimeout); |
| 135 this._hidePopoverTimer = setTimeout(doHide.bind(this), this._hideTimeout); | |
| 136 } | 152 } |
| 137 | 153 |
| 138 _handleMouseAction(event, isMouseDown) { | 154 /** |
| 139 this._resetHoverTimer(); | 155 * @param {!Event} event |
| 140 if (event.which && this._disableOnClick) | 156 * @param {number} timeout |
| 157 */ |
| 158 _startShowPopoverTimer(event, timeout) { |
| 159 this._scheduledRequest = this._getRequest.call(null, event); |
| 160 if (!this._scheduledRequest) |
| 141 return; | 161 return; |
| 142 this._hoverElement = this._getAnchor(event.target, event); | 162 |
| 143 if (!this._hoverElement) | 163 this._showPopoverTimer = setTimeout(() => { |
| 144 return; | 164 delete this._showPopoverTimer; |
| 145 const toolTipDelay = isMouseDown ? 0 : (this._popup ? this._timeout * 0.6 :
this._timeout); | 165 this._showPopover(event.target.ownerDocument); |
| 146 this._hoverTimer = | 166 }, timeout); |
| 147 setTimeout(this._mouseHover.bind(this, this._hoverElement, event.target.
ownerDocument), toolTipDelay); | |
| 148 } | 167 } |
| 149 | 168 |
| 150 _resetHoverTimer() { | 169 _stopShowPopoverTimer() { |
| 151 if (this._hoverTimer) { | 170 if (!this._showPopoverTimer) |
| 152 clearTimeout(this._hoverTimer); | 171 return; |
| 153 delete this._hoverTimer; | 172 clearTimeout(this._showPopoverTimer); |
| 154 } | 173 delete this._showPopoverTimer; |
| 155 } | 174 } |
| 156 | 175 |
| 157 /** | 176 /** |
| 158 * @return {boolean} | 177 * @return {boolean} |
| 159 */ | 178 */ |
| 160 isPopoverVisible() { | 179 isPopoverVisible() { |
| 161 return !!this._popover; | 180 return !!this._hidePopoverCallback; |
| 162 } | 181 } |
| 163 | 182 |
| 164 hidePopover() { | 183 hidePopover() { |
| 165 this._resetHoverTimer(); | 184 this._stopShowPopoverTimer(); |
| 166 this._hidePopover(); | 185 this._hidePopover(); |
| 167 } | 186 } |
| 168 | 187 |
| 169 _hidePopover() { | 188 _hidePopover() { |
| 170 if (!this._popover) | 189 if (!this._hidePopoverCallback) |
| 171 return; | 190 return; |
| 172 | 191 this._hidePopoverCallback.call(null); |
| 173 delete UI.PopoverHelper._popover; | 192 this._hidePopoverCallback = null; |
| 174 if (this._onHide) | |
| 175 this._onHide(); | |
| 176 | |
| 177 if (this._popover.isShowing()) | |
| 178 this._popover.hide(); | |
| 179 delete this._popover; | |
| 180 this._hoverElement = null; | |
| 181 } | 193 } |
| 182 | 194 |
| 183 _mouseHover(element, document) { | 195 /** |
| 184 delete this._hoverTimer; | 196 * @param {!Document} document |
| 185 this._hidePopover(); | 197 */ |
| 186 this._hoverElement = element; | 198 _showPopover(document) { |
| 199 var popover = new UI.GlassPane(); |
| 200 popover.registerRequiredCSS('ui/popover.css'); |
| 201 popover.setSizeBehavior(UI.GlassPane.SizeBehavior.MeasureContent); |
| 202 popover.setBlockPointerEvents(false); |
| 203 popover.setShowArrow(true); |
| 204 var request = this._scheduledRequest; |
| 205 request.show.call(null, popover).then(success => { |
| 206 if (!success) |
| 207 return; |
| 187 | 208 |
| 188 this._popover = new UI.GlassPane(); | 209 if (this._scheduledRequest !== request) { |
| 189 this._popover.registerRequiredCSS('ui/popover.css'); | 210 if (request.hide) |
| 190 this._popover.setBlockPointerEvents(false); | 211 request.hide.call(null); |
| 191 this._popover.setSizeBehavior(UI.GlassPane.SizeBehavior.MeasureContent); | 212 return; |
| 192 this._popover.setShowArrow(true); | 213 } |
| 193 this._popover.contentElement.classList.toggle('has-padding', this._hasPaddin
g); | |
| 194 this._popover.contentElement.addEventListener('mousemove', this._killHidePop
overTimer.bind(this), true); | |
| 195 this._popover.contentElement.addEventListener('mouseout', this._popoverMouse
Out.bind(this), true); | |
| 196 this._popover.setContentAnchorBox( | |
| 197 this._hoverElement instanceof AnchorBox ? this._hoverElement : this._hov
erElement.boxInWindow()); | |
| 198 | 214 |
| 199 // This should not happen, but we hide previous popover to be on the safe si
de. | 215 // This should not happen, but we hide previous popover to be on the safe
side. |
| 200 if (UI.PopoverHelper._popover) { | 216 if (UI.PopoverHelper._popoverHelper) { |
| 201 console.error('One popover is already visible'); | 217 console.error('One popover is already visible'); |
| 202 UI.PopoverHelper._popover.hide(); | 218 UI.PopoverHelper._popoverHelper.hidePopover(); |
| 203 } | 219 } |
| 204 UI.PopoverHelper._popover = this._popover; | 220 UI.PopoverHelper._popoverHelper = this; |
| 205 var popover = this._popover; | 221 |
| 206 this._showPopover(element, this._popover).then(success => { | 222 popover.contentElement.classList.toggle('has-padding', this._hasPadding); |
| 207 if (success && this._popover === popover && this._hoverElement === element
) | 223 popover.contentElement.addEventListener('mousemove', this._popoverMouseMov
e.bind(this), true); |
| 208 popover.show(document); | 224 popover.contentElement.addEventListener('mouseout', this._popoverMouseOut.
bind(this, popover), true); |
| 225 popover.setContentAnchorBox(request.box); |
| 226 popover.show(document); |
| 227 |
| 228 this._hidePopoverCallback = () => { |
| 229 if (request.hide) |
| 230 request.hide.call(null); |
| 231 popover.hide(); |
| 232 delete UI.PopoverHelper._popoverHelper; |
| 233 }; |
| 209 }); | 234 }); |
| 210 } | 235 } |
| 211 | 236 |
| 212 _killHidePopoverTimer() { | 237 _stopHidePopoverTimer() { |
| 213 if (this._hidePopoverTimer) { | 238 if (!this._hidePopoverTimer) |
| 214 clearTimeout(this._hidePopoverTimer); | 239 return; |
| 215 delete this._hidePopoverTimer; | 240 clearTimeout(this._hidePopoverTimer); |
| 241 delete this._hidePopoverTimer; |
| 216 | 242 |
| 217 // We know that we reached the popup, but we might have moved over other e
lements. | 243 // We know that we reached the popup, but we might have moved over other ele
ments. |
| 218 // Discard pending command. | 244 // Discard pending command. |
| 219 this._resetHoverTimer(); | 245 this._stopShowPopoverTimer(); |
| 220 } | |
| 221 } | 246 } |
| 222 }; | 247 }; |
| 248 |
| 249 /** @typedef {{box: !AnchorBox, show:(function(!UI.GlassPane):!Promise<boolean>)
, hide:(function()|undefined)}} */ |
| 250 UI.PopoverRequest; |
| OLD | NEW |