11const HTMLElement = globalThis . HTMLElement || ( null as unknown as ( typeof window ) [ 'HTMLElement' ] )
22
3- type IncrementKeyCode = 'ArrowRight' | 'ArrowDown'
4- type DecrementKeyCode = 'ArrowUp' | 'ArrowLeft'
5-
63export class TabContainerChangeEvent extends Event {
74 constructor ( type : string , { tab, panel, ...init } : EventInit & { tab ?: Element ; panel ?: Element } ) {
85 super ( type , init )
@@ -27,17 +24,6 @@ export class TabContainerChangeEvent extends Event {
2724 }
2825}
2926
30- function getNavigationKeyCodes ( vertical : boolean ) : [ IncrementKeyCode [ ] , DecrementKeyCode [ ] ] {
31- if ( vertical ) {
32- return [
33- [ 'ArrowDown' , 'ArrowRight' ] ,
34- [ 'ArrowUp' , 'ArrowLeft' ] ,
35- ]
36- } else {
37- return [ [ 'ArrowRight' ] , [ 'ArrowLeft' ] ]
38- }
39- }
40-
4127export class TabContainerElement extends HTMLElement {
4228 static define ( tag = 'tab-container' , registry = customElements ) {
4329 registry . define ( tag , this )
@@ -91,49 +77,8 @@ export class TabContainerElement extends HTMLElement {
9177 }
9278
9379 connectedCallback ( ) : void {
94- this . addEventListener ( 'keydown' , ( event : KeyboardEvent ) => {
95- const target = event . target
96- if ( ! ( target instanceof HTMLElement ) ) return
97- if ( target . closest ( this . tagName ) !== this ) return
98- if ( target . getAttribute ( 'role' ) !== 'tab' && ! target . closest ( '[role="tablist"]' ) ) return
99- const tabs = this . #tabs
100- const currentIndex = tabs . indexOf ( tabs . find ( tab => tab . matches ( '[aria-selected="true"]' ) ) ! )
101- const [ incrementKeys , decrementKeys ] = getNavigationKeyCodes (
102- target . closest ( '[role="tablist"]' ) ?. getAttribute ( 'aria-orientation' ) === 'vertical' ,
103- )
104-
105- if ( incrementKeys . some ( code => event . code === code ) ) {
106- let index = currentIndex + 1
107- if ( index >= tabs . length ) index = 0
108- this . selectTab ( index )
109- } else if ( decrementKeys . some ( code => event . code === code ) ) {
110- let index = currentIndex - 1
111- if ( index < 0 ) index = tabs . length - 1
112- this . selectTab ( index )
113- } else if ( event . code === 'Home' ) {
114- this . selectTab ( 0 )
115- event . preventDefault ( )
116- } else if ( event . code === 'End' ) {
117- this . selectTab ( tabs . length - 1 )
118- event . preventDefault ( )
119- }
120- } )
121-
122- this . addEventListener ( 'click' , ( event : MouseEvent ) => {
123- const tabs = this . #tabs
124-
125- if ( ! ( event . target instanceof Element ) ) return
126- if ( event . target . closest ( this . tagName ) !== this ) return
127-
128- const tab = event . target . closest ( '[role="tab"]' )
129- if ( ! ( tab instanceof HTMLElement ) || ! tab . closest ( '[role="tablist"]' ) ) {
130- return
131- }
132-
133- const index = tabs . indexOf ( tab )
134- this . selectTab ( index )
135- } )
136-
80+ this . addEventListener ( 'keydown' , this )
81+ this . addEventListener ( 'click' , this )
13782 for ( const tab of this . #tabs) {
13883 if ( ! tab . hasAttribute ( 'aria-selected' ) ) {
13984 tab . setAttribute ( 'aria-selected' , 'false' )
@@ -148,6 +93,47 @@ export class TabContainerElement extends HTMLElement {
14893 }
14994 }
15095
96+ handleEvent ( event : Event ) {
97+ if ( event . type === 'click' ) return this . #handleClick( event as MouseEvent )
98+ if ( event . type === 'keydown' ) return this . #handleKeydown( event as KeyboardEvent )
99+ }
100+
101+ #handleKeydown( event : KeyboardEvent ) {
102+ const tab = ( event . target as HTMLElement ) ?. closest ?.( '[role="tab"]' )
103+ if ( ! tab ) return
104+ const tabs = getTabs ( this )
105+ if ( ! tabs . includes ( tab as HTMLElement ) ) return
106+
107+ const currentIndex = tabs . indexOf ( tabs . find ( e => e . matches ( '[aria-selected="true"]' ) ) ! )
108+ const vertical = tab . closest ( '[role="tablist"]' ) ?. getAttribute ( 'aria-orientation' ) === 'vertical'
109+ const prevTab = event . code === 'ArrowLeft' || ( vertical && event . code === 'ArrowUp' )
110+ const nextTab = event . code === 'ArrowRight' || ( vertical && event . code === 'ArrowDown' )
111+
112+ if ( nextTab ) {
113+ let index = currentIndex + 1
114+ if ( index >= tabs . length ) index = 0
115+ this . selectTab ( index )
116+ } else if ( prevTab ) {
117+ let index = currentIndex - 1
118+ if ( index < 0 ) index = tabs . length - 1
119+ this . selectTab ( index )
120+ } else if ( event . code === 'Home' ) {
121+ this . selectTab ( 0 )
122+ event . preventDefault ( )
123+ } else if ( event . code === 'End' ) {
124+ this . selectTab ( tabs . length - 1 )
125+ event . preventDefault ( )
126+ }
127+ }
128+
129+ #handleClick( event : MouseEvent ) {
130+ const tab = ( event . target as HTMLElement ) ?. closest ?.( '[role=tab]' )
131+ if ( ! tab ) return
132+ const tabs = getTabs ( this )
133+ const index = tabs . indexOf ( tab as HTMLElement )
134+ if ( index >= 0 ) this . selectTab ( index )
135+ }
136+
151137 selectTab ( index : number ) : void {
152138 const tabs = this . #tabs
153139 const panels = Array . from ( this . querySelectorAll < HTMLElement > ( '[role="tabpanel"]' ) ) . filter (
0 commit comments