| 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 |