Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be a web component issue but noticed this on the IT page. Somethimes the tooltip ends up behind a sub-menu:

Kapture.2026-05-04.at.16.17.47.mp4

Copy link
Copy Markdown
Contributor Author

@vursen vursen May 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After detaching and reattaching the target button, it moves to the next line. As a result, the tooltip doesn't fit on the left side anymore, so it falls back to the right side and ends up under the menu perhaps because the web component opens its overlay before the sub-menu's one (not sure about that though). Either way, it's a web component issue indeed.

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2000-2026 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.flow.component.contextmenu.it;

import com.vaadin.flow.component.contextmenu.ContextMenu;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.component.shared.Tooltip.TooltipPosition;
import com.vaadin.flow.component.shared.TooltipConfiguration;
import com.vaadin.flow.router.Route;

@Route("vaadin-context-menu/tooltip")
public class ContextMenuTooltipPage extends Div {

public ContextMenuTooltipPage() {
// Reset default delay values from 500 to 0
TooltipConfiguration.setDefaultFocusDelay(0);
TooltipConfiguration.setDefaultHoverDelay(0);
TooltipConfiguration.setDefaultHideDelay(0);

var target = new NativeButton("Target");
target.setId("target");

var contextMenu = new ContextMenu(target);

// Root items
var item0 = contextMenu.addItem("Item 0", "Item 0 / Tooltip");

var item1 = contextMenu.addItem("Item 1", "Item 1 / Tooltip");
item1.setEnabled(false);

var item2 = contextMenu.addItem("Item 2", "Item 2 / Tooltip");
item2.setTooltipPosition(TooltipPosition.TOP);

// Sub menu items
var item0_0 = item0.getSubMenu().addItem("Item 0-0",

Check warning on line 49 in vaadin-context-menu-flow-parent/vaadin-context-menu-flow-integration-tests/src/main/java/com/vaadin/flow/component/contextmenu/it/ContextMenuTooltipPage.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=vaadin_flow-components&issues=AZ3iYy5-wWIgk1RLdsJf&open=AZ3iYy5-wWIgk1RLdsJf&pullRequest=9223
"Item 0-0 / Tooltip");

var item0_1 = item0.getSubMenu().addItem("Item 0-1",

Check warning on line 52 in vaadin-context-menu-flow-parent/vaadin-context-menu-flow-integration-tests/src/main/java/com/vaadin/flow/component/contextmenu/it/ContextMenuTooltipPage.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=vaadin_flow-components&issues=AZ3iYy5-wWIgk1RLdsJg&open=AZ3iYy5-wWIgk1RLdsJg&pullRequest=9223
"Item 0-1 / Tooltip");
item0_1.setEnabled(false);

var item0_2 = item0.getSubMenu().addItem("Item 0-2",

Check warning on line 56 in vaadin-context-menu-flow-parent/vaadin-context-menu-flow-integration-tests/src/main/java/com/vaadin/flow/component/contextmenu/it/ContextMenuTooltipPage.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Rename this local variable to match the regular expression '^[a-z][a-zA-Z0-9]*$'.

See more on https://sonarcloud.io/project/issues?id=vaadin_flow-components&issues=AZ3iYy5-wWIgk1RLdsJh&open=AZ3iYy5-wWIgk1RLdsJh&pullRequest=9223
"Item 0-2 / Tooltip");
item0_2.setTooltipPosition(TooltipPosition.TOP);

var attach = new NativeButton("Attach", event -> add(target));
attach.setId("attach");
var detach = new NativeButton("Detach", event -> remove(target));
detach.setId("detach");

var updateTooltips = new NativeButton("Update tooltips", event -> {
item0.setTooltipText("Item 0 / Updated Tooltip");
item0_0.setTooltipText("Item 0-0 / Updated Tooltip");
});
updateTooltips.setId("update-tooltips");

add(attach, detach, updateTooltips, target, contextMenu);
Comment thread
web-padawan marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
com.vaadin.experimental.accessibleDisabledMenuItems=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 2000-2026 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.flow.component.contextmenu.it;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import com.vaadin.flow.component.contextmenu.testbench.ContextMenuElement;
import com.vaadin.flow.testutil.TestPath;
import com.vaadin.testbench.TestBenchElement;
import com.vaadin.tests.AbstractComponentIT;

@TestPath("vaadin-context-menu/tooltip")
public class ContextMenuTooltipIT extends AbstractComponentIT {

private TestBenchElement target;
private ContextMenuElement contextMenu;
private TestBenchElement contextMenuTooltip;

@Before
public void init() {
open();
target = $(TestBenchElement.class).id("target");
contextMenu = $(ContextMenuElement.class).single();
contextMenuTooltip = contextMenu.$("vaadin-tooltip").single();
}

@Test
public void openMenu_hoverOverRootItems_tooltipDisplayed() {
ContextMenuElement.openByRightClick(target);

var items = contextMenu.getMenuItems();

items.get(0).hover();
Assert.assertEquals("Item 0 / Tooltip", contextMenuTooltip.getText());

items.get(1).hover();
Assert.assertEquals("Item 1 / Tooltip", contextMenuTooltip.getText());

items.get(2).hover();
Assert.assertEquals("Item 2 / Tooltip", contextMenuTooltip.getText());
Assert.assertEquals("top",
contextMenuTooltip.getDomProperty("_position"));
Comment thread
web-padawan marked this conversation as resolved.
}

@Test
public void openMenu_hoverOverSubMenuItems_tooltipDisplayed() {
ContextMenuElement.openByRightClick(target);

var subMenu = contextMenu.getMenuItems().get(0).openSubMenu();
var subMenuItems = subMenu.getMenuItems();

subMenuItems.get(0).hover();
Assert.assertEquals("Item 0-0 / Tooltip", contextMenuTooltip.getText());

subMenuItems.get(1).hover();
Assert.assertEquals("Item 0-1 / Tooltip", contextMenuTooltip.getText());

subMenuItems.get(2).hover();
Assert.assertEquals("Item 0-2 / Tooltip", contextMenuTooltip.getText());
Assert.assertEquals("top",
contextMenuTooltip.getDomProperty("_position"));
}

@Test
public void updateTooltip_openMenu_hoverOverItems_updatedTooltipDisplayed() {
clickElementWithJs("update-tooltips");

ContextMenuElement.openByRightClick(target);

contextMenu.getMenuItems().get(0).hover();
Assert.assertEquals("Item 0 / Updated Tooltip",
contextMenuTooltip.getText());

var subMenu = contextMenu.getMenuItems().get(0).openSubMenu();
subMenu.getMenuItems().get(0).hover();
Assert.assertEquals("Item 0-0 / Updated Tooltip",
contextMenuTooltip.getText());
}

@Test
public void detachAndAttach_openMenu_hoverOverItems_tooltipDisplayed() {
detachAndAttach();

ContextMenuElement.openByRightClick(target);

contextMenu.getMenuItems().get(0).hover();
Assert.assertEquals("Item 0 / Tooltip", contextMenuTooltip.getText());

var subMenu = contextMenu.getMenuItems().get(0).openSubMenu();
subMenu.getMenuItems().get(0).hover();
Assert.assertEquals("Item 0-0 / Tooltip", contextMenuTooltip.getText());
}

private void detachAndAttach() {
clickElementWithJs("detach");
clickElementWithJs("attach");
target = $(TestBenchElement.class).id("target");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.dependency.NpmPackage;
import com.vaadin.flow.component.page.PendingJavaScriptResult;
import com.vaadin.flow.component.shared.SlotUtils;
import com.vaadin.flow.component.shared.internal.OverlayAutoAddController;
import com.vaadin.flow.dom.DomEvent;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.function.SerializableRunnable;
import com.vaadin.flow.shared.Registration;

Expand All @@ -55,7 +57,9 @@
@SuppressWarnings("serial")
@Tag("vaadin-context-menu")
@NpmPackage(value = "@vaadin/context-menu", version = "25.2.0-alpha10")
@NpmPackage(value = "@vaadin/tooltip", version = "25.2.0-alpha10")
@JsModule("@vaadin/context-menu/src/vaadin-context-menu.js")
@JsModule("@vaadin/tooltip/src/vaadin-tooltip.js")
@JsModule("./flow-component-renderer.js")
@JsModule("./contextMenuConnector.js")
@JsModule("./contextMenuTargetConnector.js")
Expand Down Expand Up @@ -225,6 +229,34 @@ public I addItem(Component component) {
return getMenuManager().addItem(component);
}

/**
* Creates a new menu item with the given text content and tooltip text and
* adds it to the context menu.
*
* @param text
* the text content for the created menu item
* @param tooltipText
* the tooltip text for the created menu item
* @return the created menu item
*/
public I addItem(String text, String tooltipText) {
return getMenuManager().addItem(text, tooltipText);
}

/**
* Creates a new menu item with the given component content and tooltip text
* and adds it to the context menu.
*
* @param component
* the component to add to the created menu item
* @param tooltipText
* the tooltip text for the created menu item
* @return the created menu item
*/
public I addItem(Component component, String tooltipText) {
return getMenuManager().addItem(component, tooltipText);
}

/**
* Adds the given components to the context menu.
* <p>
Expand Down Expand Up @@ -479,4 +511,10 @@ private void initConnector(String appId) {
"window.Vaadin.Flow.contextMenuConnector.initLazy(this, $0)",
appId);
}

void ensureTooltipElement() {
if (SlotUtils.getElementsInSlot(this, "tooltip").count() == 0) {
SlotUtils.addToSlot(this, "tooltip", new Element("vaadin-tooltip"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class MenuItem extends MenuItemBase<ContextMenu, MenuItem, SubMenu>

public MenuItem(ContextMenu contextMenu,
SerializableRunnable contentReset) {
super(contextMenu);
super(contextMenu, contentReset);
this.contentReset = contentReset;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
import com.vaadin.flow.component.HasEnabled;
import com.vaadin.flow.component.HasText;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.shared.Tooltip.TooltipPosition;
import com.vaadin.flow.component.shared.internal.DisableOnClickController;
import com.vaadin.flow.dom.SignalBinding;
import com.vaadin.flow.function.SerializableRunnable;
import com.vaadin.flow.internal.nodefeature.SignalBindingFeature;
import com.vaadin.flow.signals.Signal;

Expand Down Expand Up @@ -62,14 +64,30 @@
private final DisableOnClickController<MenuItemBase<C, I, S>> disableOnClickController = new DisableOnClickController<>(
this);

private final SerializableRunnable contentReset;

/**
* Default constructor
*
* @param contextMenu
* the context menu to which this item belongs to
*/
public MenuItemBase(C contextMenu) {
this(contextMenu, () -> {
});
}

/**
* Creates a menu item belonging to the given menu.
*
* @param contextMenu
* the context menu to which this item belongs to
* @param contentReset
* callback to reset the menu content
*/
public MenuItemBase(C contextMenu, SerializableRunnable contentReset) {

Check warning on line 88 in vaadin-context-menu-flow-parent/vaadin-context-menu-flow/src/main/java/com/vaadin/flow/component/contextmenu/MenuItemBase.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Change the visibility of this constructor to "protected".

See more on https://sonarcloud.io/project/issues?id=vaadin_flow-components&issues=AZ3yIKc5klOD4tlnv-fG&open=AZ3yIKc5klOD4tlnv-fG&pullRequest=9223
this.contextMenu = contextMenu;
this.contentReset = contentReset;
getElement().addEventListener("click", e -> {
if (checkable) {
setChecked(!isChecked());
Expand Down Expand Up @@ -339,6 +357,42 @@
getElement(), themeName);
}

/**
* Sets the tooltip text for this menu item. Setting {@code null} or an
* empty text removes the tooltip from the item.
*
* @param tooltipText
* the tooltip text to set for the item, or {@code null} to clear
* it
* @see #setTooltipPosition(TooltipPosition)
*/
public void setTooltipText(String tooltipText) {
ensureTooltipElement();
getElement().setProperty("tooltip", tooltipText);
contentReset.run();
}

/**
* Sets the tooltip position for this menu item, overriding the default.
* Items with a sub-menu default to {@code start} so the tooltip doesn't
* overlap the opening sub-menu; all other items, including disabled ones,
* default to {@code end}.
*
* @param position
* the tooltip position, or {@code null} to clear it and use the
* default
* @see #setTooltipText(String)
*/
public void setTooltipPosition(TooltipPosition position) {
getElement().setProperty("tooltipPosition",
position != null ? position.getPosition() : null);
contentReset.run();
}

protected void ensureTooltipElement() {
contextMenu.ensureTooltipElement();
}

protected abstract S createSubMenu();

protected void executeJsWhenAttached(String expression,
Expand Down
Loading
Loading