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), |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
82 */ | 82 */ |
83 function getDefaultShowConfig() { | 83 function getDefaultShowConfig() { |
84 var doc = document.scrollingElement; | 84 var doc = document.scrollingElement; |
85 return { | 85 return { |
86 top: 0, | 86 top: 0, |
87 left: 0, | 87 left: 0, |
88 height: 0, | 88 height: 0, |
89 width: 0, | 89 width: 0, |
90 anchorAlignmentX: AnchorAlignment.AFTER_START, | 90 anchorAlignmentX: AnchorAlignment.AFTER_START, |
91 anchorAlignmentY: AnchorAlignment.AFTER_START, | 91 anchorAlignmentY: AnchorAlignment.AFTER_START, |
92 minX: 0, | 92 minX: doc.scrollLeft, |
93 minY: 0, | 93 minY: doc.scrollTop, |
94 maxX: 0, | 94 maxX: doc.scrollLeft + window.innerWidth, |
95 maxY: 0, | 95 maxY: doc.scrollTop + window.innerHeight, |
96 }; | 96 }; |
97 } | 97 } |
98 | 98 |
99 Polymer({ | 99 Polymer({ |
100 is: 'cr-action-menu', | 100 is: 'cr-action-menu', |
101 extends: 'dialog', | 101 extends: 'dialog', |
102 | 102 |
103 /** | 103 /** |
104 * The element which the action menu will be anchored to. Also the element | 104 * The element which the action menu will be anchored to. Also the element |
105 * where focus will be returned after the menu is closed. Only populated if | 105 * where focus will be returned after the menu is closed. Only populated if |
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
246 * Shows the menu anchored to the given element. | 246 * Shows the menu anchored to the given element. |
247 * @param {!Element} anchorElement | 247 * @param {!Element} anchorElement |
248 * @param {ShowConfig=} opt_config | 248 * @param {ShowConfig=} opt_config |
249 */ | 249 */ |
250 showAt: function(anchorElement, opt_config) { | 250 showAt: function(anchorElement, opt_config) { |
251 this.anchorElement_ = anchorElement; | 251 this.anchorElement_ = anchorElement; |
252 // Scroll the anchor element into view so that the bounding rect will be | 252 // Scroll the anchor element into view so that the bounding rect will be |
253 // accurate for where the menu should be shown. | 253 // accurate for where the menu should be shown. |
254 this.anchorElement_.scrollIntoViewIfNeeded(); | 254 this.anchorElement_.scrollIntoViewIfNeeded(); |
255 | 255 |
| 256 // Save the scroll position that ensures the anchor element is onscreen. |
| 257 var doc = document.scrollingElement; |
| 258 var scrollLeft = doc.scrollLeft; |
| 259 var scrollTop = doc.scrollTop; |
| 260 |
| 261 // Reset position so that layout isn't affected by the previous position, |
| 262 // and so that the dialog is positioned at the top-start corner of the |
| 263 // document. |
| 264 this.resetStyle_(); |
| 265 |
| 266 // Show the dialog which will focus the top-start of the body. This makes |
| 267 // the client rect calculation relative to the top-start of the body. |
| 268 this.showModal(); |
| 269 |
256 var rect = this.anchorElement_.getBoundingClientRect(); | 270 var rect = this.anchorElement_.getBoundingClientRect(); |
257 this.showAtPosition(/** @type {ShowConfig} */ (Object.assign( | 271 this.positionDialog_(/** @type {ShowConfig} */ (Object.assign( |
258 { | 272 { |
259 top: rect.top, | 273 top: rect.top, |
260 left: rect.left, | 274 left: rect.left, |
261 height: rect.height, | 275 height: rect.height, |
262 width: rect.width, | 276 width: rect.width, |
263 // Default to anchoring towards the left. | 277 // Default to anchoring towards the left. |
264 anchorAlignmentX: AnchorAlignment.BEFORE_END, | 278 anchorAlignmentX: AnchorAlignment.BEFORE_END, |
| 279 minX: scrollLeft, |
| 280 minY: scrollTop, |
| 281 maxX: scrollLeft + window.innerWidth, |
| 282 maxY: scrollTop + window.innerHeight, |
265 }, | 283 }, |
266 opt_config))); | 284 opt_config))); |
| 285 |
| 286 // Restore the scroll position. |
| 287 doc.scrollTop = scrollTop; |
| 288 doc.scrollLeft = scrollLeft; |
| 289 |
| 290 this.addCloseListeners_(); |
267 }, | 291 }, |
268 | 292 |
269 /** | 293 /** |
270 * Shows the menu anchored to the given box. The anchor alignment is | 294 * Shows the menu anchored to the given box. The anchor alignment is |
271 * specified as an X and Y alignment which represents a point in the anchor | 295 * specified as an X and Y alignment which represents a point in the anchor |
272 * where the menu will align to, which can have the menu either before or | 296 * where the menu will align to, which can have the menu either before or |
273 * after the given point in each axis. Center alignment places the center of | 297 * after the given point in each axis. Center alignment places the center of |
274 * the menu in line with the center of the anchor. Coordinates are relative to | 298 * the menu in line with the center of the anchor. |
275 * the top-left of the viewport. | |
276 * | 299 * |
277 * y-start | 300 * y-start |
278 * _____________ | 301 * _____________ |
279 * | | | 302 * | | |
280 * | | | 303 * | | |
281 * | CENTER | | 304 * | CENTER | |
282 * x-start | x | x-end | 305 * x-start | x | x-end |
283 * | | | 306 * | | |
284 * |anchor box | | 307 * |anchor box | |
285 * |___________| | 308 * |___________| |
286 * | 309 * |
287 * y-end | 310 * y-end |
288 * | 311 * |
289 * For example, aligning the menu to the inside of the top-right edge of | 312 * 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 | 313 * the anchor, extending towards the bottom-left would use a alignment of |
291 * (BEFORE_END, AFTER_START), whereas centering the menu below the bottom | 314 * (BEFORE_END, AFTER_START), whereas centering the menu below the bottom |
292 * edge of the anchor would use (CENTER, AFTER_END). | 315 * edge of the anchor would use (CENTER, AFTER_END). |
293 * | 316 * |
294 * @param {!ShowConfig} config | 317 * @param {!ShowConfig} config |
295 */ | 318 */ |
296 showAtPosition: function(config) { | 319 showAtPosition: function(config) { |
297 // Save the scroll position of the viewport. | |
298 var doc = document.scrollingElement; | |
299 var scrollLeft = doc.scrollLeft; | |
300 var scrollTop = doc.scrollTop; | |
301 | |
302 // Reset position so that layout isn't affected by the previous position, | |
303 // and so that the dialog is positioned at the top-start corner of the | |
304 // document. | |
305 this.resetStyle_(); | 320 this.resetStyle_(); |
306 this.showModal(); | 321 this.showModal(); |
307 | 322 this.positionDialog_(config); |
308 config.top += scrollTop; | |
309 config.left += scrollLeft; | |
310 | |
311 this.positionDialog_(/** @type {ShowConfig} */ (Object.assign( | |
312 { | |
313 minX: scrollLeft, | |
314 minY: scrollTop, | |
315 maxX: scrollLeft + doc.clientWidth, | |
316 maxY: scrollTop + doc.clientHeight, | |
317 }, | |
318 config))); | |
319 | |
320 // Restore the scroll position. | |
321 doc.scrollTop = scrollTop; | |
322 doc.scrollLeft = scrollLeft; | |
323 this.addCloseListeners_(); | 323 this.addCloseListeners_(); |
324 }, | 324 }, |
325 | 325 |
326 /** @private */ | 326 /** @private */ |
327 resetStyle_: function() { | 327 resetStyle_: function() { |
328 this.style.left = ''; | 328 this.style.left = ''; |
329 this.style.right = ''; | 329 this.style.right = ''; |
330 this.style.top = '0'; | 330 this.style.top = '0'; |
331 }, | 331 }, |
332 | 332 |
333 /** | 333 /** |
334 * Position the dialog using the coordinates in config. Coordinates are | |
335 * relative to the top-left of the viewport when scrolled to (0, 0). | |
336 * @param {!ShowConfig} config | 334 * @param {!ShowConfig} config |
337 * @private | 335 * @private |
338 */ | 336 */ |
339 positionDialog_: function(config) { | 337 positionDialog_: function(config) { |
340 var c = Object.assign(getDefaultShowConfig(), config); | 338 var c = Object.assign(getDefaultShowConfig(), config); |
341 | 339 |
342 var top = c.top; | 340 var top = c.top; |
343 var left = c.left; | 341 var left = c.left; |
344 var bottom = top + c.height; | 342 var bottom = top + c.height; |
345 var right = left + c.width; | 343 var right = left + c.width; |
346 | 344 |
347 // Flip the X anchor in RTL. | 345 // Flip the X anchor in RTL. |
348 var rtl = getComputedStyle(this).direction == 'rtl'; | 346 var rtl = getComputedStyle(this).direction == 'rtl'; |
349 if (rtl) | 347 if (rtl) |
350 c.anchorAlignmentX *= -1; | 348 c.anchorAlignmentX *= -1; |
351 | 349 |
352 var menuLeft = getStartPointWithAnchor( | 350 var menuLeft = getStartPointWithAnchor( |
353 left, right, this.offsetWidth, c.anchorAlignmentX, c.minX, c.maxX); | 351 left, right, this.offsetWidth, c.anchorAlignmentX, c.minX, c.maxX); |
354 | 352 |
355 if (rtl) { | 353 if (rtl) { |
356 var menuRight = | 354 var menuRight = document.body.scrollWidth - menuLeft - this.offsetWidth; |
357 document.scrollingElement.clientWidth - menuLeft - this.offsetWidth; | |
358 this.style.right = menuRight + 'px'; | 355 this.style.right = menuRight + 'px'; |
359 } else { | 356 } else { |
360 this.style.left = menuLeft + 'px'; | 357 this.style.left = menuLeft + 'px'; |
361 } | 358 } |
362 | 359 |
363 var menuTop = getStartPointWithAnchor( | 360 var menuTop = getStartPointWithAnchor( |
364 top, bottom, this.offsetHeight, c.anchorAlignmentY, c.minY, c.maxY); | 361 top, bottom, this.offsetHeight, c.anchorAlignmentY, c.minY, c.maxY); |
365 this.style.top = menuTop + 'px'; | 362 this.style.top = menuTop + 'px'; |
366 }, | 363 }, |
367 | 364 |
368 /** | 365 /** |
369 * @private | 366 * @private |
370 */ | 367 */ |
371 addCloseListeners_: function() { | 368 addCloseListeners_: function() { |
372 this.boundClose_ = this.boundClose_ || function() { | 369 this.boundClose_ = this.boundClose_ || function() { |
373 if (this.open) | 370 if (this.open) |
374 this.close(); | 371 this.close(); |
375 }.bind(this); | 372 }.bind(this); |
376 window.addEventListener('resize', this.boundClose_); | 373 window.addEventListener('resize', this.boundClose_); |
377 window.addEventListener('popstate', this.boundClose_); | 374 window.addEventListener('popstate', this.boundClose_); |
378 }, | 375 }, |
379 }); | 376 }); |
380 })(); | 377 })(); |
OLD | NEW |