OLD | NEW |
---|---|
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @typedef {{ | 6 * @typedef {{ |
7 * top: number, | 7 * top: number, |
8 * left: number, | 8 * left: number, |
9 * width: (number| undefined), | 9 * width: (number|undefined), |
10 * height: (number| undefined), | 10 * height: (number|undefined), |
11 * anchorAlignmentX: (number| undefined), | 11 * anchorAlignmentX: (number|undefined), |
12 * anchorAlignmentY: (number| undefined), | 12 * anchorAlignmentY: (number|undefined), |
13 * minX: (number| undefined), | 13 * minX: (number|undefined), |
14 * minY: (number| undefined), | 14 * minY: (number|undefined), |
15 * maxX: (number| undefined), | 15 * maxX: (number|undefined), |
16 * maxY: (number| undefined), | 16 * maxY: (number|undefined), |
17 * }} | 17 * }} |
18 */ | 18 */ |
19 var ShowConfig; | 19 var ShowConfig; |
20 | 20 |
21 /** | 21 /** |
22 * @enum {number} | 22 * @enum {number} |
23 * @const | 23 * @const |
24 */ | 24 */ |
25 var AnchorAlignment = { | 25 var AnchorAlignment = { |
26 BEFORE_START: -2, | 26 BEFORE_START: -2, |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
66 break; | 66 break; |
67 } | 67 } |
68 | 68 |
69 if (startPoint + length > max) | 69 if (startPoint + length > max) |
70 startPoint = end - length; | 70 startPoint = end - length; |
71 if (startPoint < min) | 71 if (startPoint < min) |
72 startPoint = start; | 72 startPoint = start; |
73 return startPoint; | 73 return startPoint; |
74 } | 74 } |
75 | 75 |
76 | |
77 /** | 76 /** |
78 * @private | 77 * @private |
79 * @return {!ShowConfig} | 78 * @return {!ShowConfig} |
80 */ | 79 */ |
81 function getDefaultShowConfig() { | 80 function getDefaultShowConfig() { |
82 return { | 81 return { |
83 top: 0, | 82 top: 0, |
84 left: 0, | 83 left: 0, |
85 height: 0, | 84 height: 0, |
86 width: 0, | 85 width: 0, |
87 anchorAlignmentX: AnchorAlignment.AFTER_START, | 86 anchorAlignmentX: AnchorAlignment.AFTER_START, |
88 anchorAlignmentY: AnchorAlignment.AFTER_START, | 87 anchorAlignmentY: AnchorAlignment.AFTER_START, |
89 minX: 0, | 88 minX: document.body.scrollLeft, |
90 minY: 0, | 89 minY: document.body.scrollTop, |
91 maxX: window.innerWidth, | 90 maxX: document.body.scrollLeft + window.innerWidth, |
92 maxY: window.innerHeight, | 91 maxY: document.body.scrollTop + window.innerHeight, |
93 }; | 92 }; |
94 } | 93 } |
95 | 94 |
96 Polymer({ | 95 Polymer({ |
97 is: 'cr-action-menu', | 96 is: 'cr-action-menu', |
98 extends: 'dialog', | 97 extends: 'dialog', |
99 | 98 |
100 /** | 99 /** |
101 * List of all options in this action menu. | 100 * List of all options in this action menu. |
102 * @private {?NodeList<!Element>} | 101 * @private {?NodeList<!Element>} |
(...skipping 142 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
245 HTMLDialogElement.prototype.close.call(this); | 244 HTMLDialogElement.prototype.close.call(this); |
246 if (this.anchorElement_) { | 245 if (this.anchorElement_) { |
247 cr.ui.focusWithoutInk(assert(this.anchorElement_)); | 246 cr.ui.focusWithoutInk(assert(this.anchorElement_)); |
248 this.anchorElement_ = null; | 247 this.anchorElement_ = null; |
249 } | 248 } |
250 }, | 249 }, |
251 | 250 |
252 /** | 251 /** |
253 * Shows the menu anchored to the given element. | 252 * Shows the menu anchored to the given element. |
254 * @param {!Element} anchorElement | 253 * @param {!Element} anchorElement |
254 * @param {ShowConfig=} opt_config | |
255 */ | 255 */ |
256 showAt: function(anchorElement) { | 256 showAt: function(anchorElement, opt_config) { |
257 this.anchorElement_ = anchorElement; | 257 this.anchorElement_ = anchorElement; |
258 // Scroll the anchor element into view so that the bounding rect will be | |
259 // accurate for where the menu should be shown. | |
258 this.anchorElement_.scrollIntoViewIfNeeded(); | 260 this.anchorElement_.scrollIntoViewIfNeeded(); |
261 | |
262 // Save the scroll position that ensures the anchor element is onscreen. | |
263 var scrollLeft = document.body.scrollLeft; | |
264 var scrollTop = document.body.scrollTop; | |
265 | |
266 // Reset position so that layout isn't affected by the previous position, | |
267 // and so that the dialog is positioned at the top-start corner of the | |
268 // document. | |
269 this.resetStyle_(); | |
dpapad
2017/06/22 21:18:37
After patching this CL locally I discovered an und
calamity
2017/06/28 04:00:47
Changed to use document.scrollingElement instead o
| |
270 | |
271 // Show the dialog which will focus the top-start of the body. This makes | |
272 // the client rect calculation relative to the top-start of the body. | |
273 this.showModal(); | |
274 | |
259 var rect = this.anchorElement_.getBoundingClientRect(); | 275 var rect = this.anchorElement_.getBoundingClientRect(); |
260 this.showAtPosition({ | 276 this.positionDialog_(/** @type {ShowConfig} */ (Object.assign( |
261 top: rect.top, | 277 { |
262 left: rect.left, | 278 top: rect.top, |
263 height: rect.height, | 279 left: rect.left, |
264 width: rect.width, | 280 height: rect.height, |
265 // Default to anchoring towards the left. | 281 width: rect.width, |
266 anchorAlignmentX: AnchorAlignment.BEFORE_END, | 282 // Default to anchoring towards the left. |
267 }); | 283 anchorAlignmentX: AnchorAlignment.BEFORE_END, |
284 minX: scrollLeft, | |
285 minY: scrollTop, | |
286 maxX: scrollLeft + window.innerWidth, | |
287 maxY: scrollTop + window.innerHeight, | |
288 }, | |
289 opt_config))); | |
290 | |
291 // Restore the scroll position. | |
292 document.body.scrollTop = scrollTop; | |
293 document.body.scrollLeft = scrollLeft; | |
294 | |
295 this.addCloseListeners_(); | |
268 }, | 296 }, |
269 | 297 |
270 /** | 298 /** |
271 * Shows the menu anchored to the given box. The anchor alignment is | 299 * Shows the menu anchored to the given box. The anchor alignment is |
272 * specified as an X and Y alignment which represents a point in the anchor | 300 * specified as an X and Y alignment which represents a point in the anchor |
273 * where the menu will align to, which can have the menu either before or | 301 * where the menu will align to, which can have the menu either before or |
274 * after the given point in each axis. Center alignment places the center of | 302 * after the given point in each axis. Center alignment places the center of |
275 * the menu in line with the center of the anchor. | 303 * the menu in line with the center of the anchor. |
276 * | 304 * |
277 * y-start | 305 * y-start |
278 * _____________ | 306 * _____________ |
279 * | | | 307 * | | |
280 * | | | 308 * | | |
281 * | CENTER | | 309 * | CENTER | |
282 * x-start | x | x-end | 310 * x-start | x | x-end |
283 * | | | 311 * | | |
284 * |anchor box | | 312 * |anchor box | |
285 * |___________| | 313 * |___________| |
286 * | 314 * |
287 * y-end | 315 * y-end |
288 * | 316 * |
289 * For example, aligning the menu to the inside of the top-right edge of | 317 * For example, aligning the menu to the inside of the top-right edge of |
290 * the anchor, extending towards the bottom-left would use a alignment of | 318 * the anchor, extending towards the bottom-left would use a alignment of |
291 * (BEFORE_END, AFTER_START), whereas centering the menu below the bottom | 319 * (BEFORE_END, AFTER_START), whereas centering the menu below the bottom |
292 * edge of the anchor would use (CENTER, AFTER_END). | 320 * edge of the anchor would use (CENTER, AFTER_END). |
293 * | 321 * |
294 * @param {!ShowConfig} config | 322 * @param {!ShowConfig} config |
295 */ | 323 */ |
296 showAtPosition: function(config) { | 324 showAtPosition: function(config) { |
325 this.resetStyle_(); | |
326 this.showModal(); | |
327 this.positionDialog_(config); | |
328 this.addCloseListeners_(); | |
329 }, | |
330 | |
331 /** @private */ | |
332 resetStyle_: function() { | |
333 this.style.left = ''; | |
334 this.style.right = ''; | |
335 this.style.top = '0'; | |
336 }, | |
337 | |
338 /** | |
339 * @param {!ShowConfig} config | |
340 * @private | |
341 */ | |
342 positionDialog_: function(config) { | |
297 var c = Object.assign(getDefaultShowConfig(), config); | 343 var c = Object.assign(getDefaultShowConfig(), config); |
298 | 344 |
299 var top = c.top; | 345 var top = c.top; |
300 var left = c.left; | 346 var left = c.left; |
301 var bottom = top + c.height; | 347 var bottom = top + c.height; |
302 var right = left + c.width; | 348 var right = left + c.width; |
303 | 349 |
304 this.boundClose_ = this.boundClose_ || function() { | |
305 if (this.open) | |
306 this.close(); | |
307 }.bind(this); | |
308 window.addEventListener('resize', this.boundClose_); | |
309 window.addEventListener('popstate', this.boundClose_); | |
310 | |
311 // Reset position to prevent previous values from affecting layout. | |
312 this.style.left = ''; | |
313 this.style.right = ''; | |
314 this.style.top = ''; | |
315 | |
316 this.showModal(); | |
317 | |
318 // Flip the X anchor in RTL. | 350 // Flip the X anchor in RTL. |
319 var rtl = getComputedStyle(this).direction == 'rtl'; | 351 var rtl = getComputedStyle(this).direction == 'rtl'; |
320 if (rtl) | 352 if (rtl) |
321 c.anchorAlignmentX *= -1; | 353 c.anchorAlignmentX *= -1; |
322 | 354 |
323 var menuLeft = getStartPointWithAnchor( | 355 var menuLeft = getStartPointWithAnchor( |
324 left, right, this.offsetWidth, c.anchorAlignmentX, c.minX, c.maxX); | 356 left, right, this.offsetWidth, c.anchorAlignmentX, c.minX, c.maxX); |
325 | 357 |
326 if (rtl) { | 358 if (rtl) { |
327 var menuRight = window.innerWidth - menuLeft - this.offsetWidth; | 359 var menuRight = document.body.scrollWidth - menuLeft - this.offsetWidth; |
328 this.style.right = menuRight + 'px'; | 360 this.style.right = menuRight + 'px'; |
329 } else { | 361 } else { |
330 this.style.left = menuLeft + 'px'; | 362 this.style.left = menuLeft + 'px'; |
331 } | 363 } |
332 | 364 |
333 var menuTop = getStartPointWithAnchor( | 365 var menuTop = getStartPointWithAnchor( |
334 top, bottom, this.offsetHeight, c.anchorAlignmentY, c.minY, c.maxY); | 366 top, bottom, this.offsetHeight, c.anchorAlignmentY, c.minY, c.maxY); |
335 this.style.top = menuTop + 'px'; | 367 this.style.top = menuTop + 'px'; |
336 }, | 368 }, |
369 | |
370 /** | |
371 * @private | |
372 */ | |
373 addCloseListeners_: function() { | |
374 this.boundClose_ = this.boundClose_ || function() { | |
375 if (this.open) | |
376 this.close(); | |
377 }.bind(this); | |
378 window.addEventListener('resize', this.boundClose_); | |
379 window.addEventListener('popstate', this.boundClose_); | |
380 }, | |
337 }); | 381 }); |
338 })(); | 382 })(); |
OLD | NEW |