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: doc.scrollLeft, | 92 minX: 0, |
93 minY: doc.scrollTop, | 93 minY: 0, |
94 maxX: doc.scrollLeft + window.innerWidth, | 94 maxX: 0, |
95 maxY: doc.scrollTop + window.innerHeight, | 95 maxY: 0, |
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 | |
270 var rect = this.anchorElement_.getBoundingClientRect(); | 256 var rect = this.anchorElement_.getBoundingClientRect(); |
271 this.positionDialog_(/** @type {ShowConfig} */ (Object.assign( | 257 this.showAtPosition(/** @type {ShowConfig} */ (Object.assign( |
272 { | 258 { |
273 top: rect.top, | 259 top: rect.top, |
274 left: rect.left, | 260 left: rect.left, |
275 height: rect.height, | 261 height: rect.height, |
276 width: rect.width, | 262 width: rect.width, |
277 // Default to anchoring towards the left. | 263 // Default to anchoring towards the left. |
278 anchorAlignmentX: AnchorAlignment.BEFORE_END, | 264 anchorAlignmentX: AnchorAlignment.BEFORE_END, |
279 minX: scrollLeft, | |
280 minY: scrollTop, | |
281 maxX: scrollLeft + window.innerWidth, | |
282 maxY: scrollTop + window.innerHeight, | |
283 }, | 265 }, |
284 opt_config))); | 266 opt_config))); |
285 | |
286 // Restore the scroll position. | |
287 doc.scrollTop = scrollTop; | |
288 doc.scrollLeft = scrollLeft; | |
289 | |
290 this.addCloseListeners_(); | |
291 }, | 267 }, |
292 | 268 |
293 /** | 269 /** |
294 * Shows the menu anchored to the given box. The anchor alignment is | 270 * Shows the menu anchored to the given box. The anchor alignment is |
295 * specified as an X and Y alignment which represents a point in the anchor | 271 * specified as an X and Y alignment which represents a point in the anchor |
296 * where the menu will align to, which can have the menu either before or | 272 * where the menu will align to, which can have the menu either before or |
297 * after the given point in each axis. Center alignment places the center of | 273 * after the given point in each axis. Center alignment places the center of |
298 * the menu in line with the center of the anchor. | 274 * the menu in line with the center of the anchor. Coordinates are relative to |
| 275 * the top-left of the viewport. |
299 * | 276 * |
300 * y-start | 277 * y-start |
301 * _____________ | 278 * _____________ |
302 * | | | 279 * | | |
303 * | | | 280 * | | |
304 * | CENTER | | 281 * | CENTER | |
305 * x-start | x | x-end | 282 * x-start | x | x-end |
306 * | | | 283 * | | |
307 * |anchor box | | 284 * |anchor box | |
308 * |___________| | 285 * |___________| |
309 * | 286 * |
310 * y-end | 287 * y-end |
311 * | 288 * |
312 * For example, aligning the menu to the inside of the top-right edge of | 289 * For example, aligning the menu to the inside of the top-right edge of |
313 * the anchor, extending towards the bottom-left would use a alignment of | 290 * the anchor, extending towards the bottom-left would use a alignment of |
314 * (BEFORE_END, AFTER_START), whereas centering the menu below the bottom | 291 * (BEFORE_END, AFTER_START), whereas centering the menu below the bottom |
315 * edge of the anchor would use (CENTER, AFTER_END). | 292 * edge of the anchor would use (CENTER, AFTER_END). |
316 * | 293 * |
317 * @param {!ShowConfig} config | 294 * @param {!ShowConfig} config |
318 */ | 295 */ |
319 showAtPosition: function(config) { | 296 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. |
320 this.resetStyle_(); | 305 this.resetStyle_(); |
321 this.showModal(); | 306 this.showModal(); |
322 this.positionDialog_(config); | 307 |
| 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). |
334 * @param {!ShowConfig} config | 336 * @param {!ShowConfig} config |
335 * @private | 337 * @private |
336 */ | 338 */ |
337 positionDialog_: function(config) { | 339 positionDialog_: function(config) { |
338 var c = Object.assign(getDefaultShowConfig(), config); | 340 var c = Object.assign(getDefaultShowConfig(), config); |
339 | 341 |
340 var top = c.top; | 342 var top = c.top; |
341 var left = c.left; | 343 var left = c.left; |
342 var bottom = top + c.height; | 344 var bottom = top + c.height; |
343 var right = left + c.width; | 345 var right = left + c.width; |
344 | 346 |
345 // Flip the X anchor in RTL. | 347 // Flip the X anchor in RTL. |
346 var rtl = getComputedStyle(this).direction == 'rtl'; | 348 var rtl = getComputedStyle(this).direction == 'rtl'; |
347 if (rtl) | 349 if (rtl) |
348 c.anchorAlignmentX *= -1; | 350 c.anchorAlignmentX *= -1; |
349 | 351 |
350 var menuLeft = getStartPointWithAnchor( | 352 var menuLeft = getStartPointWithAnchor( |
351 left, right, this.offsetWidth, c.anchorAlignmentX, c.minX, c.maxX); | 353 left, right, this.offsetWidth, c.anchorAlignmentX, c.minX, c.maxX); |
352 | 354 |
353 if (rtl) { | 355 if (rtl) { |
354 var menuRight = document.body.scrollWidth - menuLeft - this.offsetWidth; | 356 var menuRight = |
| 357 document.scrollingElement.clientWidth - menuLeft - this.offsetWidth; |
355 this.style.right = menuRight + 'px'; | 358 this.style.right = menuRight + 'px'; |
356 } else { | 359 } else { |
357 this.style.left = menuLeft + 'px'; | 360 this.style.left = menuLeft + 'px'; |
358 } | 361 } |
359 | 362 |
360 var menuTop = getStartPointWithAnchor( | 363 var menuTop = getStartPointWithAnchor( |
361 top, bottom, this.offsetHeight, c.anchorAlignmentY, c.minY, c.maxY); | 364 top, bottom, this.offsetHeight, c.anchorAlignmentY, c.minY, c.maxY); |
362 this.style.top = menuTop + 'px'; | 365 this.style.top = menuTop + 'px'; |
363 }, | 366 }, |
364 | 367 |
365 /** | 368 /** |
366 * @private | 369 * @private |
367 */ | 370 */ |
368 addCloseListeners_: function() { | 371 addCloseListeners_: function() { |
369 this.boundClose_ = this.boundClose_ || function() { | 372 this.boundClose_ = this.boundClose_ || function() { |
370 if (this.open) | 373 if (this.open) |
371 this.close(); | 374 this.close(); |
372 }.bind(this); | 375 }.bind(this); |
373 window.addEventListener('resize', this.boundClose_); | 376 window.addEventListener('resize', this.boundClose_); |
374 window.addEventListener('popstate', this.boundClose_); | 377 window.addEventListener('popstate', this.boundClose_); |
375 }, | 378 }, |
376 }); | 379 }); |
377 })(); | 380 })(); |
OLD | NEW |