diff --git a/src/vaadin-context-menu-overlay.html b/src/vaadin-context-menu-overlay.html
index c958e79f..c9a51fcb 100644
--- a/src/vaadin-context-menu-overlay.html
+++ b/src/vaadin-context-menu-overlay.html
@@ -92,7 +92,8 @@
xMax: overlayRect.right - contentRect.width,
yMax,
left: overlayRect.left,
- top: overlayRect.top
+ top: overlayRect.top,
+ width: contentRect.width
};
}
}
diff --git a/src/vaadin-context-menu.html b/src/vaadin-context-menu.html
index ed08da7a..c38fc1cb 100644
--- a/src/vaadin-context-menu.html
+++ b/src/vaadin-context-menu.html
@@ -556,28 +556,51 @@
['right-aligned', 'bottom-aligned'].forEach(attr => overlay.removeAttribute(attr));
// Maximum x and y values are imposed by content size and overlay limits.
- const {xMax, yMax, left, top} = overlay.getBoundaries();
+ const {xMax, yMax, left, top, width} = overlay.getBoundaries();
// Reuse saved x and y event values, in order to this method be used async
// in the `vaadin-overlay-change` which guarantees that overlay is ready
- const x = this.__x || left;
+ let x = this.__x || left;
const y = this.__y || top;
// Select one overlay corner and move to the event x/y position.
- // Then set styling attrs for flex-aligning the content appropriatelly.
+ // Then set styling attrs for flex-aligning the content appropriately.
const wdthVport = document.documentElement.clientWidth;
const hghtVport = document.documentElement.clientHeight;
+ // Align to the parent menu overlay, if any.
const parent = overlay.parentOverlay;
- if (parent && parent.hasAttribute('right-aligned')) {
- const parentStyle = getComputedStyle(parent);
+ let alignedToParent = false;
+ if (parent) {
const parentContentRect = parent.$.overlay.getBoundingClientRect();
- overlay.setAttribute('right-aligned', '');
- style.right = parseFloat(parentStyle.right) + parentContentRect.width + 'px';
- } else if (x < wdthVport / 2 || x < xMax) {
- style.left = x + 'px';
- } else {
- style.right = Math.max(0, (wdthVport - x)) + 'px';
- overlay.setAttribute('right-aligned', '');
+ if (parent.hasAttribute('right-aligned')) {
+ const parentStyle = getComputedStyle(parent);
+
+ const getPadding = (el, direction) => {
+ return parseFloat(getComputedStyle(el.$.content)['padding' + direction]);
+ };
+ const right = parseFloat(parentStyle.right) + parentContentRect.width;
+ const padding = getPadding(parent, 'Left') + getPadding(overlay, 'Right');
+
+ // Preserve right-aligned, if possible.
+ if ((wdthVport - (right - padding)) > width) {
+ overlay.setAttribute('right-aligned', '');
+ style.right = right - padding + 'px';
+ alignedToParent = true;
+ } else if (!(x < wdthVport / 2 || x < xMax)) {
+ // We use parent menu item to predict "x", which isn't accurate.
+ // Adjust it now, based on the rendered sub-menu content width.
+ x = x - (width - parentContentRect.width);
+ }
+ }
+ }
+
+ if (!alignedToParent) {
+ if (x < wdthVport / 2 || x < xMax) {
+ style.left = x + 'px';
+ } else {
+ style.right = Math.max(0, (wdthVport - x)) + 'px';
+ overlay.setAttribute('right-aligned', '');
+ }
}
if (y < hghtVport / 2 || y < yMax) {
style.top = y + 'px';
diff --git a/test/items.html b/test/items.html
index 174b8539..4271982a 100644
--- a/test/items.html
+++ b/test/items.html
@@ -56,7 +56,10 @@
{text: 'foo-0', children:
[
{text: 'foo-0-0', checked: true},
- {text: 'foo-0-1', disabled: true}
+ {text: 'foo-0-1', disabled: true},
+ {text: 'foo-0-2', children: [
+ {text: 'foo-0-2-0'}
+ ]},
]
},
{text: 'foo-1'}
@@ -130,19 +133,69 @@
});
it('should open the subMenu on the left if root menu is right-aligned', done => {
+ let padding;
subMenu.close();
- const rootItemRect = menuComponents()[0].getBoundingClientRect();
- rootMenu.$.overlay.style.removeProperty('left');
- rootMenu.$.overlay.style.right = rootItemRect.width + 'px';
- rootMenu.$.overlay.setAttribute('right-aligned', '');
- open(menuComponents()[0]);
+ const rootItem = menuComponents()[0];
+ const rootItemRect = rootItem.getBoundingClientRect();
+ const rootOverlay = rootMenu.$.overlay;
+ rootOverlay.style.removeProperty('left');
+ rootOverlay.style.right = rootItemRect.width + 'px';
+ rootOverlay.setAttribute('right-aligned', '');
+ padding = parseFloat(getComputedStyle(rootOverlay.$.content).paddingLeft) * 2;
+ open(rootItem);
Polymer.RenderStatus.afterNextRender(subMenu, () => {
expect(subMenu.$.overlay.hasAttribute('right-aligned')).to.be.true;
- const rootMenuRect = rootMenu.$.overlay.$.content.getBoundingClientRect();
+ const rootMenuRect = rootOverlay.$.content.getBoundingClientRect();
const subMenuRect = subMenu.$.overlay.$.content.getBoundingClientRect();
- expect(subMenuRect.right).to.be.closeTo(rootMenuRect.left, 1);
- done();
+ expect(subMenuRect.right).to.be.closeTo(rootMenuRect.left + padding, 1);
+
+ const nestedItem = menuComponents(subMenu)[2];
+ padding = parseFloat(getComputedStyle(subMenu.$.overlay.$.content).paddingLeft) * 2;
+ open(nestedItem);
+
+ Polymer.RenderStatus.afterNextRender(subMenu, () => {
+ const subMenu2 = getSubMenu(subMenu);
+ expect(subMenu2.$.overlay.hasAttribute('right-aligned')).to.be.true;
+ const subMenu2Rect = subMenu2.$.overlay.$.content.getBoundingClientRect();
+ expect(subMenu2Rect.right).to.be.closeTo(subMenuRect.left + padding, 1);
+ done();
+ });
+ });
+ });
+
+ it('should open the second subMenu on the right again if not enough space', done => {
+ let padding;
+ subMenu.close();
+ rootMenu.items[0].children[2].text = 'foo-0-2-longer';
+
+ const rootItem = menuComponents()[0];
+ const rootItemRect = rootItem.getBoundingClientRect();
+ const rootOverlay = rootMenu.$.overlay;
+ rootOverlay.style.removeProperty('left');
+ rootOverlay.style.right = rootItemRect.width + 'px';
+ rootOverlay.setAttribute('right-aligned', '');
+ padding = parseFloat(getComputedStyle(rootOverlay.$.content).paddingLeft) * 2;
+ open(rootItem);
+
+ Polymer.RenderStatus.afterNextRender(subMenu, () => {
+ expect(subMenu.$.overlay.hasAttribute('right-aligned')).to.be.true;
+ const rootMenuRect = rootOverlay.$.content.getBoundingClientRect();
+ const subMenuRect = subMenu.$.overlay.$.content.getBoundingClientRect();
+ expect(subMenuRect.right).to.be.closeTo(rootMenuRect.left + padding, 1);
+
+ const nestedItem = menuComponents(subMenu)[2];
+ const nestedItemRect = nestedItem.getBoundingClientRect();
+ padding = parseFloat(getComputedStyle(subMenu.$.overlay.$.content).paddingLeft) * 2;
+ open(nestedItem);
+
+ Polymer.RenderStatus.afterNextRender(subMenu, () => {
+ const subMenu2 = getSubMenu(subMenu);
+ expect(subMenu2.$.overlay.hasAttribute('right-aligned')).to.be.false;
+ const subMenu2Rect = subMenu2.$.overlay.$.content.getBoundingClientRect();
+ expect(subMenu2Rect.left).to.be.closeTo(nestedItemRect.right, 1);
+ done();
+ });
});
});
}