mirror of
https://github.com/material-components/material-web.git
synced 2024-09-11 13:46:10 +03:00
refactor(list)!: refactor list using md-item
BREAKING CHANGE: `<md-list-item>` now uses slots instead of properties and has removed many prescriptive items (such as avatar, image, and video items). The default slot can be used for any custom content. ```html <md-list-item> <div slot="overline">OVERLINE</div> <div slot="headline">First line</div> <div slot="supporting-text">Second+ lines</div> <div slot="trailing-supporting-text">Trailing</div> <md-icon slot="start">star</md-icon> <md-icon slot="end">star</md-icon> </md-list-item> ``` Add `type="button"` or `type="link"` for interactive list items. PiperOrigin-RevId: 567732201
This commit is contained in:
parent
5fad4f088f
commit
753677489b
@ -144,7 +144,7 @@ function getKnobContent(knobs: StoryKnobs, threeLines = false) {
|
||||
html`<md-icon class=${classMap(classes)} slot="end">star</md-icon>` :
|
||||
nothing;
|
||||
|
||||
return html`${overline}${trailingText}${leadingIcon}${trailingIcon}`;
|
||||
return [overline, trailingText, leadingIcon, trailingIcon];
|
||||
}
|
||||
|
||||
/** Item stories. */
|
||||
|
File diff suppressed because one or more lines are too long
@ -10,159 +10,113 @@ import '@material/web/list/list.js';
|
||||
import '@material/web/icon/icon.js';
|
||||
|
||||
import {MaterialStoryInit} from './material-collection.js';
|
||||
import {css, html} from 'lit';
|
||||
import {css, html, nothing} from 'lit';
|
||||
import {classMap} from 'lit/directives/class-map.js';
|
||||
|
||||
/** Knob types for list stories. */
|
||||
export interface StoryKnobs {
|
||||
'md-list-item': void;
|
||||
disabled: boolean;
|
||||
interactive: boolean;
|
||||
multiLineSupportingText: boolean;
|
||||
headline: string;
|
||||
supportingText: string;
|
||||
overline: string;
|
||||
trailingSupportingText: string;
|
||||
href: string;
|
||||
target: string;
|
||||
'link end icon': string;
|
||||
|
||||
'slot[name=start|end-icon]': void;
|
||||
'start icon': string;
|
||||
'end icon': string;
|
||||
|
||||
'slot[name=start-avatar]': void;
|
||||
'avatar img': string;
|
||||
'avatar label': string;
|
||||
|
||||
'slot[name=start-image]': void;
|
||||
image: string;
|
||||
|
||||
'slot[name=start-video]': void;
|
||||
'slot[name=start-video-large]': boolean;
|
||||
'video src': string;
|
||||
leadingIcon: boolean;
|
||||
trailingIcon: boolean;
|
||||
}
|
||||
|
||||
const styles = css`
|
||||
md-list {
|
||||
border-radius: 8px;
|
||||
outline: 1px solid var(--md-sys-color-outline);
|
||||
max-width: 360px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
const standard: MaterialStoryInit<StoryKnobs> = {
|
||||
name: '<md-list>',
|
||||
styles: css`
|
||||
.list-demo {
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--md-sys-color-outline);
|
||||
max-width: 360px;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.list {
|
||||
max-width: 200px;
|
||||
}`,
|
||||
name: 'List',
|
||||
styles,
|
||||
render(knobs) {
|
||||
const {
|
||||
disabled,
|
||||
interactive,
|
||||
multiLineSupportingText,
|
||||
headline,
|
||||
supportingText,
|
||||
trailingSupportingText,
|
||||
href,
|
||||
target,
|
||||
image,
|
||||
} = knobs;
|
||||
return html`
|
||||
<div class="list-demo">
|
||||
<md-list class="list" role="listbox">
|
||||
<md-list-item
|
||||
.headline=${headline}
|
||||
.supportingText=${supportingText}
|
||||
.multiLineSupportingText=${multiLineSupportingText}
|
||||
.trailingSupportingText=${trailingSupportingText}
|
||||
.disabled=${disabled}
|
||||
.interactive=${interactive}>
|
||||
</md-list-item>
|
||||
<md-list>
|
||||
<md-list-item ?disabled=${knobs.disabled}>
|
||||
Single line item
|
||||
${getKnobContent(knobs)}
|
||||
</md-list-item>
|
||||
|
||||
<md-list-item
|
||||
.headline=${headline}
|
||||
.supportingText=${supportingText}
|
||||
.multiLineSupportingText=${multiLineSupportingText}
|
||||
.trailingSupportingText=${trailingSupportingText}
|
||||
.disabled=${disabled}
|
||||
.interactive=${interactive}>
|
||||
<md-icon slot="start-icon">
|
||||
${knobs['start icon']}
|
||||
</md-icon>
|
||||
<md-icon slot="end-icon">
|
||||
${knobs['end icon']}
|
||||
</md-icon>
|
||||
</md-list-item>
|
||||
<md-list-item ?disabled=${knobs.disabled}>
|
||||
Two line item
|
||||
<div slot="supporting-text">Supporting text</div>
|
||||
${getKnobContent(knobs)}
|
||||
</md-list-item>
|
||||
|
||||
<md-list-item
|
||||
.headline=${headline}
|
||||
.supportingText=${supportingText}
|
||||
.multiLineSupportingText=${multiLineSupportingText}
|
||||
.trailingSupportingText=${trailingSupportingText}
|
||||
.disabled=${disabled}
|
||||
.interactive=${interactive}
|
||||
.href=${href}
|
||||
.target=${target as '' | '_blank' | '_parent' | '_self' | '_top'}>
|
||||
<md-icon slot="end-icon">${knobs['link end icon']}</md-icon>
|
||||
</md-list-item>
|
||||
|
||||
<md-divider></md-divider>
|
||||
|
||||
<md-list-item
|
||||
.headline=${headline}
|
||||
.supportingText=${supportingText}
|
||||
.multiLineSupportingText=${multiLineSupportingText}
|
||||
.trailingSupportingText=${trailingSupportingText}
|
||||
.disabled=${disabled}
|
||||
.interactive=${interactive}>
|
||||
<img src=${knobs['avatar img']} slot="start-avatar">
|
||||
</md-list-item>
|
||||
|
||||
<md-list-item
|
||||
.headline=${headline}
|
||||
.supportingText=${supportingText}
|
||||
.multiLineSupportingText=${multiLineSupportingText}
|
||||
.trailingSupportingText=${trailingSupportingText}
|
||||
.disabled=${disabled}
|
||||
.interactive=${interactive}>
|
||||
<span slot="start-avatar">
|
||||
${knobs['avatar label']}
|
||||
</span>
|
||||
</md-list-item>
|
||||
|
||||
<md-list-item
|
||||
.headline=${headline}
|
||||
.supportingText=${supportingText}
|
||||
.multiLineSupportingText=${multiLineSupportingText}
|
||||
.trailingSupportingText=${trailingSupportingText}
|
||||
.disabled=${disabled}
|
||||
.interactive=${interactive}>
|
||||
<img .src=${image} slot="start-image">
|
||||
</md-list-item>
|
||||
|
||||
<md-list-item
|
||||
.headline=${headline}
|
||||
.supportingText=${supportingText}
|
||||
.multiLineSupportingText=${multiLineSupportingText}
|
||||
.trailingSupportingText=${trailingSupportingText}
|
||||
.disabled=${disabled}
|
||||
.interactive=${interactive}>
|
||||
<video
|
||||
muted
|
||||
autoplay
|
||||
loop
|
||||
playsinline
|
||||
.src=${knobs['video src']}
|
||||
slot=${
|
||||
knobs['slot[name=start-video-large]'] ? 'start-video-large' :
|
||||
'start-video'}
|
||||
></video>
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
</div>
|
||||
<md-list-item ?disabled=${knobs.disabled}>
|
||||
Three line item
|
||||
<div slot="supporting-text">
|
||||
<div>Second line text</div>
|
||||
<div>Third line text</div>
|
||||
</div>
|
||||
${getKnobContent(knobs, /* threeLines */ true)}
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
`;
|
||||
},
|
||||
};
|
||||
|
||||
const interactive: MaterialStoryInit<StoryKnobs> = {
|
||||
name: 'Interactive list',
|
||||
styles,
|
||||
render(knobs) {
|
||||
const knobsNoTrailing = {...knobs, trailingIcon: false};
|
||||
return html`
|
||||
<md-list>
|
||||
<md-list-item ?disabled=${knobs.disabled}
|
||||
type="link"
|
||||
href="https://google.com"
|
||||
target="_blank"
|
||||
>
|
||||
Link item
|
||||
<md-icon slot="end">link</md-icon>
|
||||
${getKnobContent(knobsNoTrailing)}
|
||||
</md-list-item>
|
||||
|
||||
<md-list-item type="button" ?disabled=${knobs.disabled}>
|
||||
Button item
|
||||
${getKnobContent(knobs)}
|
||||
</md-list-item>
|
||||
|
||||
<md-list-item ?disabled=${knobs.disabled}>
|
||||
Non-interactive item
|
||||
${getKnobContent(knobs)}
|
||||
</md-list-item>
|
||||
</md-list>
|
||||
`;
|
||||
},
|
||||
};
|
||||
|
||||
function getKnobContent(knobs: StoryKnobs, threeLines = false) {
|
||||
const overline = knobs.overline ?
|
||||
html`<div slot="overline">${knobs.overline}</div>` :
|
||||
nothing;
|
||||
|
||||
const classes = {
|
||||
'align-start': threeLines,
|
||||
};
|
||||
|
||||
const trailingText = knobs.trailingSupportingText ?
|
||||
html`<div class=${classMap(classes)} slot="trailing-supporting-text">${
|
||||
knobs.trailingSupportingText}</div>` :
|
||||
nothing;
|
||||
|
||||
const leadingIcon = knobs.leadingIcon ?
|
||||
html`<md-icon class=${classMap(classes)} slot="start">event</md-icon>` :
|
||||
nothing;
|
||||
|
||||
const trailingIcon = knobs.trailingIcon ?
|
||||
html`<md-icon class=${classMap(classes)} slot="end">star</md-icon>` :
|
||||
nothing;
|
||||
|
||||
return [overline, trailingText, leadingIcon, trailingIcon];
|
||||
}
|
||||
|
||||
/** List stories. */
|
||||
export const stories = [standard];
|
||||
export const stories = [standard, interactive];
|
||||
|
@ -35,7 +35,7 @@ export class ListHarness extends Harness<List> {
|
||||
*/
|
||||
protected override async getInteractiveElement() {
|
||||
await this.element.updateComplete;
|
||||
return this.element.renderRoot.querySelector('.list') as HTMLElement;
|
||||
return this.element as List;
|
||||
}
|
||||
|
||||
/** @return List item harnesses. */
|
||||
@ -60,8 +60,7 @@ export class ListHarness extends Harness<List> {
|
||||
* @param key The key to dispatch on the list.
|
||||
*/
|
||||
override async keypress(key: string, init = {} as KeyboardEventInit) {
|
||||
const nativeList = this.element.renderRoot.querySelector('ul')!;
|
||||
init = {code: key, ...init};
|
||||
this.simulateKeypress(nativeList, key, init);
|
||||
await super.keypress(key, init);
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@
|
||||
// go/keep-sorted start
|
||||
@use 'sass:list';
|
||||
@use 'sass:map';
|
||||
@use 'sass:string';
|
||||
// go/keep-sorted end
|
||||
// go/keep-sorted start
|
||||
@use '../../tokens';
|
||||
@ -29,23 +28,15 @@
|
||||
|
||||
@mixin styles() {
|
||||
$tokens: tokens.md-comp-list-values();
|
||||
|
||||
:host {
|
||||
@each $token, $value in $tokens {
|
||||
--_#{$token}: var(--md-list-#{$token}, #{$value});
|
||||
}
|
||||
|
||||
color: unset;
|
||||
display: flex;
|
||||
@each $token, $value in $tokens {
|
||||
$tokens: map.set($tokens, $token, var(--md-list-#{$token}, #{$value}));
|
||||
}
|
||||
|
||||
.list {
|
||||
background-color: var(--_container-color);
|
||||
border-radius: inherit;
|
||||
display: block;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
min-width: inherit;
|
||||
:host {
|
||||
background: map.get($tokens, 'container-color');
|
||||
color: unset;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
outline: none;
|
||||
padding: 8px 0;
|
||||
// Add position so the elevation overlay (which is absolutely positioned)
|
||||
|
@ -92,30 +92,13 @@ export class List extends LitElement {
|
||||
}
|
||||
|
||||
protected override render() {
|
||||
return this.renderList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the main list element.
|
||||
*/
|
||||
private renderList() {
|
||||
return html`
|
||||
<ul class="list" role="presentation">
|
||||
${this.renderContent()}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* The content to be slotted into the list.
|
||||
*/
|
||||
private renderContent() {
|
||||
return html`
|
||||
<slot
|
||||
@deactivate-items=${this.onDeactivateItems}
|
||||
@request-activation=${this.onRequestActivation}
|
||||
@slotchange=${this.onSlotchange}>
|
||||
</slot>`;
|
||||
</slot>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,7 +6,6 @@
|
||||
// go/keep-sorted start
|
||||
@use 'sass:list';
|
||||
@use 'sass:map';
|
||||
@use 'sass:string';
|
||||
// go/keep-sorted end
|
||||
// go/keep-sorted start
|
||||
@use '../../../focus/focus-ring';
|
||||
@ -31,35 +30,32 @@
|
||||
|
||||
@mixin styles() {
|
||||
$tokens: tokens.md-comp-list-item-values();
|
||||
|
||||
:host {
|
||||
@each $token, $value in $tokens {
|
||||
--_#{$token}: var(--md-list-item-#{$token}, #{$value});
|
||||
}
|
||||
@each $token, $value in $tokens {
|
||||
$tokens: map.set($tokens, $token, var(--md-list-item-#{$token}, #{$value}));
|
||||
}
|
||||
|
||||
@include _list-item;
|
||||
@include _image;
|
||||
@include _icon;
|
||||
@include _avatar;
|
||||
@include _video;
|
||||
}
|
||||
|
||||
@mixin _list-item() {
|
||||
:host {
|
||||
color: unset;
|
||||
border-radius: map.get($tokens, 'container-shape');
|
||||
display: flex;
|
||||
|
||||
@include ripple.theme(
|
||||
(
|
||||
hover-color: var(--_hover-state-layer-color),
|
||||
hover-opacity: var(--_hover-state-layer-opacity),
|
||||
pressed-color: var(--_pressed-state-layer-color),
|
||||
pressed-opacity: var(--_pressed-state-layer-opacity),
|
||||
hover-color: map.get($tokens, 'hover-state-layer-color'),
|
||||
hover-opacity: map.get($tokens, 'hover-state-layer-opacity'),
|
||||
pressed-color: map.get($tokens, 'pressed-state-layer-color'),
|
||||
pressed-opacity: map.get($tokens, 'pressed-state-layer-opacity'),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
:host([disabled]) {
|
||||
opacity: map.get($tokens, 'disabled-opacity');
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
md-focus-ring {
|
||||
z-index: 1;
|
||||
|
||||
@include focus-ring.theme(
|
||||
(
|
||||
'shape': 8px,
|
||||
@ -67,329 +63,84 @@
|
||||
);
|
||||
}
|
||||
|
||||
.list-item {
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
outline: none;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
a,
|
||||
button,
|
||||
li {
|
||||
// Resets. These can be removed once we're no longer use these tags
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: unset;
|
||||
text-decoration: none;
|
||||
background-color: var(--_container-color);
|
||||
border-radius: var(--_container-shape);
|
||||
}
|
||||
|
||||
.list-item {
|
||||
border-radius: inherit;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
outline: none;
|
||||
// hide android tap color since we have ripple
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
&:not(.disabled).interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: inherit;
|
||||
padding-inline-end: var(--_trailing-space);
|
||||
[slot='container'] {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
md-ripple {
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.with-one-line {
|
||||
min-height: var(--_one-line-container-height);
|
||||
md-item {
|
||||
border-radius: inherit;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
color: map.get($tokens, 'label-text-color');
|
||||
font-family: map.get($tokens, 'label-text-font');
|
||||
font-size: map.get($tokens, 'label-text-size');
|
||||
line-height: map.get($tokens, 'label-text-line-height');
|
||||
font-weight: map.get($tokens, 'label-text-weight');
|
||||
min-height: map.get($tokens, 'one-line-container-height');
|
||||
padding-top: map.get($tokens, 'top-space');
|
||||
padding-bottom: map.get($tokens, 'bottom-space');
|
||||
padding-inline-start: map.get($tokens, 'leading-space');
|
||||
padding-inline-end: map.get($tokens, 'trailing-space');
|
||||
}
|
||||
|
||||
.with-two-line {
|
||||
min-height: var(--_two-line-container-height);
|
||||
md-item[multiline] {
|
||||
min-height: map.get($tokens, 'two-line-container-height');
|
||||
}
|
||||
|
||||
.with-three-line {
|
||||
min-height: var(--_three-line-container-height);
|
||||
[slot='supporting-text'] {
|
||||
color: map.get($tokens, 'supporting-text-color');
|
||||
font-family: map.get($tokens, 'supporting-text-font');
|
||||
font-size: map.get($tokens, 'supporting-text-size');
|
||||
line-height: map.get($tokens, 'supporting-text-line-height');
|
||||
font-weight: map.get($tokens, 'supporting-text-weight');
|
||||
}
|
||||
|
||||
.start {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
z-index: 1;
|
||||
|
||||
.with-three-line & {
|
||||
justify-content: start;
|
||||
}
|
||||
[slot='trailing-supporting-text'] {
|
||||
color: map.get($tokens, 'trailing-supporting-text-color');
|
||||
font-family: map.get($tokens, 'trailing-supporting-text-font');
|
||||
font-size: map.get($tokens, 'trailing-supporting-text-size');
|
||||
line-height: map.get($tokens, 'trailing-supporting-text-line-height');
|
||||
font-weight: map.get($tokens, 'trailing-supporting-text-weight');
|
||||
}
|
||||
|
||||
slot[name='start-icon']::slotted(*),
|
||||
slot[name='start-image']::slotted(*),
|
||||
slot[name='start-avatar']::slotted(*) {
|
||||
margin-inline-start: var(--_leading-element-leading-space);
|
||||
}
|
||||
|
||||
.body {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
flex: 1 0 0;
|
||||
padding-inline-start: var(--_leading-space);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.end {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
flex: 0 0 auto;
|
||||
z-index: 1;
|
||||
|
||||
.with-three-line & {
|
||||
justify-content: start;
|
||||
}
|
||||
}
|
||||
|
||||
slot[name='end']::slotted(*),
|
||||
slot[name='end-icon']::slotted(*),
|
||||
.trailing-supporting-text {
|
||||
margin-inline-start: var(
|
||||
--_trailing-element-headline-trailing-element-space
|
||||
);
|
||||
}
|
||||
|
||||
.label-text {
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
color: var(--_label-text-color);
|
||||
font-family: var(--_label-text-font);
|
||||
font-size: var(--_label-text-size);
|
||||
line-height: var(--_label-text-line-height);
|
||||
font-weight: var(--_label-text-weight);
|
||||
|
||||
:hover & {
|
||||
color: var(--_hover-label-text-color);
|
||||
}
|
||||
|
||||
:focus & {
|
||||
color: var(--_focus-label-text-color);
|
||||
}
|
||||
|
||||
:active & {
|
||||
color: var(--_pressed-label-text-color);
|
||||
}
|
||||
|
||||
.disabled & {
|
||||
color: var(--_disabled-label-text-color);
|
||||
opacity: var(--_disabled-label-text-opacity);
|
||||
}
|
||||
}
|
||||
|
||||
.supporting-text {
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
color: var(--_supporting-text-color);
|
||||
font-family: var(--_supporting-text-font);
|
||||
font-size: var(--_supporting-text-size);
|
||||
line-height: var(--_supporting-text-line-height);
|
||||
font-weight: var(--_supporting-text-weight);
|
||||
|
||||
// Box is supposed to be deprecated, but line-clamp is not. It's still on
|
||||
// standards track and can only be applied with display: -webkit-box and
|
||||
// -webkit-box-orient: vertical. Must change once un-prefixed line-clamp
|
||||
// ships
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
display: -webkit-box;
|
||||
|
||||
.disabled & {
|
||||
color: var(--_disabled-label-text-color);
|
||||
opacity: var(--_disabled-label-text-opacity);
|
||||
}
|
||||
}
|
||||
|
||||
.supporting-text--multi-line {
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.trailing-supporting-text {
|
||||
font-family: var(--_trailing-supporting-text-font);
|
||||
font-size: var(--_trailing-supporting-text-size);
|
||||
line-height: var(--_trailing-supporting-text-line-height);
|
||||
font-weight: var(--_trailing-supporting-text-weight);
|
||||
|
||||
.list-item:not(.disabled) & {
|
||||
color: var(--_trailing-supporting-text-color);
|
||||
}
|
||||
|
||||
.disabled & {
|
||||
color: var(--_disabled-label-text-color);
|
||||
opacity: var(--_disabled-label-text-opacity);
|
||||
}
|
||||
|
||||
.with-three-line & {
|
||||
// In three line, trailing-supporting-text must align with the mid-line of
|
||||
// the headline text.
|
||||
margin-block-start: calc(
|
||||
(
|
||||
var(--_label-text-line-height) -
|
||||
var(--_trailing-supporting-text-line-height)
|
||||
) / 2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.focus-ring {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin _image() {
|
||||
slot[name='start-image']::slotted(*) {
|
||||
display: inline-flex;
|
||||
height: var(--_leading-image-height);
|
||||
width: var(--_leading-image-width);
|
||||
border-radius: var(--_leading-image-shape);
|
||||
// Min height is two-line height
|
||||
margin-block: calc(
|
||||
(var(--_two-line-container-height) - var(--_leading-image-height)) / 2
|
||||
);
|
||||
|
||||
.with-three-line & {
|
||||
margin-block: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin _icon() {
|
||||
::slotted(*) {
|
||||
:is([slot='start'], [slot='end'])::slotted(*) {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
slot[name='start-icon']::slotted(*) {
|
||||
font-size: var(--_leading-icon-size);
|
||||
width: var(--_leading-icon-size);
|
||||
height: var(--_leading-icon-size);
|
||||
color: var(--_leading-icon-color);
|
||||
|
||||
.with-three-line & {
|
||||
// In three line, icon must align with the mid-line of headline text
|
||||
margin-block-start: calc(
|
||||
(var(--_label-text-line-height) - var(--_leading-icon-size)) / 2
|
||||
);
|
||||
}
|
||||
[slot='start'] {
|
||||
color: map.get($tokens, 'leading-icon-color');
|
||||
}
|
||||
|
||||
slot[name='end-icon']::slotted(*) {
|
||||
font-size: var(--_trailing-icon-size);
|
||||
width: var(--_trailing-icon-size);
|
||||
height: var(--_trailing-icon-size);
|
||||
color: var(--_trailing-icon-color);
|
||||
|
||||
.with-three-line & {
|
||||
// In three line, icon must align with the mid-line of headline text
|
||||
margin-block-start: calc(
|
||||
(var(--_label-text-line-height) - var(--_trailing-icon-size)) / 2
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
:hover {
|
||||
slot[name='start-icon']::slotted(*) {
|
||||
color: var(--_hover-leading-icon-icon-color);
|
||||
}
|
||||
|
||||
slot[name='end-icon']::slotted(*) {
|
||||
color: var(--_hover-trailing-icon-icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
:focus {
|
||||
slot[name='start-icon']::slotted(*) {
|
||||
color: var(--_focus-leading-icon-icon-color);
|
||||
}
|
||||
|
||||
slot[name='end-icon']::slotted(*) {
|
||||
color: var(--_focus-trailing-icon-icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
:active {
|
||||
slot[name='start-icon']::slotted(*) {
|
||||
color: var(--_pressed-leading-icon-icon-color);
|
||||
}
|
||||
|
||||
slot[name='end-icon']::slotted(*) {
|
||||
color: var(--_pressed-trailing-icon-icon-color);
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
slot[name='start-icon']::slotted(*) {
|
||||
opacity: var(--_disabled-leading-icon-opacity);
|
||||
color: var(--_disabled-leading-icon-color);
|
||||
}
|
||||
|
||||
slot[name='end-icon']::slotted(*) {
|
||||
opacity: var(--_disabled-trailing-icon-opacity);
|
||||
color: var(--_disabled-trailing-icon-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin _avatar() {
|
||||
slot[name='start-avatar']::slotted(*) {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: var(--_leading-avatar-color);
|
||||
height: var(--_leading-avatar-size);
|
||||
width: var(--_leading-avatar-size);
|
||||
border-radius: var(--_leading-avatar-shape);
|
||||
color: var(--_leading-avatar-label-color);
|
||||
font-family: var(--_leading-avatar-label-font);
|
||||
font-size: var(--_leading-avatar-label-size);
|
||||
line-height: var(--_leading-avatar-label-line-height);
|
||||
font-weight: var(--_leading-avatar-label-weight);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin _video() {
|
||||
slot[name='start-video']::slotted(*),
|
||||
slot[name='start-video-large']::slotted(*) {
|
||||
display: inline-flex;
|
||||
object-fit: cover;
|
||||
height: var(--_small-leading-video-height);
|
||||
width: var(--_leading-video-width);
|
||||
border-radius: var(--_leading-video-shape);
|
||||
margin-inline-start: var(--_leading-video-leading-space);
|
||||
// Min height is three-line height
|
||||
margin-block: calc(
|
||||
(var(--_three-line-container-height) - var(--_small-leading-video-height)) /
|
||||
2
|
||||
);
|
||||
|
||||
// Let it auto-layout so that we don't mess with the icons and supporting
|
||||
// text that is supposed to be top-aligned in three-line.
|
||||
.with-three-line & {
|
||||
margin-block: 0;
|
||||
}
|
||||
}
|
||||
|
||||
slot[name='start-video-large']::slotted(*) {
|
||||
// Min height is three-line height
|
||||
margin-block: calc(
|
||||
(var(--_three-line-container-height) - var(--_large-leading-video-height)) /
|
||||
2
|
||||
);
|
||||
height: var(--_large-leading-video-height);
|
||||
[slot='end'] {
|
||||
color: map.get($tokens, 'trailing-icon-color');
|
||||
}
|
||||
}
|
||||
|
@ -8,16 +8,9 @@
|
||||
// go/keep-sorted end
|
||||
|
||||
@media (forced-colors: active) {
|
||||
:host {
|
||||
@include list-item.theme(
|
||||
(
|
||||
disabled-label-text-color: GrayText,
|
||||
disabled-label-text-opacity: 1,
|
||||
disabled-leading-icon-color: GrayText,
|
||||
disabled-leading-icon-opacity: 1,
|
||||
disabled-trailing-icon-color: GrayText,
|
||||
disabled-trailing-icon-opacity: 1,
|
||||
)
|
||||
);
|
||||
:host([disabled]),
|
||||
:host([disabled]) slot {
|
||||
color: GrayText;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {nothing} from 'lit';
|
||||
import {property} from 'lit/decorators.js';
|
||||
|
||||
import {createRequestActivationEvent, ListItemEl} from './list-item.js';
|
||||
|
||||
// tslint:disable-next-line:enforce-comments-on-exported-symbols
|
||||
export class ListItemOnly extends ListItemEl {
|
||||
/**
|
||||
* Enables focusing the list item, and adds hover and click ripples when set
|
||||
* to true. By default `interactive` is false.
|
||||
*/
|
||||
@property({type: Boolean}) interactive = false;
|
||||
|
||||
override getRenderClasses() {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'interactive': this.interactive,
|
||||
};
|
||||
}
|
||||
|
||||
override renderRipple() {
|
||||
return this.interactive ? super.renderRipple() : nothing;
|
||||
}
|
||||
|
||||
override renderFocusRing() {
|
||||
return this.interactive ? super.renderFocusRing() : nothing;
|
||||
}
|
||||
|
||||
override onFocus() {
|
||||
if (this.tabIndex !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handles the case where the user clicks on the element and then tabs.
|
||||
this.dispatchEvent(createRequestActivationEvent());
|
||||
}
|
||||
}
|
@ -6,11 +6,12 @@
|
||||
|
||||
import '../../../ripple/ripple.js';
|
||||
import '../../../focus/md-focus-ring.js';
|
||||
import '../../../item/item.js';
|
||||
|
||||
import {html, LitElement, nothing, TemplateResult} from 'lit';
|
||||
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 {ClassInfo, classMap} from 'lit/directives/class-map.js';
|
||||
import {html as staticHtml, literal, StaticValue} from 'lit/static-html.js';
|
||||
|
||||
import {ARIAMixinStrict} from '../../../internal/aria/aria.js';
|
||||
import {requestUpdateOnAriaChange} from '../../../internal/aria/delegate.js';
|
||||
@ -49,9 +50,9 @@ export type RequestActivationEvent =
|
||||
ReturnType<typeof createRequestActivationEvent>;
|
||||
|
||||
/**
|
||||
* Supported roles for a list item.
|
||||
* Supported behaviors for a list item.
|
||||
*/
|
||||
export type ListItemRole = 'listitem'|'menuitem'|'option'|'link'|'none';
|
||||
export type ListItemType = 'text'|'button'|'link';
|
||||
|
||||
interface ListItemSelf {
|
||||
disabled: boolean;
|
||||
@ -63,7 +64,9 @@ interface ListItemSelf {
|
||||
*/
|
||||
export type ListItem = ListItemSelf&HTMLElement;
|
||||
|
||||
// tslint:disable-next-line:enforce-comments-on-exported-symbols
|
||||
/**
|
||||
* @fires request-activation
|
||||
*/
|
||||
export class ListItemEl extends LitElement implements ListItem {
|
||||
static {
|
||||
requestUpdateOnAriaChange(ListItemEl);
|
||||
@ -75,42 +78,16 @@ export class ListItemEl extends LitElement implements ListItem {
|
||||
delegatesFocus: true
|
||||
};
|
||||
|
||||
/**
|
||||
* The primary, headline text of the list item.
|
||||
*/
|
||||
@property() headline = '';
|
||||
|
||||
/**
|
||||
* The one-line supporting text below the headline. Set
|
||||
* `multiLineSupportingText` to `true` to support multiple lines in the
|
||||
* supporting text.
|
||||
*/
|
||||
@property({attribute: 'supporting-text'}) supportingText = '';
|
||||
|
||||
/**
|
||||
* Modifies `supportingText` to support multiple lines.
|
||||
*/
|
||||
@property({type: Boolean, attribute: 'multi-line-supporting-text'})
|
||||
multiLineSupportingText = false;
|
||||
|
||||
/**
|
||||
* The supporting text placed at the end of the item. Overridden by elements
|
||||
* slotted into the `end` slot.
|
||||
*/
|
||||
@property({attribute: 'trailing-supporting-text'})
|
||||
trailingSupportingText = '';
|
||||
|
||||
/**
|
||||
* Disables the item and makes it non-selectable and non-interactive.
|
||||
*/
|
||||
@property({type: Boolean, reflect: true}) disabled = false;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Sets the behavior of the list item, defaults to "text". Change to "link" or
|
||||
* "button" for interactive items.
|
||||
*/
|
||||
@property() type: ListItemRole = 'listitem';
|
||||
@property() type: ListItemType = 'text';
|
||||
|
||||
/**
|
||||
* READONLY. Sets the `md-list-item` attribute on the element.
|
||||
@ -131,15 +108,25 @@ export class ListItemEl extends LitElement implements ListItem {
|
||||
|
||||
@query('.list-item') protected readonly listItemRoot!: HTMLElement|null;
|
||||
|
||||
protected override willUpdate(changed: PropertyValues<ListItemEl>) {
|
||||
if (this.href) {
|
||||
this.type = 'link';
|
||||
}
|
||||
|
||||
super.willUpdate(changed);
|
||||
}
|
||||
|
||||
protected override render() {
|
||||
return this.renderListItem(html`
|
||||
<div class="content-wrapper">
|
||||
${this.renderStart()}
|
||||
<md-item>
|
||||
<div slot="container">
|
||||
${this.renderRipple()}
|
||||
${this.renderFocusRing()}
|
||||
</div>
|
||||
<slot name="start" slot="start"></slot>
|
||||
<slot name="end" slot="end"></slot>
|
||||
${this.renderBody()}
|
||||
${this.renderEnd()}
|
||||
${this.renderRipple()}
|
||||
${this.renderFocusRing()}
|
||||
</div>
|
||||
</md-item>
|
||||
`);
|
||||
}
|
||||
|
||||
@ -149,15 +136,29 @@ export class ListItemEl extends LitElement implements ListItem {
|
||||
* @param content the child content of the list item.
|
||||
*/
|
||||
protected renderListItem(content: unknown) {
|
||||
const isAnchor = !!this.href;
|
||||
const tag = isAnchor ? literal`a` : literal`li`;
|
||||
const role = isAnchor || this.type === 'none' ? nothing : this.type;
|
||||
const isAnchor = this.type === 'link';
|
||||
let tag: StaticValue;
|
||||
switch (this.type) {
|
||||
case 'link':
|
||||
tag = literal`a`;
|
||||
break;
|
||||
case 'button':
|
||||
tag = literal`button`;
|
||||
break;
|
||||
default:
|
||||
case 'text':
|
||||
tag = literal`li`;
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO(b/265339866): announce "button"/"link" inside of a list item. Until
|
||||
// then all are "listitem" roles for correct announcement.
|
||||
const target = isAnchor && !!this.target ? this.target : nothing;
|
||||
return staticHtml`
|
||||
<${tag}
|
||||
id="item"
|
||||
tabindex="${this.disabled ? -1 : 0}"
|
||||
role=${role}
|
||||
tabindex="${this.disabled && !isAnchor ? -1 : 0}"
|
||||
role="listitem"
|
||||
aria-selected=${(this as ARIAMixinStrict).ariaSelected || nothing}
|
||||
aria-checked=${(this as ARIAMixinStrict).ariaChecked || nothing}
|
||||
aria-expanded=${(this as ARIAMixinStrict).ariaExpanded || nothing}
|
||||
@ -166,10 +167,6 @@ export class ListItemEl extends LitElement implements ListItem {
|
||||
href=${this.href || nothing}
|
||||
target=${target}
|
||||
@focus=${this.onFocus}
|
||||
@click=${this.onClick}
|
||||
@mouseenter=${this.onMouseenter}
|
||||
@mouseleave=${this.onMouseleave}
|
||||
@keydown=${this.onKeydown}
|
||||
>${content}</${tag}>
|
||||
`;
|
||||
}
|
||||
@ -178,6 +175,10 @@ export class ListItemEl extends LitElement implements ListItem {
|
||||
* Handles rendering of the ripple element.
|
||||
*/
|
||||
protected renderRipple(): TemplateResult|typeof nothing {
|
||||
if (this.type === 'text') {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<md-ripple
|
||||
part="ripple"
|
||||
@ -189,10 +190,13 @@ export class ListItemEl extends LitElement implements ListItem {
|
||||
* Handles rendering of the focus ring.
|
||||
*/
|
||||
protected renderFocusRing(): TemplateResult|typeof nothing {
|
||||
if (this.type === 'text') {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
return html`
|
||||
<md-focus-ring
|
||||
@visibility-changed=${this.onFocusRingVisibilityChanged}
|
||||
class="focus-ring"
|
||||
part="focus-ring"
|
||||
for="item"
|
||||
inward></md-focus-ring>`;
|
||||
@ -203,89 +207,33 @@ export class ListItemEl extends LitElement implements ListItem {
|
||||
/**
|
||||
* Classes applied to the list item root.
|
||||
*/
|
||||
protected getRenderClasses() {
|
||||
return {
|
||||
'with-one-line': this.supportingText === '',
|
||||
'with-two-line':
|
||||
this.supportingText !== '' && !this.multiLineSupportingText,
|
||||
'with-three-line':
|
||||
this.supportingText !== '' && this.multiLineSupportingText,
|
||||
'disabled': this.disabled
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The content rendered at the start of the list item.
|
||||
*/
|
||||
protected renderStart() {
|
||||
return html`
|
||||
<div class="start">
|
||||
<slot name="start"></slot>
|
||||
<slot name="start-icon"></slot>
|
||||
<slot name="start-image"></slot>
|
||||
<slot name="start-avatar"></slot>
|
||||
<slot name="start-video"></slot>
|
||||
<slot name="start-video-large"></slot>
|
||||
</div>`;
|
||||
protected getRenderClasses(): ClassInfo {
|
||||
// TODO(b/265339866): links may not be disabled
|
||||
return {'disabled': this.disabled};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles rendering the headline and supporting text.
|
||||
*/
|
||||
protected renderBody() {
|
||||
const supportingText =
|
||||
this.supportingText !== '' ? this.renderSupportingText() : '';
|
||||
|
||||
return html`<div class="body"
|
||||
><span class="label-text">${this.headline}</span>${supportingText}</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the one-line supporting text.
|
||||
*/
|
||||
protected renderSupportingText() {
|
||||
return html`<span
|
||||
class="supporting-text ${classMap(this.getSupportingTextClasses())}"
|
||||
>${this.supportingText}</span>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the classes for the supporting text node
|
||||
*/
|
||||
protected getSupportingTextClasses() {
|
||||
return {'supporting-text--multi-line': this.multiLineSupportingText};
|
||||
}
|
||||
|
||||
/**
|
||||
* The content rendered at the end of the list item.
|
||||
*/
|
||||
protected renderEnd() {
|
||||
const supportingText = this.trailingSupportingText !== '' ?
|
||||
this.renderTrailingSupportingText() :
|
||||
'';
|
||||
return html`
|
||||
<div class="end">
|
||||
${supportingText}
|
||||
<slot name="end"></slot>
|
||||
<slot name="end-icon"></slot>
|
||||
</div>`;
|
||||
<slot></slot>
|
||||
<slot name="overline" slot="overline"></slot>
|
||||
<slot name="headline" slot="headline"></slot>
|
||||
<slot name="supporting-text" slot="supporting-text"></slot>
|
||||
<slot name="trailing-supporting-text"
|
||||
slot="trailing-supporting-text"></slot>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the supporting text at the end of the list item.
|
||||
*/
|
||||
protected renderTrailingSupportingText() {
|
||||
return html`<span class="trailing-supporting-text"
|
||||
>${this.trailingSupportingText}</span>`;
|
||||
protected onFocus() {
|
||||
if (this.tabIndex !== -1) {
|
||||
return;
|
||||
}
|
||||
// Handles the case where the user clicks on the element and then tabs.
|
||||
this.dispatchEvent(createRequestActivationEvent());
|
||||
}
|
||||
|
||||
// For easier overriding in menu-item
|
||||
protected onClick?(event: Event): void;
|
||||
protected onKeydown?(event: KeyboardEvent): void;
|
||||
protected onMouseenter?(event: Event): void;
|
||||
protected onMouseleave?(event: Event): void;
|
||||
protected onFocus?(event: FocusEvent): void;
|
||||
|
||||
override focus() {
|
||||
// TODO(b/300334509): needed for some cases where delegatesFocus doesn't
|
||||
// work programmatically like in FF and select-option
|
||||
|
@ -7,10 +7,10 @@
|
||||
import {customElement} from 'lit/decorators.js';
|
||||
|
||||
import {styles as forcedColors} from './internal/listitem/forced-colors-styles.css.js';
|
||||
import {ListItemOnly as ListItem} from './internal/listitem/list-item-only.js';
|
||||
import {ListItemEl as ListItem} from './internal/listitem/list-item.js';
|
||||
import {styles} from './internal/listitem/list-item-styles.css.js';
|
||||
|
||||
export {ListItemRole} from './internal/listitem/list-item.js';
|
||||
export {ListItemType} from './internal/listitem/list-item.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
|
@ -1008,85 +1008,6 @@ describe('<md-list-item>', () => {
|
||||
expect(internalRoot.tabIndex).toBe(-1);
|
||||
});
|
||||
|
||||
it('supportingText is rendered only when set', async () => {
|
||||
const root = env.render(html`<md-list-item></md-list-item>`);
|
||||
|
||||
const listItem = root.querySelector('md-list-item')!;
|
||||
|
||||
await env.waitForStability();
|
||||
|
||||
let supporingTextEl = listItem.renderRoot.querySelector('.supporting-text');
|
||||
|
||||
expect(supporingTextEl).toBeNull();
|
||||
|
||||
listItem.supportingText = 'Yolo';
|
||||
|
||||
await env.waitForStability();
|
||||
|
||||
supporingTextEl = listItem.renderRoot.querySelector('.supporting-text');
|
||||
expect(supporingTextEl).toBeTruthy();
|
||||
});
|
||||
|
||||
it('trailingSupportingText is rendered only when set', async () => {
|
||||
const root = env.render(html`<md-list-item></md-list-item>`);
|
||||
|
||||
const listItem = root.querySelector('md-list-item')!;
|
||||
|
||||
await env.waitForStability();
|
||||
|
||||
let supporingTextEl =
|
||||
listItem.renderRoot.querySelector('.trailing-supporting-text');
|
||||
|
||||
expect(supporingTextEl).toBeNull();
|
||||
|
||||
listItem.trailingSupportingText = 'Yolo';
|
||||
|
||||
await env.waitForStability();
|
||||
|
||||
supporingTextEl =
|
||||
listItem.renderRoot.querySelector('.trailing-supporting-text');
|
||||
expect(supporingTextEl).toBeTruthy();
|
||||
});
|
||||
|
||||
it('only one "with-*-line" class is set at a time', async () => {
|
||||
const root = env.render(html`<md-list-item></md-list-item>`);
|
||||
|
||||
const listItem = root.querySelector('md-list-item')!;
|
||||
|
||||
await env.waitForStability();
|
||||
|
||||
const rootEl = listItem.renderRoot.querySelector('#item') as HTMLElement;
|
||||
|
||||
expect(rootEl.classList.contains('with-one-line')).toBeTrue();
|
||||
expect(rootEl.classList.contains('with-two-line')).toBeFalse();
|
||||
expect(rootEl.classList.contains('with-three-line')).toBeFalse();
|
||||
|
||||
listItem.multiLineSupportingText = true;
|
||||
|
||||
await env.waitForStability();
|
||||
|
||||
expect(rootEl.classList.contains('with-one-line')).toBeTrue();
|
||||
expect(rootEl.classList.contains('with-two-line')).toBeFalse();
|
||||
expect(rootEl.classList.contains('with-three-line')).toBeFalse();
|
||||
|
||||
listItem.multiLineSupportingText = false;
|
||||
listItem.supportingText = 'YOLO';
|
||||
|
||||
await env.waitForStability();
|
||||
|
||||
expect(rootEl.classList.contains('with-one-line')).toBeFalse();
|
||||
expect(rootEl.classList.contains('with-two-line')).toBeTrue();
|
||||
expect(rootEl.classList.contains('with-three-line')).toBeFalse();
|
||||
|
||||
listItem.multiLineSupportingText = true;
|
||||
|
||||
await env.waitForStability();
|
||||
|
||||
expect(rootEl.classList.contains('with-one-line')).toBeFalse();
|
||||
expect(rootEl.classList.contains('with-two-line')).toBeFalse();
|
||||
expect(rootEl.classList.contains('with-three-line')).toBeTrue();
|
||||
});
|
||||
|
||||
it('ripple and focus ring rendered on interactive', async () => {
|
||||
const root = env.render(html`<md-list-item></md-list-item>`);
|
||||
|
||||
@ -1100,7 +1021,7 @@ describe('<md-list-item>', () => {
|
||||
expect(rippleEl).toBeNull();
|
||||
expect(focusRingEl).toBeNull();
|
||||
|
||||
listItem.interactive = true;
|
||||
listItem.type = 'button';
|
||||
|
||||
await env.waitForStability();
|
||||
|
||||
@ -1133,20 +1054,6 @@ describe('<md-list-item> link', () => {
|
||||
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>`);
|
||||
|
||||
const listItem = root.querySelector('md-list-item')!;
|
||||
|
||||
await env.waitForStability();
|
||||
|
||||
const internalRoot =
|
||||
listItem.renderRoot.querySelector('#item') as HTMLElement;
|
||||
|
||||
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>`);
|
||||
|
@ -10,8 +10,6 @@
|
||||
// go/keep-sorted end
|
||||
// go/keep-sorted start
|
||||
@use './md-sys-color';
|
||||
@use './md-sys-elevation';
|
||||
@use './md-sys-shape';
|
||||
@use './md-sys-state';
|
||||
@use './md-sys-typescale';
|
||||
@use './v0_192/md-comp-list';
|
||||
@ -20,62 +18,29 @@
|
||||
|
||||
$supported-tokens: (
|
||||
// go/keep-sorted start
|
||||
'container-color',
|
||||
'container-shape',
|
||||
'disabled-label-text-color',
|
||||
'disabled-label-text-opacity',
|
||||
'disabled-leading-icon-color',
|
||||
'disabled-leading-icon-opacity',
|
||||
'disabled-trailing-icon-color',
|
||||
'disabled-trailing-icon-opacity',
|
||||
'focus-label-text-color',
|
||||
'focus-leading-icon-icon-color',
|
||||
'focus-trailing-icon-icon-color',
|
||||
'hover-label-text-color',
|
||||
'hover-leading-icon-icon-color',
|
||||
'bottom-space',
|
||||
'disabled-opacity',
|
||||
'focus-state-layer-color',
|
||||
'focus-state-layer-opacity',
|
||||
'hover-state-layer-color',
|
||||
'hover-state-layer-opacity',
|
||||
'hover-trailing-icon-icon-color',
|
||||
'label-text-color',
|
||||
'label-text-font',
|
||||
'label-text-line-height',
|
||||
'label-text-size',
|
||||
'label-text-weight',
|
||||
'large-leading-video-height',
|
||||
'leading-avatar-color',
|
||||
'leading-avatar-label-color',
|
||||
'leading-avatar-label-font',
|
||||
'leading-avatar-label-line-height',
|
||||
'leading-avatar-label-size',
|
||||
'leading-avatar-label-weight',
|
||||
'leading-avatar-shape',
|
||||
'leading-avatar-size',
|
||||
'leading-element-leading-space',
|
||||
'leading-icon-color',
|
||||
'leading-icon-size',
|
||||
'leading-image-height',
|
||||
'leading-image-shape',
|
||||
'leading-image-width',
|
||||
'leading-space',
|
||||
'leading-video-leading-space',
|
||||
'leading-video-shape',
|
||||
'leading-video-width',
|
||||
'one-line-container-height',
|
||||
'pressed-label-text-color',
|
||||
'pressed-leading-icon-icon-color',
|
||||
'pressed-state-layer-color',
|
||||
'pressed-state-layer-opacity',
|
||||
'pressed-trailing-icon-icon-color',
|
||||
'small-leading-video-height',
|
||||
'supporting-text-color',
|
||||
'supporting-text-font',
|
||||
'supporting-text-line-height',
|
||||
'supporting-text-size',
|
||||
'supporting-text-weight',
|
||||
'three-line-container-height',
|
||||
'trailing-element-headline-trailing-element-space',
|
||||
'top-space',
|
||||
'trailing-icon-color',
|
||||
'trailing-icon-size',
|
||||
'trailing-space',
|
||||
'trailing-supporting-text-color',
|
||||
'trailing-supporting-text-font',
|
||||
@ -88,9 +53,17 @@ $supported-tokens: (
|
||||
|
||||
$unsupported-tokens: (
|
||||
// go/keep-sorted start
|
||||
'container-color',
|
||||
'container-elevation',
|
||||
'container-shape',
|
||||
'disabled-label-text-color',
|
||||
'disabled-label-text-opacity',
|
||||
'disabled-leading-icon-color',
|
||||
'disabled-leading-icon-opacity',
|
||||
'disabled-state-layer-color',
|
||||
'disabled-state-layer-opacity',
|
||||
'disabled-trailing-icon-color',
|
||||
'disabled-trailing-icon-opacity',
|
||||
'divider-leading-space',
|
||||
'divider-trailing-space',
|
||||
'dragged-container-elevation',
|
||||
@ -99,12 +72,31 @@ $unsupported-tokens: (
|
||||
'dragged-state-layer-color',
|
||||
'dragged-state-layer-opacity',
|
||||
'dragged-trailing-icon-icon-color',
|
||||
'focus-state-layer-color',
|
||||
'focus-state-layer-opacity',
|
||||
'focus-label-text-color',
|
||||
'focus-leading-icon-icon-color',
|
||||
'focus-trailing-icon-icon-color',
|
||||
'hover-label-text-color',
|
||||
'hover-leading-icon-icon-color',
|
||||
'hover-trailing-icon-icon-color',
|
||||
'label-text-tracking',
|
||||
'label-text-type',
|
||||
'large-leading-video-height',
|
||||
'leading-avatar-color',
|
||||
'leading-avatar-label-color',
|
||||
'leading-avatar-label-font',
|
||||
'leading-avatar-label-line-height',
|
||||
'leading-avatar-label-size',
|
||||
'leading-avatar-label-tracking',
|
||||
'leading-avatar-label-type',
|
||||
'leading-avatar-label-weight',
|
||||
'leading-avatar-shape',
|
||||
'leading-avatar-size',
|
||||
'leading-icon-size',
|
||||
'leading-image-height',
|
||||
'leading-image-shape',
|
||||
'leading-image-width',
|
||||
'leading-video-shape',
|
||||
'leading-video-width',
|
||||
'overline-color',
|
||||
'overline-font',
|
||||
'overline-line-height',
|
||||
@ -112,9 +104,15 @@ $unsupported-tokens: (
|
||||
'overline-tracking',
|
||||
'overline-type',
|
||||
'overline-weight',
|
||||
'pressed-label-text-color',
|
||||
'pressed-leading-icon-icon-color',
|
||||
'pressed-trailing-icon-icon-color',
|
||||
'selected-trailing-icon-color',
|
||||
'small-leading-video-height',
|
||||
'supporting-text-tracking',
|
||||
'supporting-text-type',
|
||||
'three-line-container-height',
|
||||
'trailing-icon-size',
|
||||
'trailing-supporting-text-tracking',
|
||||
'trailing-supporting-text-type',
|
||||
'unselected-trailing-icon-color',
|
||||
@ -123,8 +121,6 @@ $unsupported-tokens: (
|
||||
|
||||
$_default: (
|
||||
'md-sys-color': md-sys-color.values-light(),
|
||||
'md-sys-elevation': md-sys-elevation.values(),
|
||||
'md-sys-shape': md-sys-shape.values(),
|
||||
'md-sys-state': md-sys-state.values(),
|
||||
'md-sys-typescale': md-sys-typescale.values(),
|
||||
);
|
||||
@ -136,7 +132,12 @@ $_default: (
|
||||
$original-tokens,
|
||||
$supported-tokens: $supported-tokens,
|
||||
$unsupported-tokens: $unsupported-tokens,
|
||||
$new-tokens: _get-new-tokens($deps, $exclude-hardcoded-values),
|
||||
$new-tokens: (
|
||||
'top-space': if($exclude-hardcoded-values, null, 12px),
|
||||
'bottom-space': if($exclude-hardcoded-values, null, 12px),
|
||||
'disabled-opacity':
|
||||
map.get($original-tokens, 'list-item-disabled-label-text-opacity'),
|
||||
),
|
||||
$renamed-tokens: _get-renamed-tokens($original-tokens)
|
||||
);
|
||||
|
||||
@ -157,20 +158,3 @@ $_default: (
|
||||
|
||||
@return $renamed-tokens;
|
||||
}
|
||||
|
||||
@function _get-new-tokens($deps, $exclude-hardcoded-values) {
|
||||
// Values pulled from b/198759625 spreadsheet
|
||||
@return (
|
||||
// go/keep-sorted start
|
||||
'leading-element-leading-space': if($exclude-hardcoded-values, null, 16px),
|
||||
'leading-video-leading-space': if($exclude-hardcoded-values, null, 0px),
|
||||
// Commented out until the comments in the spreadsheet linked above are
|
||||
// resolved regarding vertical alignment.
|
||||
// 'leading-item-top-space': if($exclude-hardcoded-values, null, 8px),
|
||||
// 'leading-video-top-space': if($exclude-hardcoded-values, null, 0px),
|
||||
// 'leading-item-bottom-space': if($exclude-hardcoded-values, null, 8px),
|
||||
// 'leading-video-bottom-space': if($exclude-hardcoded-values, null, 0px),
|
||||
'trailing-element-headline-trailing-element-space': 16px,
|
||||
// go/keep-sorted end
|
||||
);
|
||||
}
|
||||
|
@ -8,10 +8,6 @@
|
||||
// go/keep-sorted end
|
||||
// go/keep-sorted start
|
||||
@use './md-sys-color';
|
||||
@use './md-sys-elevation';
|
||||
@use './md-sys-shape';
|
||||
@use './md-sys-state';
|
||||
@use './md-sys-typescale';
|
||||
@use './v0_192/md-comp-list';
|
||||
@use './values';
|
||||
// go/keep-sorted end
|
||||
@ -22,117 +18,15 @@ $supported-tokens: (
|
||||
// go/keep-sorted end
|
||||
);
|
||||
|
||||
$unsupported-tokens: (
|
||||
// go/keep-sorted start
|
||||
'divider-leading-space',
|
||||
'divider-trailing-space',
|
||||
'list-item-container-elevation',
|
||||
'list-item-container-shape',
|
||||
'list-item-disabled-label-text-color',
|
||||
'list-item-disabled-label-text-opacity',
|
||||
'list-item-disabled-leading-icon-color',
|
||||
'list-item-disabled-leading-icon-opacity',
|
||||
'list-item-disabled-state-layer-color',
|
||||
'list-item-disabled-state-layer-opacity',
|
||||
'list-item-disabled-trailing-icon-color',
|
||||
'list-item-disabled-trailing-icon-opacity',
|
||||
'list-item-dragged-container-elevation',
|
||||
'list-item-dragged-label-text-color',
|
||||
'list-item-dragged-leading-icon-icon-color',
|
||||
'list-item-dragged-state-layer-color',
|
||||
'list-item-dragged-state-layer-opacity',
|
||||
'list-item-dragged-trailing-icon-icon-color',
|
||||
'list-item-focus-label-text-color',
|
||||
'list-item-focus-leading-icon-icon-color',
|
||||
'list-item-focus-state-layer-color',
|
||||
'list-item-focus-state-layer-opacity',
|
||||
'list-item-focus-trailing-icon-icon-color',
|
||||
'list-item-hover-label-text-color',
|
||||
'list-item-hover-leading-icon-icon-color',
|
||||
'list-item-hover-state-layer-color',
|
||||
'list-item-hover-state-layer-opacity',
|
||||
'list-item-hover-trailing-icon-icon-color',
|
||||
'list-item-label-text-color',
|
||||
'list-item-label-text-font',
|
||||
'list-item-label-text-line-height',
|
||||
'list-item-label-text-size',
|
||||
'list-item-label-text-tracking',
|
||||
'list-item-label-text-type',
|
||||
'list-item-label-text-weight',
|
||||
'list-item-large-leading-video-height',
|
||||
'list-item-leading-avatar-color',
|
||||
'list-item-leading-avatar-label-color',
|
||||
'list-item-leading-avatar-label-font',
|
||||
'list-item-leading-avatar-label-line-height',
|
||||
'list-item-leading-avatar-label-size',
|
||||
'list-item-leading-avatar-label-tracking',
|
||||
'list-item-leading-avatar-label-type',
|
||||
'list-item-leading-avatar-label-weight',
|
||||
'list-item-leading-avatar-shape',
|
||||
'list-item-leading-avatar-size',
|
||||
'list-item-leading-icon-color',
|
||||
'list-item-leading-icon-size',
|
||||
'list-item-leading-image-height',
|
||||
'list-item-leading-image-shape',
|
||||
'list-item-leading-image-width',
|
||||
'list-item-leading-space',
|
||||
'list-item-leading-video-shape',
|
||||
'list-item-leading-video-width',
|
||||
'list-item-one-line-container-height',
|
||||
'list-item-overline-color',
|
||||
'list-item-overline-font',
|
||||
'list-item-overline-line-height',
|
||||
'list-item-overline-size',
|
||||
'list-item-overline-tracking',
|
||||
'list-item-overline-type',
|
||||
'list-item-overline-weight',
|
||||
'list-item-pressed-label-text-color',
|
||||
'list-item-pressed-leading-icon-icon-color',
|
||||
'list-item-pressed-state-layer-color',
|
||||
'list-item-pressed-state-layer-opacity',
|
||||
'list-item-pressed-trailing-icon-icon-color',
|
||||
'list-item-selected-trailing-icon-color',
|
||||
'list-item-small-leading-video-height',
|
||||
'list-item-supporting-text-color',
|
||||
'list-item-supporting-text-font',
|
||||
'list-item-supporting-text-line-height',
|
||||
'list-item-supporting-text-size',
|
||||
'list-item-supporting-text-tracking',
|
||||
'list-item-supporting-text-type',
|
||||
'list-item-supporting-text-weight',
|
||||
'list-item-three-line-container-height',
|
||||
'list-item-trailing-icon-color',
|
||||
'list-item-trailing-icon-size',
|
||||
'list-item-trailing-space',
|
||||
'list-item-trailing-supporting-text-color',
|
||||
'list-item-trailing-supporting-text-font',
|
||||
'list-item-trailing-supporting-text-line-height',
|
||||
'list-item-trailing-supporting-text-size',
|
||||
'list-item-trailing-supporting-text-tracking',
|
||||
'list-item-trailing-supporting-text-type',
|
||||
'list-item-trailing-supporting-text-weight',
|
||||
'list-item-two-line-container-height',
|
||||
'list-item-unselected-trailing-icon-color',
|
||||
// go/keep-sorted end
|
||||
);
|
||||
|
||||
$_default: (
|
||||
'md-sys-color': md-sys-color.values-light(),
|
||||
'md-sys-elevation': md-sys-elevation.values(),
|
||||
'md-sys-shape': md-sys-shape.values(),
|
||||
'md-sys-state': md-sys-state.values(),
|
||||
'md-sys-typescale': md-sys-typescale.values(),
|
||||
);
|
||||
|
||||
@function values($deps: $_default, $exclude-hardcoded-values: false) {
|
||||
$tokens: values.validate(
|
||||
md-comp-list.values($deps, $exclude-hardcoded-values),
|
||||
$supported-tokens: $supported-tokens,
|
||||
$unsupported-tokens: $unsupported-tokens,
|
||||
$renamed-tokens: (
|
||||
'list-item-container-color': 'container-color',
|
||||
)
|
||||
$list-tokens: md-comp-list.values($deps, $exclude-hardcoded-values);
|
||||
$tokens: (
|
||||
'container-color': map.get($list-tokens, 'list-item-container-color'),
|
||||
);
|
||||
|
||||
@return $tokens;
|
||||
@return values.validate($tokens, $supported-tokens: $supported-tokens);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user