mirror of
https://github.com/material-components/material-web.git
synced 2026-01-09 07:21:09 +08:00
refactor(list,menu)!: add href to (list|menu)-item and remove (menu|list)-item-link
BREAKING CHANGE: list-item-link and menu-item-link have been removed and their functionality has been added to menu-item and list-item respectively. PiperOrigin-RevId: 553894393
This commit is contained in:
parent
28af606b40
commit
09cb6da8fb
4
all.ts
4
all.ts
@ -38,10 +38,8 @@ import './iconbutton/icon-button.js';
|
||||
import './iconbutton/outlined-icon-button.js';
|
||||
import './list/list.js';
|
||||
import './list/list-item.js';
|
||||
import './list/list-item-link.js';
|
||||
import './menu/menu.js';
|
||||
import './menu/menu-item.js';
|
||||
import './menu/menu-item-link.js';
|
||||
import './menu/sub-menu-item.js';
|
||||
import './progress/circular-progress.js';
|
||||
import './progress/linear-progress.js';
|
||||
@ -86,10 +84,8 @@ export * from './iconbutton/icon-button.js';
|
||||
export * from './iconbutton/outlined-icon-button.js';
|
||||
export * from './list/list.js';
|
||||
export * from './list/list-item.js';
|
||||
export * from './list/list-item-link.js';
|
||||
export * from './menu/menu.js';
|
||||
export * from './menu/menu-item.js';
|
||||
export * from './menu/menu-item-link.js';
|
||||
export * from './menu/sub-menu-item.js';
|
||||
export * from './progress/circular-progress.js';
|
||||
export * from './progress/linear-progress.js';
|
||||
|
||||
@ -59,13 +59,13 @@
|
||||
<md-list listtabindex="-1">
|
||||
{% for component in collections.component|filtersort('data.name') %}
|
||||
<li>
|
||||
<md-list-item-link
|
||||
<md-list-item
|
||||
href="{{ component.url }}"
|
||||
headline="{{ component.data.name }}"
|
||||
{% if component.url == page.url %}selected{% endif %}
|
||||
data-role=""
|
||||
itemtabindex="0"
|
||||
></md-list-item-link>
|
||||
></md-list-item>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</md-list>
|
||||
|
||||
@ -85,18 +85,18 @@ a:focus-visible {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
nav-drawer md-list-item-link[selected] {
|
||||
nav-drawer md-list-item[href][selected] {
|
||||
--md-list-item-list-item-container-color: var(
|
||||
--md-sys-color-surface-container-highest
|
||||
);
|
||||
}
|
||||
|
||||
@media (forced-colors: active) {
|
||||
nav-drawer md-list-item-link[selected] {
|
||||
nav-drawer md-list-item[href][selected] {
|
||||
border: 4px double CanvasText;
|
||||
}
|
||||
|
||||
nav-drawer md-list-item-link {
|
||||
nav-drawer md-list-item[href] {
|
||||
border-radius: var(--catalog-shape-xl);
|
||||
border: 1px solid CanvasText;
|
||||
}
|
||||
@ -110,12 +110,12 @@ md-list {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
md-list-item-link {
|
||||
md-list-item[href] {
|
||||
margin-block: var(--catalog-spacing-m);
|
||||
display: block;
|
||||
}
|
||||
|
||||
md-list-item-link:first-of-type {
|
||||
md-list-item[href]:first-of-type {
|
||||
margin-block: 4px;
|
||||
}
|
||||
|
||||
|
||||
@ -6,4 +6,4 @@
|
||||
|
||||
import '../components/nav-drawer.js';
|
||||
import '../components/top-app-bar.js';
|
||||
import '@material/web/list/list-item-link.js';
|
||||
import '@material/web/list/list-item.js';
|
||||
|
||||
@ -14,7 +14,7 @@ import './components/theme-changer.js';
|
||||
import '@material/web/menu/menu.js';
|
||||
import '@material/web/checkbox/checkbox.js';
|
||||
import '@material/web/list/list.js';
|
||||
import '@material/web/list/list-item-link.js';
|
||||
import '@material/web/list/list-item.js';
|
||||
import '@material/web/progress/circular-progress.js';
|
||||
import '@material/web/tabs/tabs.js';
|
||||
import '@material/web/tabs/tab.js';
|
||||
|
||||
@ -26,10 +26,8 @@ import './icon/icon.js';
|
||||
import './iconbutton/icon-button.js';
|
||||
import './list/list.js';
|
||||
import './list/list-item.js';
|
||||
import './list/list-item-link.js';
|
||||
import './menu/menu.js';
|
||||
import './menu/menu-item.js';
|
||||
import './menu/menu-item-link.js';
|
||||
import './menu/sub-menu-item.js';
|
||||
import './progress/circular-progress.js';
|
||||
import './progress/linear-progress.js';
|
||||
@ -55,10 +53,8 @@ export * from './icon/icon.js';
|
||||
export * from './iconbutton/icon-button.js';
|
||||
export * from './list/list.js';
|
||||
export * from './list/list-item.js';
|
||||
export * from './list/list-item-link.js';
|
||||
export * from './menu/menu.js';
|
||||
export * from './menu/menu-item.js';
|
||||
export * from './menu/menu-item-link.js';
|
||||
export * from './menu/sub-menu-item.js';
|
||||
export * from './progress/circular-progress.js';
|
||||
export * from './progress/linear-progress.js';
|
||||
|
||||
@ -48,17 +48,14 @@ const collection =
|
||||
new Knob('supportingText', {ui: textInput(), defaultValue: ''}),
|
||||
new Knob('trailingSupportingText', {ui: textInput(), defaultValue: ''}),
|
||||
new Knob('itemTabIndex', {ui: numberInput(), defaultValue: 0}),
|
||||
new Knob('href', {ui: textInput(), defaultValue: 'https://google.com'}),
|
||||
new Knob('target', {ui: textInput(), defaultValue: '_blank'}),
|
||||
new Knob('link end icon', {ui: textInput(), defaultValue: 'open_in_new'}),
|
||||
|
||||
new Knob('data-variant=icon', {ui: title()}),
|
||||
new Knob('start icon', {ui: textInput(), defaultValue: 'account_circle'}),
|
||||
new Knob('end icon', {ui: textInput(), defaultValue: 'check_circle'}),
|
||||
|
||||
|
||||
new Knob('data-variant=link', {ui: title()}),
|
||||
new Knob('href', {ui: textInput(), defaultValue: 'https://google.com'}),
|
||||
new Knob('target', {ui: textInput(), defaultValue: '_blank'}),
|
||||
new Knob('link end icon', {ui: textInput(), defaultValue: 'open_in_new'}),
|
||||
|
||||
new Knob('data-variant=avatar', {ui: title()}),
|
||||
new Knob('avatar img', {ui: textInput(), defaultValue: AVATAR_URL}),
|
||||
new Knob('avatar label', {ui: textInput(), defaultValue: 'EM'}),
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import '@material/web/list/list-item-link.js';
|
||||
import '@material/web/list/list-item.js';
|
||||
import '@material/web/divider/divider.js';
|
||||
import '@material/web/list/list.js';
|
||||
@ -110,7 +109,7 @@ const standard: MaterialStoryInit<StoryKnobs> = {
|
||||
</md-icon>
|
||||
</md-list-item>
|
||||
|
||||
<md-list-item-link
|
||||
<md-list-item
|
||||
.headline=${headline}
|
||||
.supportingText=${supportingText}
|
||||
.multiLineSupportingText=${multiLineSupportingText}
|
||||
@ -119,11 +118,11 @@ const standard: MaterialStoryInit<StoryKnobs> = {
|
||||
.noninteractive=${noninteractive}
|
||||
.itemTabIndex=${itemTabIndex}
|
||||
.href=${href}
|
||||
.target=${target}
|
||||
.target=${target as '' | '_blank' | '_parent' | '_self' | '_top'}
|
||||
.active=${active}>
|
||||
<md-icon data-variant="icon" slot="end">${
|
||||
knobs['link end icon']}</md-icon>
|
||||
</md-list-item-link>
|
||||
</md-list-item>
|
||||
|
||||
<md-divider></md-divider>
|
||||
|
||||
|
||||
@ -10,6 +10,7 @@ import '../../../focus/md-focus-ring.js';
|
||||
import {html, LitElement, nothing, PropertyValues, TemplateResult} from 'lit';
|
||||
import {property, query} from 'lit/decorators.js';
|
||||
import {classMap} from 'lit/directives/class-map.js';
|
||||
import {html as staticHtml, literal} from 'lit/static-html.js';
|
||||
|
||||
import {ARIAMixinStrict} from '../../../internal/aria/aria.js';
|
||||
import {requestUpdateOnAriaChange} from '../../../internal/aria/delegate.js';
|
||||
@ -82,10 +83,11 @@ export class ListItemEl extends LitElement implements ListItem {
|
||||
@property({type: Boolean, reflect: true}) active = false;
|
||||
|
||||
/**
|
||||
* Sets the role of the list item. Set to '' to clear the role.
|
||||
* Sets the role of the list item. Set to 'nothing' to clear the role. This
|
||||
* property will be ignored if `href` is set since the underlying element will
|
||||
* be a native anchor tag.
|
||||
*/
|
||||
@property()
|
||||
type: ListItemRole = 'listitem';
|
||||
@property() type: ListItemRole = 'listitem';
|
||||
|
||||
/**
|
||||
* READONLY. Sets the `md-list-item` attribute on the element.
|
||||
@ -93,6 +95,17 @@ export class ListItemEl extends LitElement implements ListItem {
|
||||
@property({type: Boolean, attribute: 'md-list-item', reflect: true})
|
||||
isListItem = true;
|
||||
|
||||
/**
|
||||
* Sets the underlying `HTMLAnchorElement`'s `href` resource attribute.
|
||||
*/
|
||||
@property() href = '';
|
||||
|
||||
/**
|
||||
* Sets the underlying `HTMLAnchorElement`'s `target` attribute when `href` is
|
||||
* set.
|
||||
*/
|
||||
@property() target: '_blank'|'_parent'|'_self'|'_top'|'' = '';
|
||||
|
||||
@query('.list-item') protected readonly listItemRoot!: HTMLElement|null;
|
||||
|
||||
/**
|
||||
@ -134,19 +147,25 @@ export class ListItemEl extends LitElement implements ListItem {
|
||||
* @param content the child content of the list item.
|
||||
*/
|
||||
protected renderListItem(content: unknown) {
|
||||
return html`
|
||||
<li
|
||||
const isAnchor = !!this.href;
|
||||
const tag = isAnchor ? literal`a` : literal`li`;
|
||||
const role = isAnchor || this.type === 'none' ? nothing : this.type;
|
||||
const target = isAnchor && !!this.target ? this.target : nothing;
|
||||
return staticHtml`
|
||||
<${tag}
|
||||
id="item"
|
||||
tabindex=${this.disabled ? -1 : this.itemTabIndex}
|
||||
role=${this.type === 'none' ? nothing : this.type}
|
||||
role=${role}
|
||||
aria-selected=${(this as ARIAMixinStrict).ariaSelected || nothing}
|
||||
aria-checked=${(this as ARIAMixinStrict).ariaChecked || nothing}
|
||||
class="list-item ${classMap(this.getRenderClasses())}"
|
||||
href=${this.href || nothing}
|
||||
target=${target}
|
||||
@click=${this.onClick}
|
||||
@pointerenter=${this.onPointerenter}
|
||||
@pointerleave=${this.onPointerleave}
|
||||
@keydown=${this.onKeydown}
|
||||
>${content}</li>
|
||||
>${content}</${tag}>
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {nothing} from 'lit';
|
||||
import {property} from 'lit/decorators.js';
|
||||
|
||||
import {ListItemLink} from './list-item-link.js';
|
||||
|
||||
// tslint:disable-next-line:enforce-comments-on-exported-symbols
|
||||
export class ListItemLinkOnly extends ListItemLink {
|
||||
/**
|
||||
* Removes the hover and click ripples from the item when true. Clicking the
|
||||
* link will still cause link navigation.
|
||||
*/
|
||||
@property({type: Boolean}) noninteractive = false;
|
||||
|
||||
override getRenderClasses() {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'noninteractive': this.noninteractive,
|
||||
};
|
||||
}
|
||||
|
||||
override renderRipple() {
|
||||
return this.noninteractive ? nothing : super.renderRipple();
|
||||
}
|
||||
|
||||
override renderFocusRing() {
|
||||
return this.noninteractive ? nothing : super.renderFocusRing();
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {html, nothing} from 'lit';
|
||||
import {property} from 'lit/decorators.js';
|
||||
import {classMap} from 'lit/directives/class-map.js';
|
||||
|
||||
import {ARIAMixinStrict} from '../../../internal/aria/aria.js';
|
||||
import {ListItemEl, ListItemRole} from '../listitem/list-item.js';
|
||||
|
||||
type LinkTarget = '_blank'|'_parent'|'_self'|'_top';
|
||||
|
||||
// tslint:disable-next-line:enforce-comments-on-exported-symbols
|
||||
export class ListItemLink extends ListItemEl {
|
||||
/**
|
||||
* Sets the underlying `HTMLAnchorElement`'s `href` resource attribute.
|
||||
*/
|
||||
@property() href!: string;
|
||||
|
||||
override type: ListItemRole = 'none';
|
||||
|
||||
/**
|
||||
* Sets the underlying `HTMLAnchorElement`'s `target` attribute.
|
||||
*/
|
||||
@property() target!: string;
|
||||
protected override renderListItem(content: unknown) {
|
||||
return html`
|
||||
<a
|
||||
id="item"
|
||||
tabindex=${this.disabled ? -1 : this.itemTabIndex}
|
||||
role=${this.type === 'none' ? nothing : this.type}
|
||||
aria-selected=${(this as ARIAMixinStrict).ariaSelected || nothing}
|
||||
aria-checked=${(this as ARIAMixinStrict).ariaChecked || nothing}
|
||||
class="list-item ${classMap(this.getRenderClasses())}"
|
||||
href=${this.href}
|
||||
target=${this.target as LinkTarget || nothing}
|
||||
@click=${this.onClick}
|
||||
@pointerenter=${this.onPointerenter}
|
||||
@pointerleave=${this.onPointerleave}
|
||||
@keydown=${this.onKeydown}
|
||||
>${content}</a>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -1,61 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {customElement} from 'lit/decorators.js';
|
||||
|
||||
import {styles as forcedColors} from './internal/listitem/forced-colors-styles.css.js';
|
||||
import {styles} from './internal/listitem/list-item-styles.css.js';
|
||||
import {ListItemLinkOnly as ListItemLink} from './internal/listitemlink/list-item-link-only.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'md-list-item-link': MdListItemLink;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary
|
||||
* Lists are continuous, vertical indexes of text or images. Items are placed
|
||||
* inside the list. This is a linkable variant.
|
||||
*
|
||||
* @description
|
||||
* Lists consist of one or more list items, and can contain actions represented
|
||||
* by icons and text. List items come in three sizes: one-line, two-line, and
|
||||
* three-line.
|
||||
*
|
||||
* __Takeaways:__
|
||||
*
|
||||
* - Lists should be sorted in logical ways that make content easy to scan, such
|
||||
* as alphabetical, numerical, chronological, or by user preference.
|
||||
* - Lists present content in a way that makes it easy to identify a specific
|
||||
* item in a collection and act on it.
|
||||
* - Lists should present icons, text, and actions in a consistent format.
|
||||
*
|
||||
* Example slottable child variants are:
|
||||
*
|
||||
* - `video[data-variant=video]`
|
||||
* - `img,span[data-variant=avatar]`
|
||||
* - `img[data-variant=image]`
|
||||
* - `md-icon[data-variant=icon]`
|
||||
*
|
||||
* @example
|
||||
* ```html
|
||||
* <md-list-item-link
|
||||
* headline="User Name"
|
||||
* supportingText="user@name.com"
|
||||
* href="/accounts">
|
||||
* <md-icon data-variant="icon" slot="start">account_circle</md-icon>
|
||||
* <md-icon data-variant="icon" slot="end">open_in_new</md-icon>
|
||||
* </md-list-item-link>
|
||||
* ```
|
||||
*
|
||||
* @final
|
||||
* @suppress {visibility}
|
||||
*/
|
||||
@customElement('md-list-item-link')
|
||||
export class MdListItemLink extends ListItemLink {
|
||||
static override styles = [styles, forcedColors];
|
||||
}
|
||||
@ -7,7 +7,6 @@
|
||||
// import 'jasmine'; (google3-only)
|
||||
import './list.js';
|
||||
import './list-item.js';
|
||||
import './list-item-link.js';
|
||||
|
||||
import {html} from 'lit';
|
||||
|
||||
@ -1101,65 +1100,52 @@ describe('<md-list-item>', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('<md-list-item-link>', () => {
|
||||
describe('<md-list-item> link', () => {
|
||||
const env = new Environment();
|
||||
|
||||
describe('.styles', () => {
|
||||
createTokenTests(MdListItem.styles);
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
// We repeat these tests because technically list-item-link has its own
|
||||
// implementation of these methods. Everything else is shared.
|
||||
it('disabled overrides tabIndex', async () => {
|
||||
const root = env.render(html`<md-list-item-link></md-list-item-link>`);
|
||||
it('setting href renders an anchor tag', async () => {
|
||||
const root = env.render(
|
||||
html`<md-list-item href="https://google.com"></md-list-item>`);
|
||||
|
||||
const listItem = root.querySelector('md-list-item-link')!;
|
||||
const listItem = root.querySelector('md-list-item')!;
|
||||
|
||||
await env.waitForStability();
|
||||
await env.waitForStability();
|
||||
|
||||
const internalRoot =
|
||||
listItem.renderRoot.querySelector('#item') as HTMLElement;
|
||||
const internalRoot =
|
||||
listItem.renderRoot.querySelector('#item') as HTMLElement;
|
||||
|
||||
expect(internalRoot.tabIndex).toBe(-1);
|
||||
expect(internalRoot.tagName).toBe('A');
|
||||
});
|
||||
|
||||
it('setting type and href does not render a role', async () => {
|
||||
const root = env.render(
|
||||
html`<md-list-item type="menuitem" href="https://google.com"></md-list-item>`);
|
||||
|
||||
listItem.itemTabIndex = 2;
|
||||
const listItem = root.querySelector('md-list-item')!;
|
||||
|
||||
await env.waitForStability();
|
||||
await env.waitForStability();
|
||||
|
||||
expect(listItem.disabled).toBeFalse();
|
||||
expect(internalRoot.tabIndex).toBe(2);
|
||||
const internalRoot =
|
||||
listItem.renderRoot.querySelector('#item') as HTMLElement;
|
||||
|
||||
listItem.disabled = true;
|
||||
expect(internalRoot.hasAttribute('role')).toBe(false);
|
||||
});
|
||||
|
||||
it('setting target without href renders nothing', async () => {
|
||||
const root = env.render(
|
||||
html`<md-list-item target="_blank"></md-list-item>`);
|
||||
|
||||
await env.waitForStability();
|
||||
const listItem = root.querySelector('md-list-item')!;
|
||||
|
||||
expect(listItem.disabled).toBeTrue();
|
||||
expect(internalRoot.tabIndex).toBe(-1);
|
||||
});
|
||||
await env.waitForStability();
|
||||
|
||||
it('ripple and focus ring not rendered on noninteractive', async () => {
|
||||
const root = env.render(html`<md-list-item-link></md-list-item-link>`);
|
||||
const internalRoot =
|
||||
listItem.renderRoot.querySelector('#item') as HTMLElement;
|
||||
|
||||
const listItem = root.querySelector('md-list-item-link')!;
|
||||
|
||||
await env.waitForStability();
|
||||
|
||||
let rippleEl = listItem.renderRoot.querySelector('md-ripple');
|
||||
let focusRingEl = listItem.renderRoot.querySelector('md-focus-ring');
|
||||
|
||||
expect(rippleEl).toBeTruthy();
|
||||
expect(focusRingEl).toBeTruthy();
|
||||
|
||||
listItem.noninteractive = true;
|
||||
|
||||
await env.waitForStability();
|
||||
|
||||
rippleEl = listItem.renderRoot.querySelector('md-ripple');
|
||||
focusRingEl = listItem.renderRoot.querySelector('md-focus-ring');
|
||||
|
||||
expect(rippleEl).toBeNull();
|
||||
expect(focusRingEl).toBeNull();
|
||||
});
|
||||
expect(internalRoot.hasAttribute('target')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -108,9 +108,6 @@ const collection =
|
||||
defaultValue: false,
|
||||
ui: boolInput(),
|
||||
}),
|
||||
|
||||
// menu-item-link knobs
|
||||
new Knob('menu-item-link', {ui: title()}),
|
||||
new Knob('href', {
|
||||
defaultValue: 'https://google.com',
|
||||
ui: textInput(),
|
||||
|
||||
@ -6,7 +6,6 @@
|
||||
|
||||
|
||||
import '@material/web/menu/menu-item.js';
|
||||
import '@material/web/menu/menu-item-link.js';
|
||||
import '@material/web/menu/sub-menu-item.js';
|
||||
import '@material/web/menu/menu.js';
|
||||
import '@material/web/button/filled-button.js';
|
||||
@ -41,8 +40,6 @@ export interface StoryKnobs {
|
||||
'menu-item': void;
|
||||
keepOpen: boolean;
|
||||
disabled: boolean;
|
||||
|
||||
'menu-item-link': void;
|
||||
href: string;
|
||||
target: string;
|
||||
'link icon': string;
|
||||
@ -124,7 +121,7 @@ const standard: MaterialStoryInit<StoryKnobs> = {
|
||||
};
|
||||
|
||||
const linkable: MaterialStoryInit<StoryKnobs> = {
|
||||
name: '<md-menu-item-link>',
|
||||
name: '<md-menu-item>',
|
||||
styles: sharedStyle,
|
||||
render(knobs) {
|
||||
const showMenu = () => {
|
||||
@ -262,15 +259,15 @@ function renderItems(names: string[], knobs: StoryKnobs) {
|
||||
|
||||
function renderLinkableItems(names: string[], knobs: StoryKnobs) {
|
||||
return names.map(name => html`
|
||||
<md-menu-item-link
|
||||
<md-menu-item
|
||||
headline=${name}
|
||||
.disabled=${knobs.disabled}
|
||||
.target=${knobs.target}
|
||||
.target=${knobs.target as '' | '_blank' | '_parent' | '_self' | '_top'}
|
||||
.href=${knobs.href}>
|
||||
<md-icon data-variant="icon" slot="end">
|
||||
${knobs['link icon']}
|
||||
</md-icon>
|
||||
</md-menu-item-link>
|
||||
</md-menu-item>
|
||||
`);
|
||||
}
|
||||
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {property} from 'lit/decorators.js';
|
||||
|
||||
import {ListItemLink} from '../../../list/internal/listitemlink/list-item-link.js';
|
||||
import {CLOSE_REASON, createDefaultCloseMenuEvent, isClosableKey, MenuItem, SELECTION_KEY} from '../shared.js';
|
||||
|
||||
/**
|
||||
* @fires close-menu {CloseMenuEvent}
|
||||
*/
|
||||
export class MenuItemLink extends ListItemLink implements MenuItem {
|
||||
/**
|
||||
* READONLY: self-identifies as a menu item and sets its identifying attribute
|
||||
*/
|
||||
@property({type: Boolean, attribute: 'md-menu-item', reflect: true})
|
||||
isMenuItem = true;
|
||||
|
||||
/**
|
||||
* Keeps the menu open if clicked or keyboard selected.
|
||||
*/
|
||||
@property({type: Boolean, attribute: 'keep-open'}) keepOpen = false;
|
||||
|
||||
protected keepOpenOnClick = false;
|
||||
|
||||
protected override onClick() {
|
||||
if (this.keepOpen || this.keepOpenOnClick) return;
|
||||
|
||||
this.dispatchEvent(createDefaultCloseMenuEvent(
|
||||
this, {kind: CLOSE_REASON.CLICK_SELECTION}));
|
||||
}
|
||||
|
||||
protected override onKeydown(event: KeyboardEvent) {
|
||||
if (this.keepOpen) return;
|
||||
|
||||
const keyCode = event.code;
|
||||
// Do not preventDefault on enter or else it will prevent from opening links
|
||||
if (!event.defaultPrevented && isClosableKey(keyCode) &&
|
||||
keyCode !== SELECTION_KEY.ENTER) {
|
||||
event.preventDefault();
|
||||
this.dispatchEvent(createDefaultCloseMenuEvent(
|
||||
this, {kind: CLOSE_REASON.KEYDOWN, key: keyCode}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,47 +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 {MenuItemLink} from './internal/menuitemlink/menu-item-link.js';
|
||||
|
||||
export {ListItem} from '../list/internal/listitem/list-item.js';
|
||||
export {CloseMenuEvent, MenuItem} from './internal/shared.js';
|
||||
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'md-menu-item-link': MdMenuItemLink;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Menus display a list of choices on a temporary surface.
|
||||
*
|
||||
* @description
|
||||
* Menu items are the selectable choices within the menu. Menu items must
|
||||
* implement the `MenuItem` interface and also have the `md-menu-item`
|
||||
* 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 is a linkable variant.
|
||||
*
|
||||
* @final
|
||||
* @suppress {visibility}
|
||||
*/
|
||||
@customElement('md-menu-item-link')
|
||||
export class MdMenuItemLink extends MenuItemLink {
|
||||
static override styles =
|
||||
[listItemStyles, styles, listItemForcedColorsStyles, forcedColorsStyles];
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user