blob: 2035f1a76631b8726c3d315f8111561c789f1de6 [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
Tim van der Lippeee97fa32020-04-23 15:20:5631// @ts-nocheck
32// TODO(crbug.com/1011811): Enable TypeScript compiler checks
33
Paul Lewis9950e182019-12-16 16:06:0734import {GlassPane, MarginBehavior, SizeBehavior} from './GlassPane.js';
35
36export class PopoverHelper {
Blink Reformat4c46d092018-04-07 15:32:3737 /**
38 * @param {!Element} container
Tim van der Lippeaa76aa22020-02-14 14:38:2439 * @param {function(!MouseEvent):?PopoverRequest} getRequest
Blink Reformat4c46d092018-04-07 15:32:3740 */
41 constructor(container, getRequest) {
42 this._disableOnClick = false;
43 this._hasPadding = false;
44 this._getRequest = getRequest;
45 this._scheduledRequest = null;
Tim van der Lippeee97fa32020-04-23 15:20:5646 /** @type {?function():void} */
Blink Reformat4c46d092018-04-07 15:32:3747 this._hidePopoverCallback = null;
48 this._container = container;
49 this._showTimeout = 0;
50 this._hideTimeout = 0;
51 /** @type {?number} */
52 this._hidePopoverTimer = null;
53 /** @type {?number} */
54 this._showPopoverTimer = null;
55 this._boundMouseDown = this._mouseDown.bind(this);
56 this._boundMouseMove = this._mouseMove.bind(this);
57 this._boundMouseOut = this._mouseOut.bind(this);
58 this._container.addEventListener('mousedown', this._boundMouseDown, false);
59 this._container.addEventListener('mousemove', this._boundMouseMove, false);
60 this._container.addEventListener('mouseout', this._boundMouseOut, false);
61 this.setTimeout(1000);
62 }
63
64 /**
65 * @param {number} showTimeout
66 * @param {number=} hideTimeout
67 */
68 setTimeout(showTimeout, hideTimeout) {
69 this._showTimeout = showTimeout;
70 this._hideTimeout = typeof hideTimeout === 'number' ? hideTimeout : showTimeout / 2;
71 }
72
73 /**
74 * @param {boolean} hasPadding
75 */
76 setHasPadding(hasPadding) {
77 this._hasPadding = hasPadding;
78 }
79
80 /**
81 * @param {boolean} disableOnClick
82 */
83 setDisableOnClick(disableOnClick) {
84 this._disableOnClick = disableOnClick;
85 }
86
87 /**
88 * @param {!Event} event
89 * @return {boolean}
90 */
91 _eventInScheduledContent(event) {
92 return this._scheduledRequest ? this._scheduledRequest.box.contains(event.clientX, event.clientY) : false;
93 }
94
95 /**
96 * @param {!Event} event
97 */
98 _mouseDown(event) {
99 if (this._disableOnClick) {
100 this.hidePopover();
101 return;
102 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34103 if (this._eventInScheduledContent(event)) {
Blink Reformat4c46d092018-04-07 15:32:37104 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34105 }
Blink Reformat4c46d092018-04-07 15:32:37106
107 this._startHidePopoverTimer(0);
108 this._stopShowPopoverTimer();
109 this._startShowPopoverTimer(/** @type {!MouseEvent} */ (event), 0);
110 }
111
112 /**
113 * @param {!Event} event
114 */
115 _mouseMove(event) {
116 // Pretend that nothing has happened.
Tim van der Lippe1d6e57a2019-09-30 11:55:34117 if (this._eventInScheduledContent(event)) {
Blink Reformat4c46d092018-04-07 15:32:37118 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34119 }
Blink Reformat4c46d092018-04-07 15:32:37120
121 this._startHidePopoverTimer(this._hideTimeout);
122 this._stopShowPopoverTimer();
Tim van der Lippe1d6e57a2019-09-30 11:55:34123 if (event.which && this._disableOnClick) {
Blink Reformat4c46d092018-04-07 15:32:37124 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34125 }
Blink Reformat4c46d092018-04-07 15:32:37126 this._startShowPopoverTimer(
127 /** @type {!MouseEvent} */ (event), this.isPopoverVisible() ? this._showTimeout * 0.6 : this._showTimeout);
128 }
129
130 /**
131 * @param {!Event} event
132 */
133 _popoverMouseMove(event) {
134 this._stopHidePopoverTimer();
135 }
136
137 /**
Paul Lewis9950e182019-12-16 16:06:07138 * @param {!GlassPane} popover
Blink Reformat4c46d092018-04-07 15:32:37139 * @param {!Event} event
140 */
141 _popoverMouseOut(popover, event) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34142 if (!popover.isShowing()) {
Blink Reformat4c46d092018-04-07 15:32:37143 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34144 }
145 if (event.relatedTarget && !event.relatedTarget.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
171 this._hidePopoverTimer = setTimeout(() => {
172 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
187 this._showPopoverTimer = setTimeout(() => {
188 this._showPopoverTimer = null;
189 this._stopHidePopoverTimer();
190 this._hidePopover();
191 this._showPopover(event.target.ownerDocument);
192 }, timeout);
193 }
194
195 _stopShowPopoverTimer() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34196 if (!this._showPopoverTimer) {
Blink Reformat4c46d092018-04-07 15:32:37197 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34198 }
Blink Reformat4c46d092018-04-07 15:32:37199 clearTimeout(this._showPopoverTimer);
200 this._showPopoverTimer = null;
201 }
202
203 /**
204 * @return {boolean}
205 */
206 isPopoverVisible() {
207 return !!this._hidePopoverCallback;
208 }
209
210 hidePopover() {
211 this._stopShowPopoverTimer();
212 this._hidePopover();
213 }
214
215 _hidePopover() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34216 if (!this._hidePopoverCallback) {
Blink Reformat4c46d092018-04-07 15:32:37217 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34218 }
Blink Reformat4c46d092018-04-07 15:32:37219 this._hidePopoverCallback.call(null);
220 this._hidePopoverCallback = null;
221 }
222
223 /**
224 * @param {!Document} document
225 */
226 _showPopover(document) {
Paul Lewis9950e182019-12-16 16:06:07227 const popover = new GlassPane();
Blink Reformat4c46d092018-04-07 15:32:37228 popover.registerRequiredCSS('ui/popover.css');
Paul Lewis9950e182019-12-16 16:06:07229 popover.setSizeBehavior(SizeBehavior.MeasureContent);
230 popover.setMarginBehavior(MarginBehavior.Arrow);
Blink Reformat4c46d092018-04-07 15:32:37231 const request = this._scheduledRequest;
232 request.show.call(null, popover).then(success => {
Tim van der Lippe1d6e57a2019-09-30 11:55:34233 if (!success) {
Blink Reformat4c46d092018-04-07 15:32:37234 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34235 }
Blink Reformat4c46d092018-04-07 15:32:37236
237 if (this._scheduledRequest !== request) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34238 if (request.hide) {
Blink Reformat4c46d092018-04-07 15:32:37239 request.hide.call(null);
Tim van der Lippe1d6e57a2019-09-30 11:55:34240 }
Blink Reformat4c46d092018-04-07 15:32:37241 return;
242 }
243
244 // This should not happen, but we hide previous popover to be on the safe side.
Tim van der Lippe0830b3d2019-10-03 13:20:07245 if (PopoverHelper._popoverHelper) {
Blink Reformat4c46d092018-04-07 15:32:37246 console.error('One popover is already visible');
Tim van der Lippe0830b3d2019-10-03 13:20:07247 PopoverHelper._popoverHelper.hidePopover();
Blink Reformat4c46d092018-04-07 15:32:37248 }
Tim van der Lippe0830b3d2019-10-03 13:20:07249 PopoverHelper._popoverHelper = this;
Blink Reformat4c46d092018-04-07 15:32:37250
251 popover.contentElement.classList.toggle('has-padding', this._hasPadding);
252 popover.contentElement.addEventListener('mousemove', this._popoverMouseMove.bind(this), true);
253 popover.contentElement.addEventListener('mouseout', this._popoverMouseOut.bind(this, popover), true);
254 popover.setContentAnchorBox(request.box);
255 popover.show(document);
256
257 this._hidePopoverCallback = () => {
Tim van der Lippe1d6e57a2019-09-30 11:55:34258 if (request.hide) {
Blink Reformat4c46d092018-04-07 15:32:37259 request.hide.call(null);
Tim van der Lippe1d6e57a2019-09-30 11:55:34260 }
Blink Reformat4c46d092018-04-07 15:32:37261 popover.hide();
Tim van der Lippe0830b3d2019-10-03 13:20:07262 delete PopoverHelper._popoverHelper;
Blink Reformat4c46d092018-04-07 15:32:37263 };
264 });
265 }
266
267 _stopHidePopoverTimer() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34268 if (!this._hidePopoverTimer) {
Blink Reformat4c46d092018-04-07 15:32:37269 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34270 }
Blink Reformat4c46d092018-04-07 15:32:37271 clearTimeout(this._hidePopoverTimer);
272 this._hidePopoverTimer = null;
273
274 // We know that we reached the popup, but we might have moved over other elements.
275 // Discard pending command.
276 this._stopShowPopoverTimer();
277 }
278
279 dispose() {
280 this._container.removeEventListener('mousedown', this._boundMouseDown, false);
281 this._container.removeEventListener('mousemove', this._boundMouseMove, false);
282 this._container.removeEventListener('mouseout', this._boundMouseOut, false);
283 }
Tim van der Lippe0830b3d2019-10-03 13:20:07284}
Tim van der Lippeaa76aa22020-02-14 14:38:24285
286/** @typedef {{box: !AnchorBox, show:(function(!GlassPane):!Promise<boolean>), hide:(function()|undefined)}} */
287export let PopoverRequest;