Skip to content

Commit cfb866c

Browse files
committed
Implement roving tabindex functionality in toolbar and picker components
1 parent d3fb27d commit cfb866c

File tree

6 files changed

+64
-44
lines changed

6 files changed

+64
-44
lines changed

packages/quill/src/modules/toolbar.ts

+32-10
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ class Toolbar extends Module<ToolbarProps> {
5050

5151
// Check if the parent element has the custom "roving-tabindex" class in order to enable or disable roving tabindex
5252
this.hasRovingTabindex = this.container.closest('.roving-tabindex') !== null;
53+
if (this.hasRovingTabindex) {
54+
this.container.addEventListener('keydown', (e) => {
55+
this.handleKeyboardEvent(e);
56+
});
57+
}
5358

5459
this.controls = [];
5560
this.handlers = {};
@@ -140,28 +145,45 @@ class Toolbar extends Module<ToolbarProps> {
140145
this.update(range);
141146
});
142147

143-
if (this.hasRovingTabindex && input.tagName === 'BUTTON') {
144-
input.addEventListener('keydown', (e) => {
145-
this.handleKeyboardEvent(e);
146-
});
147-
}
148-
149148
this.controls.push([format, input]);
149+
if (this.hasRovingTabindex) {
150+
this.setTabIndexes();
151+
}
150152
}
151153

154+
setTabIndexes() {
155+
this.controls.forEach((control, index) => {
156+
const [, input] = control;
157+
if (input.tagName === 'BUTTON') {
158+
input.tabIndex = index === 0 ? 0 : -1;
159+
}
160+
});
161+
};
162+
152163
handleKeyboardEvent(e: KeyboardEvent) {
153164
var target = e.currentTarget;
154165
if (!target) return;
155166

156167
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
157-
this.updateTabIndexes(target, e.key);
158-
}
168+
this.updateTabIndexes(target, e.key);
169+
}
159170
}
160171

161172
updateTabIndexes(target: EventTarget, key: string) {
162-
const currentIndex = this.controls.findIndex(control => control[1] === target);
173+
const elements = Array.from(this.container?.querySelectorAll('button, .ql-picker-label') || []) as HTMLElement[];
174+
175+
const currentIndex = elements.findIndex((el) => el.tabIndex === 0);
176+
if (currentIndex === -1) return;
177+
163178
const currentItem = this.controls[currentIndex][1];
164-
currentItem.tabIndex = -1;
179+
if (currentItem.tagName === 'SELECT') {
180+
const qlPickerLabel = currentItem.previousElementSibling?.querySelectorAll('.ql-picker-label')[0];
181+
if (qlPickerLabel && qlPickerLabel.tagName === 'SPAN') {
182+
(qlPickerLabel as HTMLElement).tabIndex = -1;
183+
}
184+
} else {
185+
currentItem.tabIndex = -1;
186+
}
165187

166188
let nextIndex: number | null = null;
167189
if (key === 'ArrowLeft') {

packages/quill/src/ui/picker.ts

-34
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,6 @@ class Picker {
9696
// Set tabIndex to -1 by default to prevent focus
9797
// @ts-expect-error
9898
label.tabIndex = '-1';
99-
label.addEventListener('keydown', (event) => {
100-
this.handleKeyboardEvent(event);
101-
});
10299

103100

104101
label.setAttribute('role', 'button');
@@ -124,37 +121,6 @@ class Picker {
124121
}
125122
}
126123

127-
handleKeyboardEvent(e: KeyboardEvent) {
128-
if (!this.hasRovingTabindex) return;
129-
var target = e.currentTarget;
130-
if (!target) return;
131-
132-
if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
133-
this.updateTabIndexes(target, e.key);
134-
}
135-
}
136-
137-
updateTabIndexes(target: EventTarget, key: string) {
138-
this.label.setAttribute('tabindex', '-1');
139-
140-
const toolbar = this.container.closest('.ql-toolbar');
141-
if (!toolbar) return;
142-
const items = Array.from(toolbar.querySelectorAll('.ql-picker .ql-picker-label, .ql-toolbar button'));
143-
const currentIndex = items.indexOf(target as HTMLElement);
144-
let newIndex;
145-
146-
if (key === 'ArrowLeft') {
147-
newIndex = (currentIndex - 1 + items.length) % items.length;
148-
} else if (key === 'ArrowRight') {
149-
newIndex = (currentIndex + 1) % items.length;
150-
}
151-
152-
if (!newIndex) return;
153-
154-
items[newIndex].setAttribute('tabindex', '0');
155-
(items[newIndex] as HTMLElement).focus();
156-
}
157-
158124
buildOptions() {
159125
const options = document.createElement('span');
160126
options.classList.add('ql-picker-options');

packages/website/src/data/playground.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ const playground = [
33
title: 'Basic setup with snow theme',
44
url: '/playground/snow',
55
},
6+
{
7+
title: 'Basic setup with roving tabindex',
8+
url: '/playground/roving-tabindex',
9+
},
610
{
711
title: 'Using Quill inside a form',
812
url: '/playground/form',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<div class="roving-tabindex">
2+
<div id="editor"></div>
3+
</div>
4+
5+
<script src="/index.js"></script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const quill = new Quill('#editor', {
2+
modules: {
3+
toolbar: [
4+
['bold', 'italic', 'underline'],
5+
[{ header: [1, 2, false] }],
6+
['image', 'code-block'],
7+
],
8+
},
9+
placeholder: 'Compose an epic...',
10+
theme: 'snow', // or 'bubble'
11+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"template": "static",
3+
"externalResources": [
4+
"{{site.highlightjs}}/highlight.min.js",
5+
"{{site.highlightjs}}/styles/atom-one-dark.min.css",
6+
"{{site.katex}}/katex.min.js",
7+
"{{site.katex}}/katex.min.css",
8+
"{{site.cdn}}/quill.snow.css",
9+
"{{site.cdn}}/quill.bubble.css",
10+
"{{site.cdn}}/quill.js"
11+
]
12+
}

0 commit comments

Comments
 (0)