Chromium Code Reviews| Index: ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js |
| diff --git a/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js b/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js |
| index 42b00655dd9e1ba6b45a7acd27a3892e9ef6d2fa..23cf76134fbfd6b667a69fa36a0268acba0aeb92 100644 |
| --- a/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js |
| +++ b/ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js |
| @@ -2,6 +2,34 @@ |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| +/** |
| + * @typedef {{ |
| + * top: number, |
| + * left: number, |
| + * width: (number| undefined), |
| + * height: (number| undefined), |
| + * anchorAlignmentX: (number| undefined), |
| + * anchorAlignmentY: (number| undefined), |
| + * minX: (number| undefined), |
| + * minY: (number| undefined), |
| + * maxX: (number| undefined), |
| + * maxY: (number| undefined), |
| + * }} |
| + */ |
| +var ShowConfig; |
| + |
| +/** |
| + * @enum |
|
dpapad
2017/04/27 01:30:23
@enum {number}
calamity
2017/04/27 03:16:24
Done.
|
| + * @const |
| + */ |
| +var AnchorAlignment = { |
| + BEFORE_START: -2, |
| + AFTER_START: -1, |
| + CENTER: 0, |
| + BEFORE_END: 1, |
| + AFTER_END: 2, |
| +}; |
| + |
| Polymer({ |
| is: 'cr-action-menu', |
| extends: 'dialog', |
| @@ -14,7 +42,8 @@ Polymer({ |
| /** |
| * The element which the action menu will be anchored to. Also the element |
| - * where focus will be returned after the menu is closed. |
| + * where focus will be returned after the menu is closed. Only populated if |
| + * menu is opened with showAt(). |
| * @private {?Element} |
| */ |
| anchorElement_: null, |
| @@ -151,8 +180,10 @@ Polymer({ |
| // Removing 'resize' and 'popstate' listeners when dialog is closed. |
| this.removeListeners_(); |
| HTMLDialogElement.prototype.close.call(this); |
| - this.anchorElement_.focus(); |
| - this.anchorElement_ = null; |
| + if (this.anchorElement_) { |
| + this.anchorElement_.focus(); |
| + this.anchorElement_ = null; |
| + } |
| }, |
| /** |
| @@ -161,6 +192,52 @@ Polymer({ |
| */ |
| showAt: function(anchorElement) { |
| this.anchorElement_ = anchorElement; |
| + this.anchorElement_.scrollIntoViewIfNeeded(); |
| + var rect = this.anchorElement_.getBoundingClientRect(); |
| + this.showAtPosition({ |
| + top: rect.top, |
| + left: rect.left, |
| + height: rect.height, |
| + width: rect.width, |
| + // Default to anchoring towards the left. |
| + anchorAlignmentX: -1, |
|
dpapad
2017/04/27 01:30:23
Should we be using the AnchorAlignment enum now th
calamity
2017/04/27 03:16:24
Done.
|
| + }); |
| + }, |
| + |
| + /** |
| + * Shows the menu anchored to the given box. The anchor alignment is |
| + * specified as an X and Y alignment which represents a point in the anchor |
| + * where the menu will align to, which can have the menu either before or |
| + * after the given point in each axis. Center alignment places the center of |
| + * the menu in line with the center of the anchor. |
| + * |
| + * y-start |
| + * _____________ |
| + * | | |
| + * | | |
| + * | CENTER | |
| + * x-start | x | x-end |
| + * | | |
| + * |anchor box | |
| + * |___________| |
| + * |
| + * y-end |
| + * |
| + * For example, aligning the menu to the inside of the top-right edge of |
| + * the anchor, extending towards the bottom-left would use a alignment of |
| + * (BEFORE_END, AFTER_START), whereas centering the menu below the bottom edge |
| + * of the anchor would use (CENTER, AFTER_END). |
| + * |
| + * @param {!ShowConfig} config |
| + */ |
| + showAtPosition: function(config) { |
| + var c = Object.assign(this.getDefaultShowConfig_(), config); |
| + |
| + var top = c.top; |
| + var left = c.left; |
| + var bottom = top + c.height; |
| + var right = left + c.width; |
| + |
| this.boundClose_ = this.boundClose_ || function() { |
| if (this.open) |
| this.close(); |
| @@ -173,25 +250,87 @@ Polymer({ |
| this.style.right = ''; |
| this.style.top = ''; |
| - this.anchorElement_.scrollIntoViewIfNeeded(); |
| this.showModal(); |
| - var rect = this.anchorElement_.getBoundingClientRect(); |
| - if (getComputedStyle(this.anchorElement_).direction == 'rtl') { |
| - var right = window.innerWidth - rect.left - this.offsetWidth; |
| - this.style.right = right + 'px'; |
| + // Flip the X anchor in RTL. |
| + var rtl = getComputedStyle(this).direction == 'rtl'; |
| + if (rtl) |
| + c.anchorAlignmentX *= -1; |
| + |
| + var menuLeft = this.getStartPointWithAnchor_( |
| + left, right, this.offsetWidth, c.anchorAlignmentX, c.minX, c.maxX); |
| + |
| + if (rtl) { |
| + var menuRight = window.innerWidth - menuLeft - this.offsetWidth; |
| + this.style.right = menuRight + 'px'; |
| } else { |
| - var left = rect.right - this.offsetWidth; |
| - this.style.left = left + 'px'; |
| + this.style.left = menuLeft + 'px'; |
| } |
| - // Attempt to show the menu starting from the top of the rectangle and |
| - // extending downwards. If that does not fit within the window, fallback to |
| - // starting from the bottom and extending upwards. |
| - var top = rect.top + this.offsetHeight <= window.innerHeight ? rect.top : |
| - rect.bottom - |
| - this.offsetHeight - Math.max(rect.bottom - window.innerHeight, 0); |
| + var menuTop = this.getStartPointWithAnchor_( |
| + top, bottom, this.offsetHeight, c.anchorAlignmentY, c.minY, c.maxY); |
| + this.style.top = menuTop + 'px'; |
| + }, |
| + |
| + /** |
| + * Returns the point to start along the X or Y axis given a start and end |
| + * point to anchor to, the length of the target and the direction to anchor |
| + * in. If honoring the anchor would force the menu outside of min/max, this |
| + * will ignore the anchor position and try to keep the menu within min/max. |
| + * @private |
| + * @param {number} start |
| + * @param {number} end |
| + * @param {number} length |
| + * @param {AnchorAlignment} anchorAlignment |
| + * @param {number} min |
| + * @param {number} max |
| + * @return {number} |
| + */ |
| + getStartPointWithAnchor_: function( |
| + start, end, length, anchorAlignment, min, max) { |
| + var startPoint = 0; |
| + switch (anchorAlignment) { |
| + case AnchorAlignment.BEFORE_START: |
| + startPoint = -length; |
| + break; |
| + case AnchorAlignment.AFTER_START: |
| + startPoint = start; |
| + break; |
| + case AnchorAlignment.CENTER: |
| + startPoint = (start + end - length) / 2; |
| + break; |
| + case AnchorAlignment.BEFORE_END: |
| + startPoint = end - length; |
| + break; |
| + case AnchorAlignment.AFTER_END: |
| + startPoint = end; |
| + break; |
| + } |
| - this.style.top = top + 'px'; |
| + if (startPoint + length > max) |
| + startPoint = end - length; |
| + if (startPoint < min) |
| + startPoint = start; |
| + return startPoint; |
| + }, |
| + |
| + |
| + /** |
| + * @private |
| + * @return {!ShowConfig} |
| + */ |
| + getDefaultShowConfig_: function() { |
|
dpapad
2017/04/27 01:30:23
This method (and also getStartPointWithAnchor_ abo
calamity
2017/04/27 03:16:24
Done.
|
| + return { |
| + top: 0, |
| + left: 0, |
| + height: 0, |
| + width: 0, |
| + anchorAlignmentX: AnchorAlignment.AFTER_START, |
| + anchorAlignmentY: AnchorAlignment.AFTER_START, |
| + minX: 0, |
| + minY: 0, |
| + maxX: window.innerWidth, |
| + maxY: window.innerHeight, |
| + }; |
| }, |
| }); |