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

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: fix nit Created 3 years, 5 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
« no previous file with comments | « chrome/test/data/webui/cr_elements/cr_action_menu_test.js ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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() {
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
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 })();
OLDNEW
« no previous file with comments | « chrome/test/data/webui/cr_elements/cr_action_menu_test.js ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698