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..124c100fd0ecbc47bc9934914bb8f208d84405c4 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,22 @@ |
// 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), |
+ * anchorConfigX: (number| undefined), |
+ * anchorConfigY: (number| undefined), |
+ * minX: (number| undefined), |
+ * minY: (number| undefined), |
+ * maxX: (number| undefined), |
+ * maxY: (number| undefined), |
+ * }} |
+ */ |
+var ShowConfig; |
+ |
Polymer({ |
is: 'cr-action-menu', |
extends: 'dialog', |
@@ -14,7 +30,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 +168,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 +180,44 @@ 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. |
+ anchorConfigX: -1, |
+ }); |
+ }, |
+ |
+ /** |
+ * Shows the menu anchored to the given box. The anchor configuration is |
+ * specified as an X and Y alignment which represents a point in the anchor |
+ * and the menu that will be aligned where (0, 0) and (1, 1) are the center |
+ * and bottom-right of each box respectively. |
+ * |
+ * For example, having the menu centered around the anchor box would use an |
+ * anchor config of (0, 0), whereas aligning the menu to the top-right edge of |
+ * the anchor would use a config of (1, -1). |
dpapad
2017/04/25 17:42:43
This explanation is much better than before. Can w
calamity
2017/04/26 03:15:50
Done.
|
+ * |
+ * To align the menu outside the given box, use either a point or a line |
+ * instead of the full box. e.g aligning a dropdown to center beneath a div |
+ * at ((0, 0), (100, 100)) would use ((0, 100), (100, 100)) as the box and |
+ * (0, 1) as the anchor, effectively using the bottom edge of the div as the |
+ * anchor and centering horizontally beneath that. |
+ * |
+ * @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 +230,70 @@ 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.anchorConfigX *= -1; |
+ |
+ var menuLeft = this.getStartPointWithAnchor_( |
+ left, right, this.offsetWidth, c.anchorConfigX, 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.anchorConfigY, 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 {number} anchorConfig |
+ * @param {number} min |
+ * @param {number} max |
+ * @return {number} |
+ */ |
+ getStartPointWithAnchor_: function( |
+ start, end, length, anchorConfig, min, max) { |
+ var startPoint = |
+ ((start + end - length) + (start - end + length) * anchorConfig) / 2; |
+ if (startPoint + length > max) |
+ startPoint = end - length; |
+ if (startPoint < min) |
+ startPoint = start; |
+ return startPoint; |
+ }, |
+ |
- this.style.top = top + 'px'; |
+ /** |
+ * @private |
+ * @return {!ShowConfig} |
+ */ |
+ getDefaultShowConfig_: function() { |
+ return { |
+ top: 0, |
+ left: 0, |
+ height: 0, |
+ width: 0, |
+ anchorConfigX: 1, |
+ anchorConfigY: 1, |
+ minX: 0, |
+ minY: 0, |
+ maxX: window.innerWidth, |
+ maxY: window.innerHeight, |
+ }; |
}, |
}); |