Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(78)

Side by Side Diff: ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js

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

Powered by Google App Engine
This is Rietveld 408576698