Skip to content

Commit

Permalink
fix(tab): move aria to host
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 563200891
  • Loading branch information
asyncLiz authored and copybara-github committed Sep 6, 2023
1 parent acd40a2 commit 6f24bd2
Show file tree
Hide file tree
Showing 4 changed files with 29 additions and 56 deletions.
3 changes: 1 addition & 2 deletions tabs/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import {Tabs} from './internal/tabs.js';
export class TabHarness extends Harness<Tab> {
override async getInteractiveElement() {
await this.element.updateComplete;
return this.element.renderRoot
.querySelector<HTMLButtonElement|HTMLLinkElement>('.button')!;
return this.element as HTMLElement;
}

private async completeIndicatorAnimation() {
Expand Down
13 changes: 2 additions & 11 deletions tabs/internal/_tab.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
outline: none;
-webkit-tap-highlight-color: transparent;
vertical-align: middle;
user-select: none;

@include ripple.theme(
(
Expand All @@ -45,28 +46,18 @@
}

.button {
appearance: none;
box-sizing: border-box;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
outline: none;
user-select: none;
vertical-align: middle;
background: none;
text-decoration: none;
width: 100%;
position: relative;
padding: 0 16px;
margin: 0;
z-index: 0; // Ensure this is a stacking context so the indicator displays
font: var(--_label-text-type);
color: var(--_label-text-color);

&::-moz-focus-inner {
padding: 0;
border: 0;
}
}

.button::before {
Expand Down
67 changes: 25 additions & 42 deletions tabs/internal/tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import {html, isServer, LitElement, nothing, PropertyValues} from 'lit';
import {property, query, queryAssignedElements, queryAssignedNodes, state} from 'lit/decorators.js';
import {classMap} from 'lit/directives/class-map.js';

import {ARIAMixinStrict} from '../../internal/aria/aria.js';
import {requestUpdateOnAriaChange} from '../../internal/aria/delegate.js';
import {dispatchActivationClick, isActivationClick} from '../../internal/controller/events.js';
import {polyfillElementInternalsAria, setupHostAria} from '../../internal/aria/aria.js';
import {EASING} from '../../internal/motion/animation.js';

interface Tabs extends HTMLElement {
Expand All @@ -28,23 +26,14 @@ interface Tabs extends HTMLElement {
*/
export class Tab extends LitElement {
static {
requestUpdateOnAriaChange(Tab);
setupHostAria(Tab);
}

/** @nocollapse */
static override shadowRootOptions:
ShadowRootInit = {mode: 'open', delegatesFocus: true};

/**
* Whether or not the tab is `selected`.
**/
@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.
*/
Expand All @@ -55,8 +44,6 @@ export class Tab extends LitElement {
*/
@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.
/** @private */
@query('.indicator') readonly indicator!: HTMLElement;
Expand All @@ -65,44 +52,33 @@ export class Tab extends LitElement {
private readonly assignedDefaultNodes!: Node[];
@queryAssignedElements({slot: 'icon', flatten: true})
private readonly assignedIcons!: HTMLElement[];
private readonly internals = polyfillElementInternalsAria(
this, (this as HTMLElement /* needed for closure */).attachInternals());

constructor() {
super();
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() {
const indicator = html`<div class="indicator"></div>`;
// Needed for closure conformance
const {ariaLabel} = this as ARIAMixinStrict;
return html`
<button
class="button"
role="tab"
.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>
<div class="button" role="presentation">
<md-focus-ring part="focus-ring" inward
.control=${this}></md-focus-ring>
<md-elevation></md-elevation>
<md-ripple></md-ripple>
<div class="content ${classMap(this.getContentClasses())}">
<md-ripple .control=${this}></md-ripple>
<div class="content ${classMap(this.getContentClasses())}"
role="presentation">
<slot name="icon" @slotchange=${this.handleIconSlotChange}></slot>
<slot @slotchange=${this.handleSlotChange}></slot>
${this.fullWidthIndicator ? nothing : indicator}
</div>
${this.fullWidthIndicator ? indicator : nothing}
</button>`;
</div>`;
}

protected getContentClasses() {
Expand All @@ -114,17 +90,24 @@ export class Tab extends LitElement {

protected override updated(changed: PropertyValues) {
if (changed.has('selected')) {
this.internals.ariaSelected = String(this.selected);
this.animateSelected();
}
}

private readonly handleActivationClick = (event: MouseEvent) => {
if (!isActivationClick((event)) || !this.button) {
private async handleKeydown(event: KeyboardEvent) {
// Allow event to bubble.
await 0;
if (event.defaultPrevented) {
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() {
this.indicator.getAnimations().forEach(a => {
Expand Down
2 changes: 1 addition & 1 deletion tabs/internal/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ export class Tabs extends LitElement {

private updateFocusableItem(focusableItem: HTMLElement|null) {
for (const item of this.items) {
item.focusable = item === focusableItem;
item.tabIndex = item === focusableItem ? 0 : -1;
}
}

Expand Down

0 comments on commit 6f24bd2

Please sign in to comment.