Skip to content

Commit 6ad2490

Browse files
committed
feat: added roving tabindex
1 parent 98a62fa commit 6ad2490

2 files changed

Lines changed: 81 additions & 2 deletions

File tree

packages/super-editor/src/components/toolbar/ButtonGroup.vue

Lines changed: 77 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,92 @@ const dropdownOptions = (item) => {
9292
const handleClickOutside = (e) => {
9393
closeDropdowns();
9494
};
95+
96+
97+
const moveToNextButton = (e) => {
98+
const currentButton = e.target;
99+
const nextButton = e.target.closest('.toolbar-item-ctn').nextElementSibling;
100+
if (nextButton) {
101+
currentButton.setAttribute('tabindex', '-1');
102+
nextButton.setAttribute('tabindex', '0');
103+
nextButton.focus();
104+
}
105+
};
106+
107+
const moveToPreviousButton = (e) => {
108+
const currentButton = e.target;
109+
const previousButton = e.target.closest('.toolbar-item-ctn').previousElementSibling;
110+
if (previousButton) {
111+
currentButton.setAttribute('tabindex', '-1');
112+
previousButton.setAttribute('tabindex', '0');
113+
previousButton.focus();
114+
}
115+
};
116+
117+
const moveToNextButtonGroup = (e) => {
118+
const nextButtonGroup = e.target.closest('.button-group').nextElementSibling;
119+
if (nextButtonGroup) {
120+
nextButtonGroup.setAttribute('tabindex', '0');
121+
nextButtonGroup.focus();
122+
}
123+
};
124+
125+
const moveToPreviousButtonGroup = (e) => {
126+
const previousButtonGroup = e.target.closest('.button-group').previousElementSibling;
127+
if (previousButtonGroup) {
128+
previousButtonGroup.setAttribute('tabindex', '0');
129+
previousButtonGroup.focus();
130+
}
131+
};
132+
133+
// Implement keyboard navigation using Roving Tabindex
134+
// https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex
135+
// Set tabindex to 0 for the current focused button
136+
// Set tabindex to -1 for all other buttons
137+
const handleKeyDown = (e) => {
138+
e.preventDefault();
139+
e.stopPropagation();
140+
141+
switch (e.key) {
142+
case 'ArrowRight':
143+
moveToNextButton(e);
144+
break;
145+
case 'ArrowLeft':
146+
moveToPreviousButton(e);
147+
break;
148+
case 'Tab':
149+
if (e.shiftKey) {
150+
moveToPreviousButtonGroup(e);
151+
} else {
152+
moveToNextButtonGroup(e);
153+
}
154+
break;
155+
default:
156+
break;
157+
}
158+
};
159+
const handleFocus = (e) => {
160+
// Set the focus to the first button inside the button group that is not disabled
161+
const firstButton = e.target.closest('.button-group').querySelector('.toolbar-item-ctn:not(.disabled)');
162+
if (firstButton) {
163+
// Force focus on the first button
164+
firstButton.focus();
165+
}
166+
};
95167
</script>
96168
97169
<template>
98-
<div :style="getPositionStyle" class="button-group">
170+
<div :style="getPositionStyle" class="button-group" @keydown="handleKeyDown" @focus="handleFocus">
99171
<div
100-
v-for="item in toolbarItems"
172+
v-for="(item, index) in toolbarItems"
101173
:key="item.id.value"
102174
:class="{
103175
narrow: item.isNarrow.value,
104176
wide: item.isWide.value,
177+
disabled: item.disabled.value,
105178
}"
106179
class="toolbar-item-ctn"
180+
:tabindex="index === 0 ? 0 : -1"
107181
>
108182
<!-- toolbar separator -->
109183
<ToolbarSeparator v-if="isSeparator(item)" style="width: 20px" />
@@ -125,6 +199,7 @@ const handleClickOutside = (e) => {
125199
<template #trigger>
126200
<ToolbarButton
127201
:toolbar-item="item"
202+
:disabled="item.disabled.value"
128203
@textSubmit="handleToolbarButtonTextSubmit(item, $event)"
129204
@buttonClick="handleToolbarButtonClick(item)"
130205
/>

packages/super-editor/src/components/toolbar/Toolbar.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,24 +49,28 @@ const onResizeThrottled = throttle(onWindowResized, 300);
4949
const handleCommand = ({ item, argument, option }) => {
5050
proxy.$toolbar.emitCommand({ item, argument, option });
5151
};
52+
5253
</script>
5354
5455
<template>
5556
<div class="superdoc-toolbar" :key="toolbarKey">
5657
<ButtonGroup
58+
tabindex="0"
5759
v-if="showLeftSide"
5860
:toolbar-items="getFilteredItems('left')"
5961
position="left"
6062
@command="handleCommand"
6163
class="superdoc-toolbar-group-side"
6264
/>
6365
<ButtonGroup
66+
tabindex="0"
6467
:toolbar-items="getFilteredItems('center')"
6568
:overflow-items="proxy.$toolbar.overflowItems"
6669
position="center"
6770
@command="handleCommand"
6871
/>
6972
<ButtonGroup
73+
tabindex="0"
7074
v-if="showRightSide"
7175
:toolbar-items="getFilteredItems('right')"
7276
position="right"

0 commit comments

Comments
 (0)