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