mirror of
https://github.com/material-components/material-web.git
synced 2026-01-09 07:21:09 +08:00
refactor(menu)!: remove sub-menu-item in favor of sub-menu
BREAKING CHANGE: We have deleted `md-sub-menu-item`. Instead it is recommended to use `md-sub-menu` which can have `md-menu-item[slot=item]` and `md-menu[slot=menu]` slotted into it. This makes `sub-menu-item` accessible for screen readers using linear navigation PiperOrigin-RevId: 567706398
This commit is contained in:
parent
0384507447
commit
d6cbf74137
4
all.ts
4
all.ts
@ -40,7 +40,7 @@ import './list/list.js';
|
||||
import './list/list-item.js';
|
||||
import './menu/menu.js';
|
||||
import './menu/menu-item.js';
|
||||
import './menu/sub-menu-item.js';
|
||||
import './menu/sub-menu.js';
|
||||
import './progress/circular-progress.js';
|
||||
import './progress/linear-progress.js';
|
||||
import './radio/radio.js';
|
||||
@ -87,7 +87,7 @@ export * from './list/list.js';
|
||||
export * from './list/list-item.js';
|
||||
export * from './menu/menu.js';
|
||||
export * from './menu/menu-item.js';
|
||||
export * from './menu/sub-menu-item.js';
|
||||
export * from './menu/sub-menu.js';
|
||||
export * from './progress/circular-progress.js';
|
||||
export * from './progress/linear-progress.js';
|
||||
export * from './radio/radio.js';
|
||||
|
||||
@ -7,4 +7,4 @@
|
||||
import '@material/web/button/filled-button.js';
|
||||
import '@material/web/menu/menu.js';
|
||||
import '@material/web/menu/menu-item.js';
|
||||
import '@material/web/menu/sub-menu-item.js';
|
||||
import '@material/web/menu/sub-menu.js';
|
||||
@ -28,7 +28,7 @@ import './list/list.js';
|
||||
import './list/list-item.js';
|
||||
import './menu/menu.js';
|
||||
import './menu/menu-item.js';
|
||||
import './menu/sub-menu-item.js';
|
||||
import './menu/sub-menu.js';
|
||||
import './progress/circular-progress.js';
|
||||
import './progress/linear-progress.js';
|
||||
import './radio/radio.js';
|
||||
@ -55,7 +55,7 @@ export * from './list/list.js';
|
||||
export * from './list/list-item.js';
|
||||
export * from './menu/menu.js';
|
||||
export * from './menu/menu-item.js';
|
||||
export * from './menu/sub-menu-item.js';
|
||||
export * from './menu/sub-menu.js';
|
||||
export * from './progress/circular-progress.js';
|
||||
export * from './progress/linear-progress.js';
|
||||
export * from './radio/radio.js';
|
||||
|
||||
@ -122,7 +122,7 @@ const collection =
|
||||
}),
|
||||
|
||||
|
||||
// sub-menu-item knobs
|
||||
// sub-menu knobs
|
||||
new Knob('sub-menu', {ui: title()}),
|
||||
new Knob('submenu.anchorCorner', {
|
||||
defaultValue: Corner.START_END as Corner,
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
|
||||
|
||||
import '@material/web/menu/menu-item.js';
|
||||
import '@material/web/menu/sub-menu-item.js';
|
||||
import '@material/web/menu/sub-menu.js';
|
||||
import '@material/web/menu/menu.js';
|
||||
import '@material/web/button/filled-button.js';
|
||||
|
||||
@ -809,14 +809,14 @@ export abstract class Menu extends LitElement {
|
||||
|
||||
private handleDeactivateTypeahead(event: DeactivateTypeaheadEvent) {
|
||||
// stopPropagation so that this does not deactivate any typeaheads in menus
|
||||
// nested above it e.g. md-sub-menu-item
|
||||
// nested above it e.g. md-sub-menu
|
||||
event.stopPropagation();
|
||||
this.typeaheadActive = false;
|
||||
}
|
||||
|
||||
private handleActivateTypeahead(event: ActivateTypeaheadEvent) {
|
||||
// stopPropagation so that this does not activate any typeaheads in menus
|
||||
// nested above it e.g. md-sub-menu-item
|
||||
// nested above it e.g. md-sub-menu
|
||||
event.stopPropagation();
|
||||
this.typeaheadActive = true;
|
||||
}
|
||||
@ -833,8 +833,9 @@ export abstract class Menu extends LitElement {
|
||||
|
||||
close() {
|
||||
this.open = false;
|
||||
const slotItems = this.slotItems as Array<HTMLElement&{close?: () => void}>;
|
||||
slotItems.forEach(item => {
|
||||
const maybeSubmenu =
|
||||
this.slotItems as Array<HTMLElement&{close?: () => void}>;
|
||||
maybeSubmenu.forEach(item => {
|
||||
item.close?.();
|
||||
});
|
||||
}
|
||||
|
||||
@ -34,15 +34,10 @@ export class MenuItemEl extends ListItemEl implements MenuItem {
|
||||
|
||||
@state() protected hasFocusRing = false;
|
||||
|
||||
/**
|
||||
* Used for overriding e.g. sub-menu-item.
|
||||
*/
|
||||
protected keepOpenOnClick = false;
|
||||
|
||||
override readonly type: ListItemRole = 'menuitem';
|
||||
|
||||
protected override onClick() {
|
||||
if (this.keepOpen || this.keepOpenOnClick) return;
|
||||
if (this.keepOpen) return;
|
||||
|
||||
this.dispatchEvent(createDefaultCloseMenuEvent(
|
||||
this, {kind: CLOSE_REASON.CLICK_SELECTION}));
|
||||
|
||||
@ -25,10 +25,6 @@ interface MenuItemAdditions {
|
||||
* Whether or not the item is in the selected visual state.
|
||||
*/
|
||||
selected: boolean;
|
||||
/**
|
||||
* If it is a sub-menu-item, a method that can close the submenu.
|
||||
*/
|
||||
close?: () => void;
|
||||
/**
|
||||
* Focuses the item.
|
||||
*/
|
||||
|
||||
@ -100,7 +100,7 @@ export class SubMenu extends LitElement {
|
||||
|
||||
// Ensures that we deselect items when the menu closes and reactivate
|
||||
// typeahead when the menu closes, so that we do not have dirty state of
|
||||
// selected sub-menu-items when we reopen.
|
||||
// `sub-menu > menu-item[selected]` when we reopen.
|
||||
//
|
||||
// This cannot happen in `close()` because the menu may close via other
|
||||
// means Additionally, this cannot happen in onCloseSubmenu because
|
||||
@ -193,9 +193,9 @@ export class SubMenu extends LitElement {
|
||||
*
|
||||
* NOTE: We explicitly use mouse events and not pointer events because
|
||||
* pointer events apply to touch events. And if a user were to tap a
|
||||
* sub-menu-item, it would fire the "pointerenter", "pointerleave", "click"
|
||||
* events which would open the menu on click, and then set the timeout to
|
||||
* close the menu due to pointerleave.
|
||||
* sub-menu, it would fire the "pointerenter", "pointerleave", "click" events
|
||||
* which would open the menu on click, and then set the timeout to close the
|
||||
* menu due to pointerleave.
|
||||
*/
|
||||
protected onMouseenter = () => {
|
||||
clearTimeout(this.previousOpenTimeout);
|
||||
@ -218,9 +218,9 @@ export class SubMenu extends LitElement {
|
||||
*
|
||||
* NOTE: We explicitly use mouse events and not pointer events because
|
||||
* pointer events apply to touch events. And if a user were to tap a
|
||||
* sub-menu-item, it would fire the "pointerenter", "pointerleave", "click"
|
||||
* events which would open the menu on click, and then set the timeout to
|
||||
* close the menu due to pointerleave.
|
||||
* sub-menu, it would fire the "pointerenter", "pointerleave", "click" events
|
||||
* which would open the menu on click, and then set the timeout to close the
|
||||
* menu due to pointerleave.
|
||||
*/
|
||||
protected onMouseleave = () => {
|
||||
clearTimeout(this.previousCloseTimeout);
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {ListItemHarness} from '../../../list/internal/listitem/harness.js';
|
||||
|
||||
/**
|
||||
* Test harness for menu item.
|
||||
*/
|
||||
export class MenuItemHarness extends ListItemHarness {}
|
||||
@ -1,395 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {html, PropertyValues} from 'lit';
|
||||
import {property, query, queryAssignedElements, state} from 'lit/decorators.js';
|
||||
|
||||
import {List} from '../../../list/internal/list.js';
|
||||
import {createDeactivateItemsEvent, createRequestActivationEvent} from '../../../list/internal/listitem/list-item.js';
|
||||
import {MdRipple} from '../../../ripple/ripple.js';
|
||||
import {Corner, Menu} from '../menu.js';
|
||||
import {MenuItemEl} from '../menuitem/menu-item.js';
|
||||
import {CLOSE_REASON, CloseMenuEvent, createActivateTypeaheadEvent, createDeactivateTypeaheadEvent, KEYDOWN_CLOSE_KEYS, NAVIGABLE_KEY, SELECTION_KEY} from '../shared.js';
|
||||
|
||||
/**
|
||||
* @fires deactivate-items Requests the parent menu to deselect other items when
|
||||
* a submenu opens
|
||||
* @fires request-activation Requests the parent make the element focusable and
|
||||
* focuses the item.
|
||||
* @fires deactivate-typeahead Requests the parent menu to deactivate the
|
||||
* typeahead functionality when a submenu opens
|
||||
* @fires activate-typeahead Requests the parent menu to activate the typeahead
|
||||
* functionality when a submenu closes
|
||||
*/
|
||||
export class SubMenuItem extends MenuItemEl {
|
||||
/**
|
||||
* The anchorCorner to set on the submenu.
|
||||
*/
|
||||
@property({attribute: 'anchor-corner'})
|
||||
anchorCorner: Corner = Corner.START_END;
|
||||
/**
|
||||
* The menuCorner to set on the submenu.
|
||||
*/
|
||||
@property({attribute: 'menu-corner'}) menuCorner: Corner = Corner.START_START;
|
||||
/**
|
||||
* The delay between mouseenter and submenu opening.
|
||||
*/
|
||||
@property({type: Number, attribute: 'hover-open-delay'}) hoverOpenDelay = 400;
|
||||
/**
|
||||
* The delay between ponterleave and the submenu closing.
|
||||
*/
|
||||
@property({type: Number, attribute: 'hover-close-delay'})
|
||||
hoverCloseDelay = 400;
|
||||
|
||||
@state() protected submenuHover = false;
|
||||
|
||||
@query('md-ripple') private readonly rippleEl!: MdRipple;
|
||||
|
||||
@queryAssignedElements({slot: 'submenu', flatten: true})
|
||||
private readonly menus!: Menu[];
|
||||
|
||||
protected override keepOpenOnClick = true;
|
||||
private previousOpenTimeout = 0;
|
||||
private previousCloseTimeout = 0;
|
||||
|
||||
private get submenuEl(): Menu|undefined {
|
||||
return this.menus[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the default 400ms countdown to open the submenu.
|
||||
*
|
||||
* NOTE: We explicitly use mouse events and not pointer events because
|
||||
* pointer events apply to touch events. And if a user were to tap a
|
||||
* sub-menu-item, it would fire the "pointerenter", "pointerleave", "click"
|
||||
* events which would open the menu on click, and then set the timeout to
|
||||
* close the menu due to pointerleave.
|
||||
*/
|
||||
protected override onMouseenter = () => {
|
||||
clearTimeout(this.previousOpenTimeout);
|
||||
clearTimeout(this.previousCloseTimeout);
|
||||
if (this.submenuEl?.open) return;
|
||||
|
||||
// Open synchronously if delay is 0. (screenshot tests infra
|
||||
// would never resolve otherwise)
|
||||
if (!this.hoverOpenDelay) {
|
||||
this.show();
|
||||
} else {
|
||||
this.previousOpenTimeout = setTimeout(() => {
|
||||
this.show();
|
||||
}, this.hoverOpenDelay);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Starts the default 400ms countdown to close the submenu.
|
||||
*
|
||||
* NOTE: We explicitly use mouse events and not pointer events because
|
||||
* pointer events apply to touch events. And if a user were to tap a
|
||||
* sub-menu-item, it would fire the "pointerenter", "pointerleave", "click"
|
||||
* events which would open the menu on click, and then set the timeout to
|
||||
* close the menu due to pointerleave.
|
||||
*/
|
||||
protected override onMouseleave = () => {
|
||||
clearTimeout(this.previousCloseTimeout);
|
||||
clearTimeout(this.previousOpenTimeout);
|
||||
|
||||
// Close synchronously if delay is 0. (screenshot tests infra
|
||||
// would never resolve otherwise)
|
||||
if (!this.hoverCloseDelay) {
|
||||
this.close();
|
||||
} else {
|
||||
this.previousCloseTimeout = setTimeout(() => {
|
||||
this.close();
|
||||
}, this.hoverCloseDelay);
|
||||
}
|
||||
};
|
||||
|
||||
protected override onClick() {
|
||||
this.show();
|
||||
}
|
||||
|
||||
protected override getRenderClasses() {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'submenu-hover': this.submenuHover,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* On item keydown handles opening the submenu.
|
||||
*/
|
||||
protected override onKeydown(event: KeyboardEvent) {
|
||||
const shouldOpenSubmenu = this.isSubmenuOpenKey(event.code);
|
||||
|
||||
if (event.defaultPrevented || event.target !== this.listItemRoot) return;
|
||||
|
||||
const openedWithLR = shouldOpenSubmenu &&
|
||||
(NAVIGABLE_KEY.LEFT === event.code ||
|
||||
NAVIGABLE_KEY.RIGHT === event.code);
|
||||
|
||||
if (event.code === SELECTION_KEY.SPACE || openedWithLR) {
|
||||
// prevent space from scrolling and Left + Right from selecting previous /
|
||||
// next items or opening / closing parent menus. Only open the submenu.
|
||||
event.preventDefault();
|
||||
|
||||
if (openedWithLR) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldOpenSubmenu) {
|
||||
super.onKeydown(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const submenu = this.submenuEl;
|
||||
if (!submenu) return;
|
||||
|
||||
const submenuItems = submenu.items;
|
||||
const firstActivatableItem = List.getFirstActivatableItem(submenuItems);
|
||||
|
||||
if (firstActivatableItem) {
|
||||
this.show(() => {
|
||||
firstActivatableItem.tabIndex = 0;
|
||||
firstActivatableItem.focus();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the submenu at the end
|
||||
*/
|
||||
protected override renderEnd() {
|
||||
return html`${super.renderEnd()}${this.renderSubMenu()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the slot for the submenu.
|
||||
*/
|
||||
private renderSubMenu() {
|
||||
return html`<span class="submenu"><slot
|
||||
name="submenu"
|
||||
@mouseenter=${this.onSubmenuMouseEnter}
|
||||
@mouseleave=${this.onSubmenuMouseLeave}
|
||||
@keydown=${this.onSubMenuKeydown}
|
||||
@close-menu=${this.onCloseSubmenu}
|
||||
></slot></span>`;
|
||||
}
|
||||
|
||||
override firstUpdated(changed: PropertyValues<SubMenuItem>) {
|
||||
super.firstUpdated(changed);
|
||||
|
||||
const {handleEvent} = this.rippleEl;
|
||||
|
||||
// TODO(b/298476971): remove once ripple has a better solution
|
||||
this.rippleEl.handleEvent =
|
||||
callIfEventNotBubbledThroughMenu(this, handleEvent.bind(this.rippleEl));
|
||||
}
|
||||
|
||||
private onCloseSubmenu(event: CloseMenuEvent) {
|
||||
const {itemPath, reason} = event.detail;
|
||||
itemPath.push(this);
|
||||
|
||||
this.dispatchEvent(createActivateTypeaheadEvent());
|
||||
// Escape should only close one menu not all of the menus unlike space or
|
||||
// click selection which should close all menus.
|
||||
if (reason.kind === CLOSE_REASON.KEYDOWN &&
|
||||
reason.key === KEYDOWN_CLOSE_KEYS.ESCAPE) {
|
||||
event.stopPropagation();
|
||||
this.dispatchEvent(createRequestActivationEvent());
|
||||
return;
|
||||
}
|
||||
|
||||
this.dispatchEvent(createDeactivateItemsEvent());
|
||||
}
|
||||
|
||||
private onSubMenuKeydown(event: KeyboardEvent) {
|
||||
if (event.defaultPrevented) return;
|
||||
const {close: shouldClose, keyCode} = this.isSubmenuCloseKey(event.code);
|
||||
if (!shouldClose) return;
|
||||
|
||||
// Communicate that it's handled so that we don't accidentally close every
|
||||
// parent menu. Additionally, we want to isolate things like the typeahead
|
||||
// keydowns from bubbling up to the parent menu and confounding things.
|
||||
event.preventDefault();
|
||||
|
||||
if (keyCode === NAVIGABLE_KEY.LEFT || keyCode === NAVIGABLE_KEY.RIGHT) {
|
||||
// Prevent this from bubbling to parents
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
this.close(() => {
|
||||
List.deactivateActiveItem(this.submenuEl!.items);
|
||||
this.listItemRoot?.focus();
|
||||
this.tabIndex = 0;
|
||||
this.focus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the submenu.
|
||||
*
|
||||
* @param onOpened A function to call after the menu is opened.
|
||||
*/
|
||||
show(onOpened = () => {}) {
|
||||
const menu = this.submenuEl;
|
||||
if (!menu || menu.open) return;
|
||||
|
||||
// Ensures that we deselect items when the menu closes and reactivate
|
||||
// typeahead when the menu closes, so that we do not have dirty state of
|
||||
// selected sub-menu-items when we reopen.
|
||||
//
|
||||
// This cannot happen in `close()` because the menu may close via other
|
||||
// means Additionally, this cannot happen in onCloseSubmenu because
|
||||
// `close-menu` may not be called via focusout of outside click and not
|
||||
// triggered by an item
|
||||
menu.addEventListener('closed', () => {
|
||||
this.dispatchEvent(createActivateTypeaheadEvent());
|
||||
this.dispatchEvent(createDeactivateItemsEvent());
|
||||
}, {once: true});
|
||||
menu.quick = true;
|
||||
// Submenus are in overflow when not fixed. Can remove once we have native
|
||||
// popup support
|
||||
menu.hasOverflow = true;
|
||||
menu.anchorCorner = this.anchorCorner;
|
||||
menu.menuCorner = this.menuCorner;
|
||||
menu.anchorElement = this;
|
||||
menu.defaultFocus = 'first-item';
|
||||
// This is required in the case where we have a leaf menu open and and the
|
||||
// user hovers a parent menu's item which is not an md-sub-menu item.
|
||||
// If this were set to true, then the menu would close and focus would be
|
||||
// lost. That means the focusout event would have a `relatedTarget` of
|
||||
// `null` since nothing in the menu would be focused anymore due to the
|
||||
// leaf menu closing. restoring focus ensures that we keep focus in the
|
||||
// submenu tree.
|
||||
menu.skipRestoreFocus = false;
|
||||
|
||||
// Menu could already be opened because of mouse interaction
|
||||
const menuAlreadyOpen = menu.open;
|
||||
menu.show();
|
||||
|
||||
// Deactivate other items. This can be the case if the user has tabbed
|
||||
// around the menu and then mouses over an md-sub-menu.
|
||||
this.dispatchEvent(createDeactivateItemsEvent());
|
||||
this.dispatchEvent(createDeactivateTypeaheadEvent());
|
||||
this.selected = true;
|
||||
|
||||
// This is the case of mouse hovering when already opened via keyboard or
|
||||
// vice versa
|
||||
if (menuAlreadyOpen) {
|
||||
onOpened();
|
||||
} else {
|
||||
menu.addEventListener('opened', onOpened, {once: true});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the submenu.
|
||||
*
|
||||
* @param onClosed A function to call after the menu is closed.
|
||||
*/
|
||||
close(onClosed = () => {}) {
|
||||
const menu = this.submenuEl;
|
||||
if (!menu || !menu.open) return;
|
||||
|
||||
this.dispatchEvent(createActivateTypeaheadEvent());
|
||||
menu.quick = true;
|
||||
menu.close();
|
||||
this.dispatchEvent(createDeactivateItemsEvent());
|
||||
menu.addEventListener('closed', onClosed, {once: true});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given KeyboardEvent code is one that should open
|
||||
* the submenu. This is RTL-aware. By default, left, right, space, or enter.
|
||||
*
|
||||
* @param code The native KeyboardEvent code.
|
||||
* @return Whether or not the key code should open the submenu.
|
||||
*/
|
||||
private isSubmenuOpenKey(code: string) {
|
||||
const isRtl = getComputedStyle(this).direction === 'rtl';
|
||||
const arrowEnterKey = isRtl ? NAVIGABLE_KEY.LEFT : NAVIGABLE_KEY.RIGHT;
|
||||
switch (code) {
|
||||
case arrowEnterKey:
|
||||
case SELECTION_KEY.SPACE:
|
||||
case SELECTION_KEY.ENTER:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the given KeyboardEvent code is one that should close
|
||||
* the submenu. This is RTL-aware. By default right, left, or escape.
|
||||
*
|
||||
* @param code The native KeyboardEvent code.
|
||||
* @return Whether or not the key code should close the submenu.
|
||||
*/
|
||||
private isSubmenuCloseKey(code: string) {
|
||||
const isRtl = getComputedStyle(this).direction === 'rtl';
|
||||
const arrowEnterKey = isRtl ? NAVIGABLE_KEY.RIGHT : NAVIGABLE_KEY.LEFT;
|
||||
switch (code) {
|
||||
case arrowEnterKey:
|
||||
case KEYDOWN_CLOSE_KEYS.ESCAPE:
|
||||
return {close: true, keyCode: code} as const;
|
||||
default:
|
||||
return {close: false} as const;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: We explicitly use mouse events and not pointer events because
|
||||
* pointer events apply to touch events. And if a user were to tap a
|
||||
* sub-menu-item, it would fire the "pointerenter", "pointerleave", "click"
|
||||
* events which would open the menu on click, and then set the timeout to
|
||||
* close the menu due to pointerleave.
|
||||
*/
|
||||
private onSubmenuMouseEnter() {
|
||||
this.submenuHover = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* NOTE: We explicitly use mouse events and not pointer events because
|
||||
* pointer events apply to touch events. And if a user were to tap a
|
||||
* sub-menu-item, it would fire the "pointerenter", "pointerleave", "click"
|
||||
* events which would open the menu on click, and then set the timeout to
|
||||
* close the menu due to pointerleave.
|
||||
*/
|
||||
private onSubmenuMouseLeave() {
|
||||
this.submenuHover = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the given callback if the event has not bubbled through an md-menu.
|
||||
*/
|
||||
function callIfEventNotBubbledThroughMenu(
|
||||
menuItem: HTMLElement, callback: (event: Event) => Promise<void>) {
|
||||
return async (event: Event) => {
|
||||
const path = event.composedPath();
|
||||
|
||||
for (const element of path) {
|
||||
const isMenu =
|
||||
!!(element as Element | HTMLElement)?.hasAttribute?.('md-menu');
|
||||
|
||||
// The path has a submenu, do not invoke callback;
|
||||
if (isMenu) return;
|
||||
|
||||
// We have reached the target submenu. Any other menus in path are
|
||||
// ancestors.
|
||||
if (element === menuItem) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// invoke the callback because we have not run into a submenu.
|
||||
await callback(event);
|
||||
};
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {customElement} from 'lit/decorators.js';
|
||||
|
||||
import {styles as listItemForcedColorsStyles} from '../list/internal/listitem/forced-colors-styles.css.js';
|
||||
import {styles as listItemStyles} from '../list/internal/listitem/list-item-styles.css.js';
|
||||
|
||||
import {styles as forcedColorsStyles} from './internal/menuitem/forced-colors-styles.css.js';
|
||||
import {styles} from './internal/menuitem/menu-item-styles.css.js';
|
||||
import {SubMenuItem} from './internal/submenuitem/sub-menu-item.js';
|
||||
|
||||
export {ListItem} from '../list/internal/listitem/list-item.js';
|
||||
export {CloseMenuEvent, MenuItem} from './internal/shared.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'md-sub-menu-item': MdSubMenuItem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Menus display a list of choices on a temporary surface.
|
||||
* @deprecated Use <md-submenu>
|
||||
*
|
||||
* @description
|
||||
* Menu items are the selectable choices within the menu. Menu items must
|
||||
* implement the `Menu` interface and also have the `md-menu`
|
||||
* attribute. Additionally menu items are list items so they must also have the
|
||||
* `md-list-item` attribute.
|
||||
*
|
||||
* Menu items can control a menu by selectively firing the `close-menu` and
|
||||
* `deselect-items` events.
|
||||
*
|
||||
* This menu item will open a sub-menu that is slotted in the `submenu` slot.
|
||||
* Additionally, the containing menu must either have `has-overflow` or `fixed`
|
||||
* set to `true` in order to display the containing menu properly.
|
||||
*
|
||||
* @example
|
||||
* ```html
|
||||
* <div style="position:relative;">
|
||||
* <button
|
||||
* id="anchor"
|
||||
* @click=${() => this.menuRef.value.show()}>
|
||||
* Click to open menu
|
||||
* </button>
|
||||
* <!--
|
||||
* `has-overflow` is required when using a submenu which overflows the
|
||||
* menu's contents
|
||||
* -->
|
||||
* <md-menu anchor="anchor" has-overflow ${ref(menuRef)}>
|
||||
* <md-menu-item headline="This is a headline"></md-menu-item>
|
||||
* <md-sub-menu>
|
||||
* <md-menu-item
|
||||
* slot="item"
|
||||
* headline="this is a submenu item">
|
||||
* </md-menu-item>
|
||||
* <md-menu slot="menu">
|
||||
* <md-menu-item headline="This is an item inside a submenu">
|
||||
* </md-menu-item>
|
||||
* </md-menu>
|
||||
* </md-sub-menu>
|
||||
* </md-menu>
|
||||
* </div>
|
||||
* ```
|
||||
*
|
||||
* @final
|
||||
* @suppress {visibility}
|
||||
*/
|
||||
@customElement('md-sub-menu-item')
|
||||
export class MdSubMenuItem extends SubMenuItem {
|
||||
static override styles =
|
||||
[listItemStyles, styles, listItemForcedColorsStyles, forcedColorsStyles];
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user