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 |