Skip to content

Commit 6c77015

Browse files
chittolinagVladaHarbour
authored andcommitted
feat: added roving tabindex
1 parent e37d79b commit 6c77015

2 files changed

Lines changed: 93 additions & 4 deletions

File tree

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

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,99 @@ const getDropdownAttributes = (option, item) => {
105105
const 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+
}
136+
};
137+
138+
const moveToPreviousButtonGroup = (e) => {
139+
const previousButtonGroup = e.target.closest('.button-group').previousElementSibling;
140+
if (previousButtonGroup) {
141+
previousButtonGroup.setAttribute('tabindex', '0');
142+
previousButtonGroup.focus();
143+
}
144+
};
145+
146+
// Implement keyboard navigation using Roving Tabindex
147+
// https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex
148+
// Set tabindex to 0 for the current focused button
149+
// Set tabindex to -1 for all other buttons
150+
const handleKeyDown = (e) => {
151+
e.preventDefault();
152+
e.stopPropagation();
153+
154+
switch (e.key) {
155+
case 'ArrowRight':
156+
moveToNextButton(e);
157+
break;
158+
case 'ArrowLeft':
159+
moveToPreviousButton(e);
160+
break;
161+
case 'Tab':
162+
if (e.shiftKey) {
163+
moveToPreviousButtonGroup(e);
164+
} else {
165+
moveToNextButtonGroup(e);
166+
}
167+
break;
168+
default:
169+
break;
170+
}
171+
};
172+
const handleFocus = (e) => {
173+
// Set the focus to the first button inside the button group that is not disabled
174+
const firstButton = e.target.closest('.button-group').querySelector('.toolbar-item-ctn:not(.disabled)');
175+
if (firstButton) {
176+
// Force focus on the first button
177+
firstButton.focus();
178+
}
179+
};
108180
</script>
109181
110182
<template>
111183
<div
112184
:style="getPositionStyle"
113185
class="button-group"
114186
role="group"
187+
@keydown="handleKeyDown"
188+
@focus="handleFocus"
115189
>
116-
<div v-for="item in toolbarItems" :key="item.id.value" :class="{
190+
<div
191+
v-for="(item, index) in toolbarItems"
192+
:key="item.id.value"
193+
:class="{
117194
narrow: item.isNarrow.value,
118195
wide: item.isWide.value,
119-
}" class="toolbar-item-ctn">
196+
disabled: item.disabled.value,
197+
}"
198+
class="toolbar-item-ctn"
199+
:tabindex="index === 0 ? 0 : -1"
200+
>
120201
<!-- toolbar separator -->
121202
<ToolbarSeparator v-if="isSeparator(item)" style="width: 20px" />
122203
@@ -140,8 +221,12 @@ const handleClickOutside = (e) => {
140221
>
141222
<n-tooltip trigger="hover" :disabled="!item.tooltip?.value">
142223
<template #trigger>
143-
<ToolbarButton :toolbar-item="item" @textSubmit="handleToolbarButtonTextSubmit(item, $event)"
144-
@buttonClick="handleToolbarButtonClick(item)" />
224+
<ToolbarButton
225+
:toolbar-item="item"
226+
:disabled="item.disabled.value"
227+
@textSubmit="handleToolbarButtonTextSubmit(item, $event)"
228+
@buttonClick="handleToolbarButtonClick(item)"
229+
/>
145230
</template>
146231
<div>
147232
{{ item.tooltip }}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ 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>
@@ -59,19 +60,22 @@ const handleCommand = ({ item, argument, option }) => {
5960
aria-label="Toolbar"
6061
>
6162
<ButtonGroup
63+
tabindex="0"
6264
v-if="showLeftSide"
6365
:toolbar-items="getFilteredItems('left')"
6466
position="left"
6567
@command="handleCommand"
6668
class="superdoc-toolbar-group-side"
6769
/>
6870
<ButtonGroup
71+
tabindex="0"
6972
:toolbar-items="getFilteredItems('center')"
7073
:overflow-items="proxy.$toolbar.overflowItems"
7174
position="center"
7275
@command="handleCommand"
7376
/>
7477
<ButtonGroup
78+
tabindex="0"
7579
v-if="showRightSide"
7680
:toolbar-items="getFilteredItems('right')"
7781
position="right"

0 commit comments

Comments
 (0)