Skip to content

Commit

Permalink
Implement roving tabindex functionality in toolbar and picker components
Browse files Browse the repository at this point in the history
  • Loading branch information
colfin-96 committed Jan 12, 2025
1 parent d3fb27d commit cfb866c
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 44 deletions.
42 changes: 32 additions & 10 deletions packages/quill/src/modules/toolbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ class Toolbar extends Module<ToolbarProps> {

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

this.controls = [];
this.handlers = {};
Expand Down Expand Up @@ -140,28 +145,45 @@ class Toolbar extends Module<ToolbarProps> {
this.update(range);
});

if (this.hasRovingTabindex && input.tagName === 'BUTTON') {
input.addEventListener('keydown', (e) => {
this.handleKeyboardEvent(e);
});
}

this.controls.push([format, input]);
if (this.hasRovingTabindex) {
this.setTabIndexes();
}
}

setTabIndexes() {
this.controls.forEach((control, index) => {
const [, input] = control;
if (input.tagName === 'BUTTON') {
input.tabIndex = index === 0 ? 0 : -1;
}
});
};

handleKeyboardEvent(e: KeyboardEvent) {
var target = e.currentTarget;
if (!target) return;

if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
this.updateTabIndexes(target, e.key);
}
this.updateTabIndexes(target, e.key);
}
}

updateTabIndexes(target: EventTarget, key: string) {
const currentIndex = this.controls.findIndex(control => control[1] === target);
const elements = Array.from(this.container?.querySelectorAll('button, .ql-picker-label') || []) as HTMLElement[];

const currentIndex = elements.findIndex((el) => el.tabIndex === 0);
if (currentIndex === -1) return;

const currentItem = this.controls[currentIndex][1];
currentItem.tabIndex = -1;
if (currentItem.tagName === 'SELECT') {
const qlPickerLabel = currentItem.previousElementSibling?.querySelectorAll('.ql-picker-label')[0];
if (qlPickerLabel && qlPickerLabel.tagName === 'SPAN') {
(qlPickerLabel as HTMLElement).tabIndex = -1;
}
} else {
currentItem.tabIndex = -1;
}

let nextIndex: number | null = null;
if (key === 'ArrowLeft') {
Expand Down
34 changes: 0 additions & 34 deletions packages/quill/src/ui/picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,6 @@ class Picker {
// Set tabIndex to -1 by default to prevent focus
// @ts-expect-error
label.tabIndex = '-1';
label.addEventListener('keydown', (event) => {
this.handleKeyboardEvent(event);
});


label.setAttribute('role', 'button');
Expand All @@ -124,37 +121,6 @@ class Picker {
}
}

handleKeyboardEvent(e: KeyboardEvent) {
if (!this.hasRovingTabindex) return;
var target = e.currentTarget;
if (!target) return;

if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {
this.updateTabIndexes(target, e.key);
}
}

updateTabIndexes(target: EventTarget, key: string) {
this.label.setAttribute('tabindex', '-1');

const toolbar = this.container.closest('.ql-toolbar');
if (!toolbar) return;
const items = Array.from(toolbar.querySelectorAll('.ql-picker .ql-picker-label, .ql-toolbar button'));
const currentIndex = items.indexOf(target as HTMLElement);
let newIndex;

if (key === 'ArrowLeft') {
newIndex = (currentIndex - 1 + items.length) % items.length;
} else if (key === 'ArrowRight') {
newIndex = (currentIndex + 1) % items.length;
}

if (!newIndex) return;

items[newIndex].setAttribute('tabindex', '0');
(items[newIndex] as HTMLElement).focus();
}

buildOptions() {
const options = document.createElement('span');
options.classList.add('ql-picker-options');
Expand Down
4 changes: 4 additions & 0 deletions packages/website/src/data/playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ const playground = [
title: 'Basic setup with snow theme',
url: '/playground/snow',
},
{
title: 'Basic setup with roving tabindex',
url: '/playground/roving-tabindex',
},
{
title: 'Using Quill inside a form',
url: '/playground/form',
Expand Down
5 changes: 5 additions & 0 deletions packages/website/src/playground/roving-tabindex/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div class="roving-tabindex">
<div id="editor"></div>
</div>

<script src="/index.js"></script>
11 changes: 11 additions & 0 deletions packages/website/src/playground/roving-tabindex/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const quill = new Quill('#editor', {
modules: {
toolbar: [
['bold', 'italic', 'underline'],
[{ header: [1, 2, false] }],
['image', 'code-block'],
],
},
placeholder: 'Compose an epic...',
theme: 'snow', // or 'bubble'
});
12 changes: 12 additions & 0 deletions packages/website/src/playground/roving-tabindex/playground.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"template": "static",
"externalResources": [
"{{site.highlightjs}}/highlight.min.js",
"{{site.highlightjs}}/styles/atom-one-dark.min.css",
"{{site.katex}}/katex.min.js",
"{{site.katex}}/katex.min.css",
"{{site.cdn}}/quill.snow.css",
"{{site.cdn}}/quill.bubble.css",
"{{site.cdn}}/quill.js"
]
}

0 comments on commit cfb866c

Please sign in to comment.