-
Notifications
You must be signed in to change notification settings - Fork 92
feat: add tooltip support for menu items #11549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
vursen
wants to merge
12
commits into
main
Choose a base branch
from
feat/menu-item-tooltips
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+768
−78
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
cff6690
feat: add tooltip support for context-menu and menu-bar items
vursen d5ccc99
refactor: extract shared menu tooltip controller
vursen 276553f
fix: refine menu item tooltip open/close behavior
vursen 4d0655a
remove tests
vursen cc4fa93
fix: default tooltip position to end for disabled menu items
vursen 3496c81
docs: clarify tooltipPosition defaults for menu items
vursen 44fa6aa
refactor: inline menu tooltip controller into context-menu
vursen df9f80d
docs: document menu tooltip controllers
vursen 80158ba
test: update context-menu shadow snapshot for tooltip slot
vursen 6df281e
docs: clarify shared tooltip controller comments
vursen bf26446
refactor: simplify redundant item null check in tooltip controller
vursen 89ca1c4
docs: document item tooltips in context-menu typings
vursen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
76 changes: 76 additions & 0 deletions
76
packages/context-menu/src/vaadin-context-menu-tooltip-controller.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| /** | ||
| * @license | ||
| * Copyright (c) 2016 - 2026 Vaadin Ltd. | ||
| * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
| */ | ||
| import { SlotController } from '@vaadin/component-base/src/slot-controller.js'; | ||
|
|
||
| /** | ||
| * Controller for the tooltip slotted into `<vaadin-context-menu>`. Configures | ||
| * the tooltip in manual mode and drives its target, context, and position | ||
| * based on the currently hovered or focused item. | ||
| */ | ||
| export class ContextMenuTooltipController extends SlotController { | ||
| constructor(host) { | ||
| super(host, 'tooltip'); | ||
| } | ||
|
|
||
| /** @override */ | ||
| initCustomNode(tooltipNode) { | ||
| tooltipNode.manual = true; | ||
| tooltipNode.generator ||= ({ item }) => item?.tooltip; | ||
| } | ||
|
|
||
| /** @protected */ | ||
| _getItem(target) { | ||
| return target._item; | ||
| } | ||
|
|
||
| /** @protected */ | ||
| _getDefaultPosition(target) { | ||
| const item = this._getItem(target); | ||
| return item.children?.length > 0 && !item.disabled ? 'start' : 'end'; | ||
| } | ||
|
|
||
| /** | ||
| * @param {HTMLElement | null} target | ||
| */ | ||
| setTarget(target) { | ||
| const tooltipNode = this.node; | ||
| if (!tooltipNode) { | ||
| return; | ||
| } | ||
|
|
||
| const item = target ? this._getItem(target) : null; | ||
| if (item?.tooltip) { | ||
| tooltipNode.target = target; | ||
| tooltipNode.context = { item }; | ||
| tooltipNode._position = item.tooltipPosition || this._getDefaultPosition(target); | ||
| } else { | ||
| tooltipNode.target = null; | ||
| tooltipNode.context = { item: null }; | ||
| this.close(true); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param {{ trigger: 'hover' | 'focus' }} options | ||
| */ | ||
| open({ trigger }) { | ||
| const tooltipNode = this.node; | ||
| if (tooltipNode?.isConnected && tooltipNode.target) { | ||
| tooltipNode._stateController.open({ | ||
| hover: trigger === 'hover', | ||
| focus: trigger === 'focus', | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @param {boolean} immediate | ||
| */ | ||
| close(immediate) { | ||
| const tooltipNode = this.node; | ||
| tooltipNode?._stateController.close(immediate); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| * Copyright (c) 2016 - 2026 Vaadin Ltd. | ||
| * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
| */ | ||
| import { isKeyboardActive } from '@vaadin/a11y-base/src/focus-utils.js'; | ||
| import { isTouch } from '@vaadin/component-base/src/browser-utils.js'; | ||
|
|
||
| /** | ||
|
|
@@ -16,6 +17,13 @@ export const ItemsMixin = (superClass) => | |
| * @typedef ContextMenuItem | ||
| * @type {object} | ||
| * @property {string} text - Text to be set as the menu item component's textContent | ||
| * @property {string} tooltip - Text to be set as the menu item's tooltip. | ||
| * Requires a `<vaadin-tooltip slot="tooltip">` element to be added inside the `<vaadin-context-menu>`. | ||
| * @property {string} tooltipPosition - Position of the item's tooltip relative to the | ||
| * item (e.g. `end`, `top`, `bottom-start`). Items with a sub-menu default to `start` | ||
| * to avoid overlap with the opening sub-menu; all other items, including disabled | ||
| * ones (whose sub-menus cannot be opened), default to `end`. If the slotted | ||
| * `<vaadin-tooltip>` has its `position` property set, that value is used instead. | ||
| * @property {string | HTMLElement} component - The component to represent the item. | ||
| * Either a tagName or an element instance. Defaults to "vaadin-context-menu-item". | ||
| * @property {boolean} disabled - If true, the item is disabled and cannot be selected | ||
|
|
@@ -54,6 +62,18 @@ export const ItemsMixin = (superClass) => | |
| * ]; | ||
| * ``` | ||
| * | ||
| * #### Item tooltips | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add this to |
||
| * | ||
| * Menu items can have tooltips that are shown on hover and keyboard | ||
| * focus. To enable them, add a slotted `<vaadin-tooltip>` element | ||
| * and set the `tooltip` property on each item that should have one: | ||
| * | ||
| * ```html | ||
| * <vaadin-context-menu> | ||
| * <vaadin-tooltip slot="tooltip"></vaadin-tooltip> | ||
| * </vaadin-context-menu> | ||
| * ``` | ||
| * | ||
| * @type {!Array<!ContextMenuItem> | undefined} | ||
| */ | ||
| items: { | ||
|
|
@@ -105,6 +125,7 @@ export const ItemsMixin = (superClass) => | |
| disconnectedCallback() { | ||
| super.disconnectedCallback(); | ||
| document.documentElement.removeEventListener('click', this.__itemsOutsideClickListener); | ||
| this._tooltipController.setTarget(null); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -264,6 +285,31 @@ export const ItemsMixin = (superClass) => | |
| } | ||
|
|
||
| this.__showSubMenu(event); | ||
|
|
||
| const itemElement = event.target.closest(`${this._tagNamePrefix}-item`); | ||
| this._tooltipController.setTarget(itemElement); | ||
| this._tooltipController.open({ trigger: 'hover' }); | ||
| }); | ||
|
|
||
| overlay.addEventListener('mouseleave', (event) => { | ||
|
web-padawan marked this conversation as resolved.
|
||
| // Ignore events from the submenus | ||
| if (event.composedPath().includes(this._subMenu)) { | ||
| return; | ||
| } | ||
|
|
||
| this._tooltipController.close(); | ||
| }); | ||
|
|
||
| overlay.addEventListener('focusin', (event) => { | ||
| // Ignore events from the submenus | ||
| // Ignore non-keyboard focus changes (e.g. clicks). | ||
| if (event.composedPath().includes(this._subMenu) || !isKeyboardActive()) { | ||
| return; | ||
| } | ||
|
|
||
| const itemElement = event.target.closest(`${this._tagNamePrefix}-item`); | ||
| this._tooltipController.setTarget(itemElement); | ||
| this._tooltipController.open({ trigger: 'focus' }); | ||
| }); | ||
|
|
||
| overlay.addEventListener('keydown', (event) => { | ||
|
|
@@ -300,6 +346,11 @@ export const ItemsMixin = (superClass) => | |
| __initSubMenu() { | ||
| const subMenu = document.createElement(this.constructor.is); | ||
|
|
||
| // The slotted `<vaadin-tooltip>` lives on the outer `<vaadin-context-menu>` | ||
| // host. Its tooltip controller instance is shared across sub-menus to | ||
| // reuse the same tooltip element for items at every nesting level. | ||
| subMenu._tooltipController = this._tooltipController; | ||
|
|
||
| subMenu._modeless = true; | ||
| subMenu.openOn = 'opensubmenu'; | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.