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