@@ -8,6 +8,43 @@ export default defineComponent({
8
8
// index of current active item
9
9
const activeIndex = ref ( - 1 )
10
10
11
+ // refs of the tab buttons
12
+ const tabRefs = ref < HTMLButtonElement [ ] > ( [ ] )
13
+
14
+ // activate next tab
15
+ const activateNext = ( i = activeIndex . value ) : void => {
16
+ if ( i < tabRefs . value . length - 1 ) {
17
+ activeIndex . value = i + 1
18
+ } else {
19
+ activeIndex . value = 0
20
+ }
21
+ tabRefs . value [ activeIndex . value ] . focus ( )
22
+ }
23
+
24
+ // activate previous tab
25
+ const activatePrev = ( i = activeIndex . value ) : void => {
26
+ if ( i > 0 ) {
27
+ activeIndex . value = i - 1
28
+ } else {
29
+ activeIndex . value = tabRefs . value . length - 1
30
+ }
31
+ tabRefs . value [ activeIndex . value ] . focus ( )
32
+ }
33
+
34
+ // handle keyboard event
35
+ const keyboardHandler = ( event : KeyboardEvent , i : number ) : void => {
36
+ if ( event . key === ' ' || event . key === 'Enter' ) {
37
+ event . preventDefault ( )
38
+ activeIndex . value = i
39
+ } else if ( event . key === 'ArrowRight' ) {
40
+ event . preventDefault ( )
41
+ activateNext ( i )
42
+ } else if ( event . key === 'ArrowLeft' ) {
43
+ event . preventDefault ( )
44
+ activatePrev ( i )
45
+ }
46
+ }
47
+
11
48
return ( ) => {
12
49
// NOTICE: here we put the `slots.default()` inside the render function to make
13
50
// the slots reactive, otherwise the slot content won't be changed once the
@@ -23,13 +60,16 @@ export default defineComponent({
23
60
return vnode as VNode & { props : Exclude < VNode [ 'props' ] , null > }
24
61
} )
25
62
63
+ // clear tabRefs for HMR
64
+ tabRefs . value = [ ]
65
+
26
66
// do not render anything if there is no code-group-item
27
67
if ( items . length === 0 ) {
28
68
return null
29
69
}
30
70
31
- if ( activeIndex . value === - 1 ) {
32
- // initial state
71
+ if ( activeIndex . value < 0 || activeIndex . value > items . length - 1 ) {
72
+ // if `activeIndex` is invalid
33
73
34
74
// find the index of the code-group-item with `active` props
35
75
activeIndex . value = items . findIndex (
@@ -41,8 +81,6 @@ export default defineComponent({
41
81
activeIndex . value = 0
42
82
}
43
83
} else {
44
- // re-render triggered by modifying `activeIndex` ref
45
-
46
84
// set the active item
47
85
items . forEach ( ( vnode , i ) => {
48
86
vnode . props . active = i === activeIndex . value
@@ -56,24 +94,33 @@ export default defineComponent({
56
94
h (
57
95
'ul' ,
58
96
{ class : 'code-group__ul' } ,
59
- items . map ( ( vnode , i ) =>
60
- h (
97
+ items . map ( ( vnode , i ) => {
98
+ const isActive = i === activeIndex . value
99
+
100
+ return h (
61
101
'li' ,
62
102
{ class : 'code-group__li' } ,
63
103
h (
64
104
'button' ,
65
105
{
66
- class : `code-group__nav-tab${
67
- i === activeIndex . value
68
- ? ' code-group__nav-tab-active'
69
- : ''
70
- } `,
106
+ ref : ( element ) => {
107
+ if ( element ) {
108
+ tabRefs . value [ i ] = element as HTMLButtonElement
109
+ }
110
+ } ,
111
+ class : {
112
+ 'code-group__nav-tab' : true ,
113
+ 'code-group__nav-tab-active' : isActive ,
114
+ } ,
115
+ ariaPressed : isActive ,
116
+ ariaExpanded : isActive ,
71
117
onClick : ( ) => ( activeIndex . value = i ) ,
118
+ onKeydown : ( e ) => keyboardHandler ( e , i ) ,
72
119
} ,
73
120
vnode . props . title
74
121
)
75
122
)
76
- )
123
+ } )
77
124
)
78
125
) ,
79
126
items ,
0 commit comments