@@ -55,11 +55,11 @@ const isButton = (item) => item.type === 'button';
5555const isDropdown = (item ) => item .type === ' dropdown' ;
5656const isSeparator = (item ) => item .type === ' separator' ;
5757const isOverflow = (item ) => item .type === ' overflow' ;
58- const handleToolbarButtonClick = (item , argument = null ) => {
58+ const handleToolbarButtonClick = (item , argument = null , switchFocusToEditor = true ) => {
5959 currentItem .value = item;
6060 currentItem .value .expand = true ;
6161 if (item .disabled .value ) return ;
62- emit (' command' , { item, argument });
62+ emit (' command' , { item, argument, switchFocusToEditor });
6363};
6464
6565const handleToolbarButtonTextSubmit = (item , argument ) => {
@@ -78,7 +78,7 @@ const selectedOption = ref(null);
7878const handleSelect = (item , option ) => {
7979 closeDropdowns ();
8080 const value = item .dropdownValueKey .value ? option[item .dropdownValueKey .value ] : option .label ;
81- emit (' command' , { item, argument: value, option });
81+ emit (' command' , { item, argument: value, option, switchFocusToEditor : true });
8282 selectedOption .value = option .key ;
8383};
8484
@@ -105,18 +105,110 @@ const getDropdownAttributes = (option, item) => {
105105const handleClickOutside = (e ) => {
106106 closeDropdowns ();
107107};
108+
109+
110+ const moveToNextButton = (e ) => {
111+ const currentButton = e .target ;
112+ const nextButton = e .target .closest (' .toolbar-item-ctn' ).nextElementSibling ;
113+ if (nextButton) {
114+ currentButton .setAttribute (' tabindex' , ' -1' );
115+ nextButton .setAttribute (' tabindex' , ' 0' );
116+ nextButton .focus ();
117+ }
118+ };
119+
120+ const moveToPreviousButton = (e ) => {
121+ const currentButton = e .target ;
122+ const previousButton = e .target .closest (' .toolbar-item-ctn' ).previousElementSibling ;
123+ if (previousButton) {
124+ currentButton .setAttribute (' tabindex' , ' -1' );
125+ previousButton .setAttribute (' tabindex' , ' 0' );
126+ previousButton .focus ();
127+ }
128+ };
129+
130+ const moveToNextButtonGroup = (e ) => {
131+ const nextButtonGroup = e .target .closest (' .button-group' ).nextElementSibling ;
132+ if (nextButtonGroup) {
133+ nextButtonGroup .setAttribute (' tabindex' , ' 0' );
134+ nextButtonGroup .focus ();
135+ } else {
136+ // Move to the editor
137+ const editor = document .querySelector (' .ProseMirror' );
138+ if (editor) {
139+ editor .focus ();
140+ }
141+ }
142+ };
143+
144+ const moveToPreviousButtonGroup = (e ) => {
145+ const previousButtonGroup = e .target .closest (' .button-group' ).previousElementSibling ;
146+ if (previousButtonGroup) {
147+ previousButtonGroup .setAttribute (' tabindex' , ' 0' );
148+ previousButtonGroup .focus ();
149+ }
150+ };
151+
152+ // Implement keyboard navigation using Roving Tabindex
153+ // https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex
154+ // Set tabindex to 0 for the current focused button
155+ // Set tabindex to -1 for all other buttons
156+ const handleKeyDown = (e , item ) => {
157+ e .preventDefault ();
158+ switch (e .key ) {
159+ case ' Enter' :
160+ handleToolbarButtonClick (item, null , false );
161+ break ;
162+ case ' Escape' :
163+ closeDropdowns ();
164+ break ;
165+ case ' ArrowRight' :
166+ closeDropdowns ();
167+ moveToNextButton (e);
168+ break ;
169+ case ' ArrowLeft' :
170+ closeDropdowns ();
171+ moveToPreviousButton (e);
172+ break ;
173+ case ' Tab' :
174+ if (e .shiftKey ) {
175+ moveToPreviousButtonGroup (e);
176+ } else {
177+ moveToNextButtonGroup (e);
178+ }
179+ break ;
180+ default :
181+ break ;
182+ }
183+ };
184+ const handleFocus = (e ) => {
185+ // Set the focus to the first button inside the button group that is not disabled
186+ const firstButton = e .target .closest (' .button-group' ).querySelector (' .toolbar-item-ctn:not(.disabled)' );
187+ if (firstButton) {
188+ firstButton .focus ();
189+ }
190+ };
108191< / script>
109192
110193< template>
111194 < div
112195 : style= " getPositionStyle"
113- class = " button-group"
196+ class = " button-group"
114197 role= " group"
198+ @focus= " handleFocus"
115199 >
116- < div v- for = " item in toolbarItems" : key= " item.id.value" : class = " {
200+ < div
201+ v- for = " (item, index) in toolbarItems"
202+ : key= " item.id.value"
203+ : class = " {
117204 narrow: item.isNarrow.value,
118205 wide: item.isWide.value,
119- }" class = " toolbar-item-ctn" >
206+ disabled: item.disabled.value,
207+ }"
208+ @keydown= " (e) => handleKeyDown(e, item)"
209+ class = " toolbar-item-ctn"
210+ : tabindex= " index === 0 ? 0 : -1"
211+ >
120212 <!-- toolbar separator -->
121213 < ToolbarSeparator v- if = " isSeparator(item)" style= " width: 20px" / >
122214
@@ -140,8 +232,12 @@ const handleClickOutside = (e) => {
140232 >
141233 < n- tooltip trigger= " hover" : disabled= " !item.tooltip?.value" >
142234 < template #trigger>
143- < ToolbarButton : toolbar- item= " item" @textSubmit= " handleToolbarButtonTextSubmit(item, $event)"
144- @buttonClick= " handleToolbarButtonClick(item)" / >
235+ < ToolbarButton
236+ : toolbar- item= " item"
237+ : disabled= " item.disabled.value"
238+ @textSubmit= " handleToolbarButtonTextSubmit(item, $event)"
239+ @buttonClick= " handleToolbarButtonClick(item)"
240+ / >
145241 < / template>
146242 < div>
147243 {{ item .tooltip }}
0 commit comments