blob: 7daf8a9b71bed2ffc53f5975e9c03c0e37525bac [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371/*
2 * Copyright (C) 2009 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
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
13 * distribution.
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.
17 *
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.
29 */
30
Paul Lewis9950e182019-12-16 16:06:0731import {GlassPane, MarginBehavior, SizeBehavior} from './GlassPane.js';
32
33export class PopoverHelper {
Blink Reformat4c46d092018-04-07 15:32:3734 /**
35 * @param {!Element} container
Tim van der Lippeaa76aa22020-02-14 14:38:2436 * @param {function(!MouseEvent):?PopoverRequest} getRequest
Blink Reformat4c46d092018-04-07 15:32:3737 */
38 constructor(container, getRequest) {
39 this._disableOnClick = false;
40 this._hasPadding = false;
41 this._getRequest = getRequest;
42 this._scheduledRequest = null;
Tim van der Lippeee97fa32020-04-23 15:20:5643 /** @type {?function():void} */
Blink Reformat4c46d092018-04-07 15:32:3744 this._hidePopoverCallback = null;
45 this._container = container;
46 this._showTimeout = 0;
47 this._hideTimeout = 0;
48 /** @type {?number} */
49 this._hidePopoverTimer = null;
50 /** @type {?number} */
51 this._showPopoverTimer = null;
52 this._boundMouseDown = this._mouseDown.bind(this);
53 this._boundMouseMove = this._mouseMove.bind(this);
54 this._boundMouseOut = this._mouseOut.bind(this);
55 this._container.addEventListener('mousedown', this._boundMouseDown, false);
56 this._container.addEventListener('mousemove', this._boundMouseMove, false);
57 this._container.addEventListener('mouseout', this._boundMouseOut, false);
58 this.setTimeout(1000);
59 }
60
61 /**
62 * @param {number} showTimeout
63 * @param {number=} hideTimeout
64 */
65 setTimeout(showTimeout, hideTimeout) {
66 this._showTimeout = showTimeout;
67 this._hideTimeout = typeof hideTimeout === 'number' ? hideTimeout : showTimeout / 2;
68 }
69
70 /**
71 * @param {boolean} hasPadding
72 */
73 setHasPadding(hasPadding) {
74 this._hasPadding = hasPadding;
75 }
76
77 /**
78 * @param {boolean} disableOnClick
79 */
80 setDisableOnClick(disableOnClick) {
81 this._disableOnClick = disableOnClick;
82 }
83
84 /**
Simon Zünd5bdf7882020-08-20 07:28:4485 * @param {!Event} ev
Blink Reformat4c46d092018-04-07 15:32:3786 * @return {boolean}
87 */
Simon Zünd5bdf7882020-08-20 07:28:4488 _eventInScheduledContent(ev) {
89 const event = /** @type {!MouseEvent} */ (ev);
Blink Reformat4c46d092018-04-07 15:32:3790 return this._scheduledRequest ? this._scheduledRequest.box.contains(event.clientX, event.clientY) : false;
91 }
92
93 /**
94 * @param {!Event} event
95 */
96 _mouseDown(event) {
97 if (this._disableOnClick) {
98 this.hidePopover();
99 return;
100 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34101 if (this._eventInScheduledContent(event)) {
Blink Reformat4c46d092018-04-07 15:32:37102 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34103 }
Blink Reformat4c46d092018-04-07 15:32:37104
105 this._startHidePopoverTimer(0);
106 this._stopShowPopoverTimer();
107 this._startShowPopoverTimer(/** @type {!MouseEvent} */ (event), 0);
108 }
109
110 /**
Simon Zünd5bdf7882020-08-20 07:28:44111 * @param {!Event} ev
Blink Reformat4c46d092018-04-07 15:32:37112 */
Simon Zünd5bdf7882020-08-20 07:28:44113 _mouseMove(ev) {
114 const event = /** @type {!MouseEvent} */ (ev);
Blink Reformat4c46d092018-04-07 15:32:37115 // Pretend that nothing has happened.
Tim van der Lippe1d6e57a2019-09-30 11:55:34116 if (this._eventInScheduledContent(event)) {
Blink Reformat4c46d092018-04-07 15:32:37117 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34118 }
Blink Reformat4c46d092018-04-07 15:32:37119
120 this._startHidePopoverTimer(this._hideTimeout);
121 this._stopShowPopoverTimer();
Tim van der Lippe1d6e57a2019-09-30 11:55:34122 if (event.which && this._disableOnClick) {
Blink Reformat4c46d092018-04-07 15:32:37123 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34124 }
Simon Zünd5bdf7882020-08-20 07:28:44125 this._startShowPopoverTimer(event, this.isPopoverVisible() ? this._showTimeout * 0.6 : this._showTimeout);
Blink Reformat4c46d092018-04-07 15:32:37126 }
127
128 /**
129 * @param {!Event} event
130 */
131 _popoverMouseMove(event) {
132 this._stopHidePopoverTimer();
133 }
134
135 /**
Paul Lewis9950e182019-12-16 16:06:07136 * @param {!GlassPane} popover
Simon Zünd5bdf7882020-08-20 07:28:44137 * @param {!Event} ev
Blink Reformat4c46d092018-04-07 15:32:37138 */
Simon Zünd5bdf7882020-08-20 07:28:44139 _popoverMouseOut(popover, ev) {
140 const event = /** @type {!MouseEvent} */ (ev);
Tim van der Lippe1d6e57a2019-09-30 11:55:34141 if (!popover.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37142 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34143 }
Simon Zünd5bdf7882020-08-20 07:28:44144 const node = /** @type {?Node} */ (event.relatedTarget);
145 if (node && !node.isSelfOrDescendant(popover.contentElement)) {
Blink Reformat4c46d092018-04-07 15:32:37146 this._startHidePopoverTimer(this._hideTimeout);
Tim van der Lippe1d6e57a2019-09-30 11:55:34147 }
Blink Reformat4c46d092018-04-07 15:32:37148 }
149
150 /**
151 * @param {!Event} event
152 */
153 _mouseOut(event) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34154 if (!this.isPopoverVisible()) {
Blink Reformat4c46d092018-04-07 15:32:37155 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34156 }
157 if (!this._eventInScheduledContent(event)) {
Blink Reformat4c46d092018-04-07 15:32:37158 this._startHidePopoverTimer(this._hideTimeout);
Tim van der Lippe1d6e57a2019-09-30 11:55:34159 }
Blink Reformat4c46d092018-04-07 15:32:37160 }
161
162 /**
163 * @param {number} timeout
164 */
165 _startHidePopoverTimer(timeout) {
166 // User has |timeout| ms to reach the popup.
Tim van der Lippe1d6e57a2019-09-30 11:55:34167 if (!this._hidePopoverCallback || this._hidePopoverTimer) {
Blink Reformat4c46d092018-04-07 15:32:37168 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34169 }
Blink Reformat4c46d092018-04-07 15:32:37170
Tim van der Lippe4dcae8f2020-10-01 11:12:30171 this._hidePopoverTimer = window.setTimeout(() => {
Blink Reformat4c46d092018-04-07 15:32:37172 this._hidePopover();
173 this._hidePopoverTimer = null;
174 }, timeout);
175 }
176
177 /**
178 * @param {!MouseEvent} event
179 * @param {number} timeout
180 */
181 _startShowPopoverTimer(event, timeout) {
182 this._scheduledRequest = this._getRequest.call(null, event);
Tim van der Lippe1d6e57a2019-09-30 11:55:34183 if (!this._scheduledRequest) {
Blink Reformat4c46d092018-04-07 15:32:37184 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34185 }
Blink Reformat4c46d092018-04-07 15:32:37186
Tim van der Lippe4dcae8f2020-10-01 11:12:30187 this._showPopoverTimer = window.setTimeout(() => {
Blink Reformat4c46d092018-04-07 15:32:37188 this._showPopoverTimer = null;
189 this._stopHidePopoverTimer();
190 this._hidePopover();
Simon Zünd5bdf7882020-08-20 07:28:44191 const document = /** @type {!Document} */ (/** @type {!Node} */ (event.target).ownerDocument);
192 this._showPopover(document);
Blink Reformat4c46d092018-04-07 15:32:37193 }, timeout);
194 }
195
196 _stopShowPopoverTimer() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34197 if (!this._showPopoverTimer) {
Blink Reformat4c46d092018-04-07 15:32:37198 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34199 }
Blink Reformat4c46d092018-04-07 15:32:37200 clearTimeout(this._showPopoverTimer);
201 this._showPopoverTimer = null;
202 }
203
204 /**
205 * @return {boolean}
206 */
207 isPopoverVisible() {
208 return !!this._hidePopoverCallback;
209 }
210
211 hidePopover() {
212 this._stopShowPopoverTimer();
213 this._hidePopover();
214 }
215
216 _hidePopover() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34217 if (!this._hidePopoverCallback) {
Blink Reformat4c46d092018-04-07 15:32:37218 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34219 }
Blink Reformat4c46d092018-04-07 15:32:37220 this._hidePopoverCallback.call(null);
221 this._hidePopoverCallback = null;
222 }
223
224 /**
225 * @param {!Document} document
226 */
227 _showPopover(document) {
Paul Lewis9950e182019-12-16 16:06:07228 const popover = new GlassPane();
Jack Franklin71519f82020-11-03 12:08:59229 popover.registerRequiredCSS('ui/popover.css', {enableLegacyPatching: true});
Paul Lewis9950e182019-12-16 16:06:07230 popover.setSizeBehavior(SizeBehavior.MeasureContent);
231 popover.setMarginBehavior(MarginBehavior.Arrow);
Blink Reformat4c46d092018-04-07 15:32:37232 const request = this._scheduledRequest;
Simon Zünd5bdf7882020-08-20 07:28:44233 if (!request) {
234 return;
235 }
Blink Reformat4c46d092018-04-07 15:32:37236 request.show.call(null, popover).then(success => {
Tim van der Lippe1d6e57a2019-09-30 11:55:34237 if (!success) {
Blink Reformat4c46d092018-04-07 15:32:37238 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34239 }
Blink Reformat4c46d092018-04-07 15:32:37240
241 if (this._scheduledRequest !== request) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34242 if (request.hide) {
Blink Reformat4c46d092018-04-07 15:32:37243 request.hide.call(null);
Tim van der Lippe1d6e57a2019-09-30 11:55:34244 }
Blink Reformat4c46d092018-04-07 15:32:37245 return;
246 }
247
248 // This should not happen, but we hide previous popover to be on the safe side.
Simon Zünd5bdf7882020-08-20 07:28:44249 if (popoverHelperInstance) {
Blink Reformat4c46d092018-04-07 15:32:37250 console.error('One popover is already visible');
Simon Zünd5bdf7882020-08-20 07:28:44251 popoverHelperInstance.hidePopover();
Blink Reformat4c46d092018-04-07 15:32:37252 }
Simon Zünd5bdf7882020-08-20 07:28:44253 popoverHelperInstance = this;
Blink Reformat4c46d092018-04-07 15:32:37254
255 popover.contentElement.classList.toggle('has-padding', this._hasPadding);
256 popover.contentElement.addEventListener('mousemove', this._popoverMouseMove.bind(this), true);
257 popover.contentElement.addEventListener('mouseout', this._popoverMouseOut.bind(this, popover), true);
258 popover.setContentAnchorBox(request.box);
259 popover.show(document);
260
261 this._hidePopoverCallback = () => {
Tim van der Lippe1d6e57a2019-09-30 11:55:34262 if (request.hide) {
Blink Reformat4c46d092018-04-07 15:32:37263 request.hide.call(null);
Tim van der Lippe1d6e57a2019-09-30 11:55:34264 }
Blink Reformat4c46d092018-04-07 15:32:37265 popover.hide();
Simon Zünd5bdf7882020-08-20 07:28:44266 popoverHelperInstance = null;
Blink Reformat4c46d092018-04-07 15:32:37267 };
268 });
269 }
270
271 _stopHidePopoverTimer() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34272 if (!this._hidePopoverTimer) {
Blink Reformat4c46d092018-04-07 15:32:37273 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34274 }
Blink Reformat4c46d092018-04-07 15:32:37275 clearTimeout(this._hidePopoverTimer);
276 this._hidePopoverTimer = null;
277
278 // We know that we reached the popup, but we might have moved over other elements.
279 // Discard pending command.
280 this._stopShowPopoverTimer();
281 }
282
283 dispose() {
284 this._container.removeEventListener('mousedown', this._boundMouseDown, false);
285 this._container.removeEventListener('mousemove', this._boundMouseMove, false);
286 this._container.removeEventListener('mouseout', this._boundMouseOut, false);
287 }
Tim van der Lippe0830b3d2019-10-03 13:20:07288}
Tim van der Lippeaa76aa22020-02-14 14:38:24289
Simon Zünd5bdf7882020-08-20 07:28:44290/** @type {?PopoverHelper} */
291let popoverHelperInstance = null;
292
Tim van der Lippe4dcae8f2020-10-01 11:12:30293/** @typedef {{box: !AnchorBox, show:(function(!GlassPane):!Promise<boolean>), hide:((function():void)|undefined)}} */
Simon Zünd5bdf7882020-08-20 07:28:44294// @ts-ignore typedef
Tim van der Lippeaa76aa22020-02-14 14:38:24295export let PopoverRequest;