mirror of
https://github.com/material-components/material-web.git
synced 2024-09-11 21:57:41 +03:00
fix(tab): move aria to host
PiperOrigin-RevId: 563200891
This commit is contained in:
parent
acd40a2f57
commit
6f24bd222a
@ -15,8 +15,7 @@ import {Tabs} from './internal/tabs.js';
|
|||||||
export class TabHarness extends Harness<Tab> {
|
export class TabHarness extends Harness<Tab> {
|
||||||
override async getInteractiveElement() {
|
override async getInteractiveElement() {
|
||||||
await this.element.updateComplete;
|
await this.element.updateComplete;
|
||||||
return this.element.renderRoot
|
return this.element as HTMLElement;
|
||||||
.querySelector<HTMLButtonElement|HTMLLinkElement>('.button')!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async completeIndicatorAnimation() {
|
private async completeIndicatorAnimation() {
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
outline: none;
|
outline: none;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
@include ripple.theme(
|
@include ripple.theme(
|
||||||
(
|
(
|
||||||
@ -45,16 +46,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
appearance: none;
|
box-sizing: border-box;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
user-select: none;
|
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
background: none;
|
|
||||||
text-decoration: none;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 16px;
|
padding: 0 16px;
|
||||||
@ -62,11 +58,6 @@
|
|||||||
z-index: 0; // Ensure this is a stacking context so the indicator displays
|
z-index: 0; // Ensure this is a stacking context so the indicator displays
|
||||||
font: var(--_label-text-type);
|
font: var(--_label-text-type);
|
||||||
color: var(--_label-text-color);
|
color: var(--_label-text-color);
|
||||||
|
|
||||||
&::-moz-focus-inner {
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.button::before {
|
.button::before {
|
||||||
|
@ -12,9 +12,7 @@ import {html, isServer, LitElement, nothing, PropertyValues} from 'lit';
|
|||||||
import {property, query, queryAssignedElements, queryAssignedNodes, state} from 'lit/decorators.js';
|
import {property, query, queryAssignedElements, queryAssignedNodes, state} from 'lit/decorators.js';
|
||||||
import {classMap} from 'lit/directives/class-map.js';
|
import {classMap} from 'lit/directives/class-map.js';
|
||||||
|
|
||||||
import {ARIAMixinStrict} from '../../internal/aria/aria.js';
|
import {polyfillElementInternalsAria, setupHostAria} from '../../internal/aria/aria.js';
|
||||||
import {requestUpdateOnAriaChange} from '../../internal/aria/delegate.js';
|
|
||||||
import {dispatchActivationClick, isActivationClick} from '../../internal/controller/events.js';
|
|
||||||
import {EASING} from '../../internal/motion/animation.js';
|
import {EASING} from '../../internal/motion/animation.js';
|
||||||
|
|
||||||
interface Tabs extends HTMLElement {
|
interface Tabs extends HTMLElement {
|
||||||
@ -28,23 +26,14 @@ interface Tabs extends HTMLElement {
|
|||||||
*/
|
*/
|
||||||
export class Tab extends LitElement {
|
export class Tab extends LitElement {
|
||||||
static {
|
static {
|
||||||
requestUpdateOnAriaChange(Tab);
|
setupHostAria(Tab);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @nocollapse */
|
|
||||||
static override shadowRootOptions:
|
|
||||||
ShadowRootInit = {mode: 'open', delegatesFocus: true};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the tab is `selected`.
|
* Whether or not the tab is `selected`.
|
||||||
**/
|
**/
|
||||||
@property({type: Boolean, reflect: true}) selected = false;
|
@property({type: Boolean, reflect: true}) selected = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether or not the tab is `focusable`.
|
|
||||||
*/
|
|
||||||
@property({type: Boolean}) focusable = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In SSR, set this to true when an icon is present.
|
* In SSR, set this to true when an icon is present.
|
||||||
*/
|
*/
|
||||||
@ -55,8 +44,6 @@ export class Tab extends LitElement {
|
|||||||
*/
|
*/
|
||||||
@property({type: Boolean, attribute: 'icon-only'}) iconOnly = false;
|
@property({type: Boolean, attribute: 'icon-only'}) iconOnly = false;
|
||||||
|
|
||||||
@query('.button') private readonly button!: HTMLElement|null;
|
|
||||||
|
|
||||||
// note, this is public so it can participate in selection animation.
|
// note, this is public so it can participate in selection animation.
|
||||||
/** @private */
|
/** @private */
|
||||||
@query('.indicator') readonly indicator!: HTMLElement;
|
@query('.indicator') readonly indicator!: HTMLElement;
|
||||||
@ -65,44 +52,33 @@ export class Tab extends LitElement {
|
|||||||
private readonly assignedDefaultNodes!: Node[];
|
private readonly assignedDefaultNodes!: Node[];
|
||||||
@queryAssignedElements({slot: 'icon', flatten: true})
|
@queryAssignedElements({slot: 'icon', flatten: true})
|
||||||
private readonly assignedIcons!: HTMLElement[];
|
private readonly assignedIcons!: HTMLElement[];
|
||||||
|
private readonly internals = polyfillElementInternalsAria(
|
||||||
|
this, (this as HTMLElement /* needed for closure */).attachInternals());
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
if (!isServer) {
|
if (!isServer) {
|
||||||
this.addEventListener('click', this.handleActivationClick);
|
this.internals.role = 'tab';
|
||||||
|
this.addEventListener('keydown', this.handleKeydown.bind(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override focus() {
|
|
||||||
this.button?.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
override blur() {
|
|
||||||
this.button?.blur();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override render() {
|
protected override render() {
|
||||||
const indicator = html`<div class="indicator"></div>`;
|
const indicator = html`<div class="indicator"></div>`;
|
||||||
// Needed for closure conformance
|
|
||||||
const {ariaLabel} = this as ARIAMixinStrict;
|
|
||||||
return html`
|
return html`
|
||||||
<button
|
<div class="button" role="presentation">
|
||||||
class="button"
|
<md-focus-ring part="focus-ring" inward
|
||||||
role="tab"
|
.control=${this}></md-focus-ring>
|
||||||
.tabIndex=${this.focusable ? 0 : -1}
|
|
||||||
aria-selected=${this.selected ? 'true' : 'false'}
|
|
||||||
aria-label=${ariaLabel || nothing}
|
|
||||||
>
|
|
||||||
<md-focus-ring part="focus-ring" inward></md-focus-ring>
|
|
||||||
<md-elevation></md-elevation>
|
<md-elevation></md-elevation>
|
||||||
<md-ripple></md-ripple>
|
<md-ripple .control=${this}></md-ripple>
|
||||||
<div class="content ${classMap(this.getContentClasses())}">
|
<div class="content ${classMap(this.getContentClasses())}"
|
||||||
|
role="presentation">
|
||||||
<slot name="icon" @slotchange=${this.handleIconSlotChange}></slot>
|
<slot name="icon" @slotchange=${this.handleIconSlotChange}></slot>
|
||||||
<slot @slotchange=${this.handleSlotChange}></slot>
|
<slot @slotchange=${this.handleSlotChange}></slot>
|
||||||
${this.fullWidthIndicator ? nothing : indicator}
|
${this.fullWidthIndicator ? nothing : indicator}
|
||||||
</div>
|
</div>
|
||||||
${this.fullWidthIndicator ? indicator : nothing}
|
${this.fullWidthIndicator ? indicator : nothing}
|
||||||
</button>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getContentClasses() {
|
protected getContentClasses() {
|
||||||
@ -114,17 +90,24 @@ export class Tab extends LitElement {
|
|||||||
|
|
||||||
protected override updated(changed: PropertyValues) {
|
protected override updated(changed: PropertyValues) {
|
||||||
if (changed.has('selected')) {
|
if (changed.has('selected')) {
|
||||||
|
this.internals.ariaSelected = String(this.selected);
|
||||||
this.animateSelected();
|
this.animateSelected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly handleActivationClick = (event: MouseEvent) => {
|
private async handleKeydown(event: KeyboardEvent) {
|
||||||
if (!isActivationClick((event)) || !this.button) {
|
// Allow event to bubble.
|
||||||
|
await 0;
|
||||||
|
if (event.defaultPrevented) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.focus();
|
|
||||||
dispatchActivationClick(this.button);
|
if (event.key === 'Enter' || event.key === ' ') {
|
||||||
};
|
// Prevent default behavior such as scrolling when pressing spacebar.
|
||||||
|
event.preventDefault();
|
||||||
|
this.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private animateSelected() {
|
private animateSelected() {
|
||||||
this.indicator.getAnimations().forEach(a => {
|
this.indicator.getAnimations().forEach(a => {
|
||||||
|
@ -227,7 +227,7 @@ export class Tabs extends LitElement {
|
|||||||
|
|
||||||
private updateFocusableItem(focusableItem: HTMLElement|null) {
|
private updateFocusableItem(focusableItem: HTMLElement|null) {
|
||||||
for (const item of this.items) {
|
for (const item of this.items) {
|
||||||
item.focusable = item === focusableItem;
|
item.tabIndex = item === focusableItem ? 0 : -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user