11<template >
22 <div class =" code-toggle" >
3- <ul class =" code-language-switcher theme-default-content-override" v-if =" !usePageToggle" >
4- <li v-for =" (language, index) in languages" :key =" index" >
5- <button
6- :class =" { active: isSelectedLanguage(language) }"
7- :aria-selected =" isSelectedLanguage(language)"
8- role =" tab"
9- :aria-controls =" '#' + getLanguageTabId(language)"
10- @click =" setLanguage(language)"
11- >{{ getLanguageLabel(language) }}</button >
12- </li >
13- </ul >
3+ <div class =" code-lang-switcher theme-default-content-override" role =" tablist" v-if =" !usePageToggle" >
4+ <button
5+ v-for =" (language, index) in languages"
6+ :key =" language"
7+ :class =" { active: isSelectedTab(language) }"
8+ :aria-selected =" isSelectedTab(language)"
9+ :id =" getTabId(language)"
10+ :data-language =" language"
11+ role =" tab"
12+ :aria-controls =" getTabPanelId(language)"
13+ @click =" setLanguage(language)"
14+ :tabindex =" isSelectedTab(language) ? null : '-1'"
15+ v-on:keyup =" handleKeyup"
16+ >{{ getLanguageLabel(language) }}</button >
17+ </div >
1418 <div
1519 v-for =" (language, index) in languages"
20+ tabindex =" 0"
1621 :key =" index"
17- :id =" getLanguageTabId(language)"
18- :hidden =" !isSelectedLanguage(language)"
22+ :id =" getTabPanelId(language)"
23+ :hidden =" !isSelectedTab(language)"
24+ :aria-labelledby =" getTabId(language)"
1925 role =" tabpanel" >
2026 <slot :name =" language" />
2127 </div >
4248 }
4349}
4450
45- ul .code-language -switcher {
51+ .code-lang -switcher {
4652 @apply flex flex-row rounded-t box-border m-0 p-2;
4753 background : var (--border-color );
4854 z-index : 2 ;
55+ gap : .3 rem ;
4956
50- li {
51- @apply p-0 mr-1 list-none;
52-
53- button {
54- @apply block py-3 px-4 font-medium text-xs tracking-wider uppercase leading-none cursor-pointer rounded;
57+ button {
58+ @apply block py-3 px-4 font-medium text-xs tracking-wider uppercase leading-none cursor-pointer rounded;
59+ border : 1 px solid transparent;
60+ &:hover {
61+ background-color : var (--sidebar-bg-color );
62+ }
5563
56- &:hover {
57- background-color : var (--sidebar-bg-color );
58- }
64+ &:focus-visible {
65+ outline : var (--custom-focus-outline );
66+ outline-offset : 2 px ;
67+ }
5968
60- & .active {
61- color : var (--text -color );
62- background- color : var (--bg -color );
63- }
69+ & .active {
70+ border- color : var (--active-tab-border -color );
71+ color : var (--text -color );
72+ background-color : var ( --bg-color );
6473 }
6574 }
6675}
6776
6877.theme-default-content {
69- ul .code-language -switcher {
78+ .code-lang -switcher {
7079 @apply mb-0;
7180 }
7281}
7382 </style >
7483
7584<script >
76- import { isStarted } from ' nprogress ' ;
85+ import { v4 as uuidv4 } from ' uuid ' ;
7786
7887export default {
7988 props: [" languages" , " labels" ],
8089
8190 data () {
8291 return {
8392 selectedLanguage: this .languages [0 ],
93+ uniqueId: null ,
94+ focusedTabIndex: null ,
8495 };
8596 },
8697
98+ mounted () {
99+ this .uniqueId = uuidv4 ();
100+ },
101+
102+ watch: {
103+ selectedLanguage (newLanguage ) {
104+ this .$el .querySelector (` button[data-language="${ newLanguage} "]` ).focus ();
105+ },
106+ },
107+
87108 computed: {
88109 usePageToggle () {
89110 if (this .$page === undefined ) {
@@ -98,6 +119,12 @@ export default {
98119 setLanguage (language ) {
99120 this .selectedLanguage = language;
100121 },
122+ getLanguageFromIndex (index ) {
123+ return this .languages [index];
124+ },
125+ getIndexFromLanguage (language ) {
126+ return this .languages .indexOf (language);
127+ },
101128 getLanguageLabel (language ) {
102129 if (this .labels && this .labels [language]) {
103130 return this .labels [language];
@@ -111,20 +138,53 @@ export default {
111138 (themeLanguages && themeLanguages[language]) ||
112139 language
113140 );
114-
115- return language;
116141 },
117- isSelectedLanguage (language ) {
142+ isSelectedTab (language ) {
118143 return (
119144 language ==
120145 (this .usePageToggle
121146 ? this .$store .state .codeLanguage
122147 : this .selectedLanguage )
123148 );
124149 },
125- getLanguageTabId (language ) {
126- return ` tab-${ this ._uid } -${ language} ` ;
127- }
150+ getTabId (language ) {
151+ return ` tab-${ this .uniqueId } -${ language} ` ;
152+ },
153+ getTabPanelId (language ) {
154+ return ` tabpanel-${ this .uniqueId } -${ language} ` ;
155+ },
156+ getNextIndex (index ) {
157+ return index + 1 <= this .languages .length - 1 ? index + 1 : 0 ;
158+ },
159+ getPrevIndex (index ) {
160+ return index - 1 >= 0 ? index - 1 : this .languages .length - 1 ;
161+ },
162+ handleKeyup (event ) {
163+ const {keyCode , target } = event ;
164+ let indexToFocus;
165+ const currentIndex = this .getIndexFromLanguage (target .getAttribute (' data-language' ));
166+
167+ switch (keyCode) {
168+ case 37 :
169+ indexToFocus = this .getPrevIndex (currentIndex);
170+ break ;
171+ case 39 :
172+ indexToFocus = this .getNextIndex (currentIndex);
173+ break ;
174+ case 36 :
175+ indexToFocus = 0 ;
176+ break ;
177+ case 35 :
178+ indexToFocus = this .languages .length - 1 ;
179+ break ;
180+ default :
181+ return ;
182+ }
183+
184+ if (indexToFocus !== undefined ) {
185+ this .setLanguage (this .getLanguageFromIndex (indexToFocus));
186+ }
187+ },
128188 }
129189};
130190 </script >
0 commit comments