@@ -12,9 +12,7 @@ import {html, isServer, LitElement, nothing, PropertyValues} from 'lit';
1212import { property , query , queryAssignedElements , queryAssignedNodes , state } from 'lit/decorators.js' ;
1313import { classMap } from 'lit/directives/class-map.js' ;
1414
15- import { ARIAMixinStrict } from '../../internal/aria/aria.js' ;
16- import { requestUpdateOnAriaChange } from '../../internal/aria/delegate.js' ;
17- import { dispatchActivationClick , isActivationClick } from '../../internal/controller/events.js' ;
15+ import { polyfillElementInternalsAria , setupHostAria } from '../../internal/aria/aria.js' ;
1816import { EASING } from '../../internal/motion/animation.js' ;
1917
2018interface Tabs extends HTMLElement {
@@ -28,23 +26,14 @@ interface Tabs extends HTMLElement {
2826 */
2927export class Tab extends LitElement {
3028 static {
31- requestUpdateOnAriaChange ( Tab ) ;
29+ setupHostAria ( Tab ) ;
3230 }
3331
34- /** @nocollapse */
35- static override shadowRootOptions :
36- ShadowRootInit = { mode : 'open' , delegatesFocus : true } ;
37-
3832 /**
3933 * Whether or not the tab is `selected`.
4034 **/
4135 @property ( { type : Boolean , reflect : true } ) selected = false ;
4236
43- /**
44- * Whether or not the tab is `focusable`.
45- */
46- @property ( { type : Boolean } ) focusable = false ;
47-
4837 /**
4938 * In SSR, set this to true when an icon is present.
5039 */
@@ -55,8 +44,6 @@ export class Tab extends LitElement {
5544 */
5645 @property ( { type : Boolean , attribute : 'icon-only' } ) iconOnly = false ;
5746
58- @query ( '.button' ) private readonly button ! : HTMLElement | null ;
59-
6047 // note, this is public so it can participate in selection animation.
6148 /** @private */
6249 @query ( '.indicator' ) readonly indicator ! : HTMLElement ;
@@ -65,44 +52,33 @@ export class Tab extends LitElement {
6552 private readonly assignedDefaultNodes ! : Node [ ] ;
6653 @queryAssignedElements ( { slot : 'icon' , flatten : true } )
6754 private readonly assignedIcons ! : HTMLElement [ ] ;
55+ private readonly internals = polyfillElementInternalsAria (
56+ this , ( this as HTMLElement /* needed for closure */ ) . attachInternals ( ) ) ;
6857
6958 constructor ( ) {
7059 super ( ) ;
7160 if ( ! isServer ) {
72- this . addEventListener ( 'click' , this . handleActivationClick ) ;
61+ this . internals . role = 'tab' ;
62+ this . addEventListener ( 'keydown' , this . handleKeydown . bind ( this ) ) ;
7363 }
7464 }
7565
76- override focus ( ) {
77- this . button ?. focus ( ) ;
78- }
79-
80- override blur ( ) {
81- this . button ?. blur ( ) ;
82- }
83-
8466 protected override render ( ) {
8567 const indicator = html `< div class ="indicator "> </ div > ` ;
86- // Needed for closure conformance
87- const { ariaLabel} = this as ARIAMixinStrict ;
8868 return html `
89- < button
90- class ="button "
91- role ="tab "
92- .tabIndex =${ this . focusable ? 0 : - 1 }
93- aria-selected =${ this . selected ? 'true' : 'false' }
94- aria-label=${ ariaLabel || nothing }
95- >
96- < md-focus-ring part ="focus-ring " inward > </ md-focus-ring >
69+ < div class ="button " role ="presentation ">
70+ < md-focus-ring part ="focus-ring " inward
71+ .control =${ this } > </ md-focus-ring >
9772 < md-elevation > </ md-elevation >
98- < md-ripple > </ md-ripple >
99- < div class ="content ${ classMap ( this . getContentClasses ( ) ) } ">
73+ < md-ripple .control =${ this } > </ md-ripple >
74+ < div class ="content ${ classMap ( this . getContentClasses ( ) ) } "
75+ role ="presentation ">
10076 < slot name ="icon " @slotchange =${ this . handleIconSlotChange } > </ slot >
10177 < slot @slotchange =${ this . handleSlotChange } > </ slot >
10278 ${ this . fullWidthIndicator ? nothing : indicator }
10379 </ div >
10480 ${ this . fullWidthIndicator ? indicator : nothing }
105- </ button > ` ;
81+ </ div > ` ;
10682 }
10783
10884 protected getContentClasses ( ) {
@@ -114,17 +90,24 @@ export class Tab extends LitElement {
11490
11591 protected override updated ( changed : PropertyValues ) {
11692 if ( changed . has ( 'selected' ) ) {
93+ this . internals . ariaSelected = String ( this . selected ) ;
11794 this . animateSelected ( ) ;
11895 }
11996 }
12097
121- private readonly handleActivationClick = ( event : MouseEvent ) => {
122- if ( ! isActivationClick ( ( event ) ) || ! this . button ) {
98+ private async handleKeydown ( event : KeyboardEvent ) {
99+ // Allow event to bubble.
100+ await 0 ;
101+ if ( event . defaultPrevented ) {
123102 return ;
124103 }
125- this . focus ( ) ;
126- dispatchActivationClick ( this . button ) ;
127- } ;
104+
105+ if ( event . key === 'Enter' || event . key === ' ' ) {
106+ // Prevent default behavior such as scrolling when pressing spacebar.
107+ event . preventDefault ( ) ;
108+ this . click ( ) ;
109+ }
110+ }
128111
129112 private animateSelected ( ) {
130113 this . indicator . getAnimations ( ) . forEach ( a => {
0 commit comments