11const HTMLElement = globalThis . HTMLElement || ( null as unknown as ( typeof window ) [ 'HTMLElement' ] )
22
3- type IncrementKeyCode = 'ArrowRight' | 'ArrowDown'
4- type DecrementKeyCode = 'ArrowUp' | 'ArrowLeft'
5-
63function getTabs ( el : TabContainerElement ) : HTMLElement [ ] {
74 return Array . from ( el . querySelectorAll < HTMLElement > ( '[role="tablist"] [role="tab"]' ) ) . filter (
85 tab => tab instanceof HTMLElement && tab . closest ( el . tagName ) === el ,
@@ -33,17 +30,6 @@ export class TabContainerChangeEvent extends Event {
3330 }
3431}
3532
36- function getNavigationKeyCodes ( vertical : boolean ) : [ IncrementKeyCode [ ] , DecrementKeyCode [ ] ] {
37- if ( vertical ) {
38- return [
39- [ 'ArrowDown' , 'ArrowRight' ] ,
40- [ 'ArrowUp' , 'ArrowLeft' ] ,
41- ]
42- } else {
43- return [ [ 'ArrowRight' ] , [ 'ArrowLeft' ] ]
44- }
45- }
46-
4733export class TabContainerElement extends HTMLElement {
4834 static define ( tag = 'tab-container' , registry = customElements ) {
4935 registry . define ( tag , this )
@@ -87,49 +73,8 @@ export class TabContainerElement extends HTMLElement {
8773 }
8874
8975 connectedCallback ( ) : void {
90- this . addEventListener ( 'keydown' , ( event : KeyboardEvent ) => {
91- const target = event . target
92- if ( ! ( target instanceof HTMLElement ) ) return
93- if ( target . closest ( this . tagName ) !== this ) return
94- if ( target . getAttribute ( 'role' ) !== 'tab' && ! target . closest ( '[role="tablist"]' ) ) return
95- const tabs = getTabs ( this )
96- const currentIndex = tabs . indexOf ( tabs . find ( tab => tab . matches ( '[aria-selected="true"]' ) ) ! )
97- const [ incrementKeys , decrementKeys ] = getNavigationKeyCodes (
98- target . closest ( '[role="tablist"]' ) ?. getAttribute ( 'aria-orientation' ) === 'vertical' ,
99- )
100-
101- if ( incrementKeys . some ( code => event . code === code ) ) {
102- let index = currentIndex + 1
103- if ( index >= tabs . length ) index = 0
104- this . selectTab ( index )
105- } else if ( decrementKeys . some ( code => event . code === code ) ) {
106- let index = currentIndex - 1
107- if ( index < 0 ) index = tabs . length - 1
108- this . selectTab ( index )
109- } else if ( event . code === 'Home' ) {
110- this . selectTab ( 0 )
111- event . preventDefault ( )
112- } else if ( event . code === 'End' ) {
113- this . selectTab ( tabs . length - 1 )
114- event . preventDefault ( )
115- }
116- } )
117-
118- this . addEventListener ( 'click' , ( event : MouseEvent ) => {
119- const tabs = getTabs ( this )
120-
121- if ( ! ( event . target instanceof Element ) ) return
122- if ( event . target . closest ( this . tagName ) !== this ) return
123-
124- const tab = event . target . closest ( '[role="tab"]' )
125- if ( ! ( tab instanceof HTMLElement ) || ! tab . closest ( '[role="tablist"]' ) ) {
126- return
127- }
128-
129- const index = tabs . indexOf ( tab )
130- this . selectTab ( index )
131- } )
132-
76+ this . addEventListener ( 'keydown' , this )
77+ this . addEventListener ( 'click' , this )
13378 for ( const tab of getTabs ( this ) ) {
13479 if ( ! tab . hasAttribute ( 'aria-selected' ) ) {
13580 tab . setAttribute ( 'aria-selected' , 'false' )
@@ -144,6 +89,47 @@ export class TabContainerElement extends HTMLElement {
14489 }
14590 }
14691
92+ handleEvent ( event : Event ) {
93+ if ( event . type === 'click' ) return this . #handleClick( event as MouseEvent )
94+ if ( event . type === 'keydown' ) return this . #handleKeydown( event as KeyboardEvent )
95+ }
96+
97+ #handleKeydown( event : KeyboardEvent ) {
98+ const tab = ( event . target as HTMLElement ) ?. closest ?.( '[role="tab"]' )
99+ if ( ! tab ) return
100+ const tabs = getTabs ( this )
101+ if ( ! tabs . includes ( tab as HTMLElement ) ) return
102+
103+ const currentIndex = tabs . indexOf ( tabs . find ( e => e . matches ( '[aria-selected="true"]' ) ) ! )
104+ const vertical = tab . closest ( '[role="tablist"]' ) ?. getAttribute ( 'aria-orientation' ) === 'vertical'
105+ const prevTab = event . code === 'ArrowLeft' || ( vertical && event . code === 'ArrowUp' )
106+ const nextTab = event . code === 'ArrowRight' || ( vertical && event . code === 'ArrowDown' )
107+
108+ if ( nextTab ) {
109+ let index = currentIndex + 1
110+ if ( index >= tabs . length ) index = 0
111+ this . selectTab ( index )
112+ } else if ( prevTab ) {
113+ let index = currentIndex - 1
114+ if ( index < 0 ) index = tabs . length - 1
115+ this . selectTab ( index )
116+ } else if ( event . code === 'Home' ) {
117+ this . selectTab ( 0 )
118+ event . preventDefault ( )
119+ } else if ( event . code === 'End' ) {
120+ this . selectTab ( tabs . length - 1 )
121+ event . preventDefault ( )
122+ }
123+ }
124+
125+ #handleClick( event : MouseEvent ) {
126+ const tab = ( event . target as HTMLElement ) ?. closest ?.( '[role=tab]' )
127+ if ( ! tab ) return
128+ const tabs = getTabs ( this )
129+ const index = tabs . indexOf ( tab as HTMLElement )
130+ if ( index >= 0 ) this . selectTab ( index )
131+ }
132+
147133 selectTab ( index : number ) : void {
148134 const tabs = getTabs ( this )
149135 const panels = Array . from ( this . querySelectorAll < HTMLElement > ( '[role="tabpanel"]' ) ) . filter (
0 commit comments