From a8368415e602fd128b49172f8b4a9854852f5365 Mon Sep 17 00:00:00 2001 From: Colin Finger Date: Mon, 6 Jan 2025 15:41:13 +0100 Subject: [PATCH 1/7] ~ --- package-lock.json | 8 ++++---- packages/quill/.eslintrc.json | 3 ++- packages/quill/package.json | 2 +- packages/quill/src/modules/toolbar.ts | 1 + packages/quill/src/ui/picker.ts | 12 +++++++++++- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 23085e2961..49a90bc690 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "quill-monorepo", - "version": "2.0.3", + "version": "2.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "quill-monorepo", - "version": "2.0.3", + "version": "2.0.2", "license": "BSD-3-Clause", "workspaces": [ "packages/*" @@ -19236,7 +19236,7 @@ } }, "packages/quill": { - "version": "2.0.3", + "version": "2.0.3-beta.1", "license": "BSD-3-Clause", "dependencies": { "eventemitter3": "^5.0.1", @@ -19361,7 +19361,7 @@ } }, "packages/website": { - "version": "2.0.3", + "version": "2.0.2", "license": "BSD-3-Clause", "dependencies": { "@codesandbox/sandpack-react": "^2.11.3", diff --git a/packages/quill/.eslintrc.json b/packages/quill/.eslintrc.json index edf5dc5f30..8d0f04b2ce 100644 --- a/packages/quill/.eslintrc.json +++ b/packages/quill/.eslintrc.json @@ -36,7 +36,8 @@ "@typescript-eslint/ban-types": "off", "@typescript-eslint/no-explicit-any": "off", "import/no-named-as-default-member": "off", - "prefer-arrow-callback": "error" + "prefer-arrow-callback": "error", + "endOfLine": "auto" } } ] diff --git a/packages/quill/package.json b/packages/quill/package.json index 4dd6a2e662..858ee53a8b 100644 --- a/packages/quill/package.json +++ b/packages/quill/package.json @@ -1,6 +1,6 @@ { "name": "quill", - "version": "2.0.3", + "version": "2.0.3-beta.2", "description": "Your powerful, rich text editor", "author": "Jason Chen ", "homepage": "https://quilljs.com", diff --git a/packages/quill/src/modules/toolbar.ts b/packages/quill/src/modules/toolbar.ts index a178d25936..9c515e2495 100644 --- a/packages/quill/src/modules/toolbar.ts +++ b/packages/quill/src/modules/toolbar.ts @@ -133,6 +133,7 @@ class Toolbar extends Module { } this.update(range); }); + input.tabIndex = 123; this.controls.push([format, input]); } diff --git a/packages/quill/src/ui/picker.ts b/packages/quill/src/ui/picker.ts index d6b387c1a4..011043fb57 100644 --- a/packages/quill/src/ui/picker.ts +++ b/packages/quill/src/ui/picker.ts @@ -34,6 +34,9 @@ class Picker { this.escape(); event.preventDefault(); break; + case 'ArrowLeft': + case 'ArrowRight': + this.toggleTabIndex(); default: } }); @@ -48,6 +51,10 @@ class Picker { toggleAriaAttribute(this.options, 'aria-hidden'); } + toggleTabIndex() { + this.label.tabIndex = this.label.tabIndex === 0 ? -1 : 0; + } + buildItem(option: HTMLOptionElement) { const item = document.createElement('span'); // @ts-expect-error @@ -85,8 +92,11 @@ class Picker { const label = document.createElement('span'); label.classList.add('ql-picker-label'); label.innerHTML = DropdownIcon; + + // TODO: @cofi set all tabindex to -1 initially and then per JS set first one to 0. Then per keyboard right/left navigation set the next/prev to 0 and the rest to -1 // @ts-expect-error - label.tabIndex = '0'; + label.tabIndex = '-1'; + label.setAttribute('role', 'button'); label.setAttribute('aria-expanded', 'false'); this.container.appendChild(label); From 94ea34e6e66d3ea94a97bc1ad4ea34a000a62e6c Mon Sep 17 00:00:00 2001 From: colfin-96 Date: Tue, 7 Jan 2025 22:32:06 +0100 Subject: [PATCH 2/7] Implement roving tabindex for toolbar and picker components --- packages/quill/src/modules/toolbar.ts | 52 ++++++++++++++++++- packages/quill/src/ui/picker.ts | 73 +++++++++++++++++++++++---- 2 files changed, 115 insertions(+), 10 deletions(-) diff --git a/packages/quill/src/modules/toolbar.ts b/packages/quill/src/modules/toolbar.ts index 9c515e2495..b522925c6b 100644 --- a/packages/quill/src/modules/toolbar.ts +++ b/packages/quill/src/modules/toolbar.ts @@ -27,6 +27,8 @@ class Toolbar extends Module { controls: [string, HTMLElement][]; handlers: Record; + hasRovingTabindex: boolean = false; + constructor(quill: Quill, options: Partial) { super(quill, options); if (Array.isArray(this.options.container)) { @@ -45,6 +47,10 @@ class Toolbar extends Module { return; } this.container.classList.add('ql-toolbar'); + + // 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; + this.controls = []; this.handlers = {}; if (this.options.handlers) { @@ -133,10 +139,54 @@ class Toolbar extends Module { } this.update(range); }); - input.tabIndex = 123; + + if (this.hasRovingTabindex && input.tagName === 'BUTTON') { + input.addEventListener('keydown', (e) => { + this.handleKeyboardEvent(e); + }); + } + this.controls.push([format, input]); } + handleKeyboardEvent(e: KeyboardEvent) { + var target = e.currentTarget; + if (!target) return; + + switch (e.key) { + case 'ArrowLeft': + case 'ArrowRight': + this.updateTabIndexes(target, e.key); + break; + } + } + + updateTabIndexes(target: EventTarget, key: string) { + const currentIndex = this.controls.findIndex(control => control[1] === target); + const currentItem = this.controls[currentIndex][1]; + currentItem.tabIndex = -1; + + let nextIndex; + if (key === 'ArrowLeft') { + nextIndex = currentIndex === 0 ? this.controls.length - 1 : currentIndex - 1; + } else if (key === 'ArrowRight') { + nextIndex = currentIndex === this.controls.length - 1 ? 0 : currentIndex + 1; + } + + if (nextIndex === undefined) return; + const nextItem = this.controls[nextIndex][1]; + if (nextItem.tagName === 'SELECT') { + const qlPickerLabel = nextItem.previousElementSibling?.querySelectorAll('.ql-picker-label')[0]; + if (qlPickerLabel && qlPickerLabel.tagName === 'SPAN') { + (qlPickerLabel as HTMLElement).tabIndex = 0; + (qlPickerLabel as HTMLElement).focus(); + } + } else { + nextItem.tabIndex = 0; + nextItem.focus(); + } + } + update(range: Range | null) { const formats = range == null ? {} : this.quill.getFormat(range); this.controls.forEach((pair) => { diff --git a/packages/quill/src/ui/picker.ts b/packages/quill/src/ui/picker.ts index 011043fb57..4f6d6a2c48 100644 --- a/packages/quill/src/ui/picker.ts +++ b/packages/quill/src/ui/picker.ts @@ -14,6 +14,8 @@ class Picker { container: HTMLElement; label: HTMLElement; + hasRovingTabindex: boolean = false; + constructor(select: HTMLSelectElement) { this.select = select; this.container = document.createElement('span'); @@ -22,6 +24,11 @@ class Picker { // @ts-expect-error Fix me later this.select.parentNode.insertBefore(this.container, this.select); + // Set tabIndex for the first item in the toolbar + this.hasRovingTabindex = this.container.closest('.roving-tabindex') !== null; + this.setTabIndexes(); + + this.label.addEventListener('mousedown', () => { this.togglePicker(); }); @@ -34,9 +41,6 @@ class Picker { this.escape(); event.preventDefault(); break; - case 'ArrowLeft': - case 'ArrowRight': - this.toggleTabIndex(); default: } }); @@ -51,10 +55,6 @@ class Picker { toggleAriaAttribute(this.options, 'aria-hidden'); } - toggleTabIndex() { - this.label.tabIndex = this.label.tabIndex === 0 ? -1 : 0; - } - buildItem(option: HTMLOptionElement) { const item = document.createElement('span'); // @ts-expect-error @@ -93,9 +93,13 @@ class Picker { label.classList.add('ql-picker-label'); label.innerHTML = DropdownIcon; - // TODO: @cofi set all tabindex to -1 initially and then per JS set first one to 0. Then per keyboard right/left navigation set the next/prev to 0 and the rest to -1 + // 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'); label.setAttribute('aria-expanded', 'false'); @@ -103,6 +107,57 @@ class Picker { return label; } + setTabIndexes() { + const toolbar = this.select.closest('.ql-toolbar'); + if (!toolbar) return; + const items = Array.from(toolbar.querySelectorAll('.ql-picker .ql-picker-label, .ql-toolbar button')); + + if (this.hasRovingTabindex) { + if (items[0] === this.label) { + items[0].setAttribute('tabindex', '0') + } + + } else { + items.forEach((item) => { + item.setAttribute('tabindex', '0'); + }); + } + } + + handleKeyboardEvent(e: KeyboardEvent) { + if (!this.hasRovingTabindex) return; + var target = e.currentTarget; + if (!target) return; + + switch (e.key) { + case 'ArrowLeft': + case 'ArrowRight': + this.updateTabIndexes(target, e.key); + break; + } + } + + 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'); @@ -190,7 +245,7 @@ class Picker { const item = // @ts-expect-error Fix me later this.container.querySelector('.ql-picker-options').children[ - this.select.selectedIndex + this.select.selectedIndex ]; option = this.select.options[this.select.selectedIndex]; // @ts-expect-error From d3770d55e08313835a974eb4952c302a3da80359 Mon Sep 17 00:00:00 2001 From: colfin-96 Date: Tue, 7 Jan 2025 22:35:11 +0100 Subject: [PATCH 3/7] Revert version to 2.0.3 and remove endOfLine rule from ESLint configuration --- packages/quill/.eslintrc.json | 3 +-- packages/quill/package.json | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/quill/.eslintrc.json b/packages/quill/.eslintrc.json index 8d0f04b2ce..edf5dc5f30 100644 --- a/packages/quill/.eslintrc.json +++ b/packages/quill/.eslintrc.json @@ -36,8 +36,7 @@ "@typescript-eslint/ban-types": "off", "@typescript-eslint/no-explicit-any": "off", "import/no-named-as-default-member": "off", - "prefer-arrow-callback": "error", - "endOfLine": "auto" + "prefer-arrow-callback": "error" } } ] diff --git a/packages/quill/package.json b/packages/quill/package.json index 858ee53a8b..4dd6a2e662 100644 --- a/packages/quill/package.json +++ b/packages/quill/package.json @@ -1,6 +1,6 @@ { "name": "quill", - "version": "2.0.3-beta.2", + "version": "2.0.3", "description": "Your powerful, rich text editor", "author": "Jason Chen ", "homepage": "https://quilljs.com", From 06661f7b87102cbbcd3bf35ab87482b7c1aae917 Mon Sep 17 00:00:00 2001 From: colfin-96 Date: Tue, 7 Jan 2025 22:35:51 +0100 Subject: [PATCH 4/7] Bump version to 2.0.3 in package-lock.json --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49a90bc690..23085e2961 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "quill-monorepo", - "version": "2.0.2", + "version": "2.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "quill-monorepo", - "version": "2.0.2", + "version": "2.0.3", "license": "BSD-3-Clause", "workspaces": [ "packages/*" @@ -19236,7 +19236,7 @@ } }, "packages/quill": { - "version": "2.0.3-beta.1", + "version": "2.0.3", "license": "BSD-3-Clause", "dependencies": { "eventemitter3": "^5.0.1", @@ -19361,7 +19361,7 @@ } }, "packages/website": { - "version": "2.0.2", + "version": "2.0.3", "license": "BSD-3-Clause", "dependencies": { "@codesandbox/sandpack-react": "^2.11.3", From d3fb27d1ca6f9f4a9e96197b25b9f1f130c4d7e9 Mon Sep 17 00:00:00 2001 From: colfin-96 Date: Fri, 10 Jan 2025 01:15:50 +0100 Subject: [PATCH 5/7] Refactor arrow key handling in toolbar and picker components for improved readability --- packages/quill/src/modules/toolbar.ts | 11 ++++------- packages/quill/src/ui/picker.ts | 7 ++----- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/quill/src/modules/toolbar.ts b/packages/quill/src/modules/toolbar.ts index b522925c6b..25091592d7 100644 --- a/packages/quill/src/modules/toolbar.ts +++ b/packages/quill/src/modules/toolbar.ts @@ -153,12 +153,9 @@ class Toolbar extends Module { var target = e.currentTarget; if (!target) return; - switch (e.key) { - case 'ArrowLeft': - case 'ArrowRight': + if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { this.updateTabIndexes(target, e.key); - break; - } + } } updateTabIndexes(target: EventTarget, key: string) { @@ -166,14 +163,14 @@ class Toolbar extends Module { const currentItem = this.controls[currentIndex][1]; currentItem.tabIndex = -1; - let nextIndex; + let nextIndex: number | null = null; if (key === 'ArrowLeft') { nextIndex = currentIndex === 0 ? this.controls.length - 1 : currentIndex - 1; } else if (key === 'ArrowRight') { nextIndex = currentIndex === this.controls.length - 1 ? 0 : currentIndex + 1; } - if (nextIndex === undefined) return; + if (nextIndex === null) return; const nextItem = this.controls[nextIndex][1]; if (nextItem.tagName === 'SELECT') { const qlPickerLabel = nextItem.previousElementSibling?.querySelectorAll('.ql-picker-label')[0]; diff --git a/packages/quill/src/ui/picker.ts b/packages/quill/src/ui/picker.ts index 4f6d6a2c48..5ba6390d09 100644 --- a/packages/quill/src/ui/picker.ts +++ b/packages/quill/src/ui/picker.ts @@ -129,12 +129,9 @@ class Picker { var target = e.currentTarget; if (!target) return; - switch (e.key) { - case 'ArrowLeft': - case 'ArrowRight': + if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { this.updateTabIndexes(target, e.key); - break; - } + } } updateTabIndexes(target: EventTarget, key: string) { From cfb866c84da2b4632b78b1c4f9283023237a7bbb Mon Sep 17 00:00:00 2001 From: colfin-96 Date: Sun, 12 Jan 2025 23:01:28 +0100 Subject: [PATCH 6/7] Implement roving tabindex functionality in toolbar and picker components --- packages/quill/src/modules/toolbar.ts | 42 ++++++++++++++----- packages/quill/src/ui/picker.ts | 34 --------------- packages/website/src/data/playground.tsx | 4 ++ .../src/playground/roving-tabindex/index.html | 5 +++ .../src/playground/roving-tabindex/index.js | 11 +++++ .../roving-tabindex/playground.json | 12 ++++++ 6 files changed, 64 insertions(+), 44 deletions(-) create mode 100644 packages/website/src/playground/roving-tabindex/index.html create mode 100644 packages/website/src/playground/roving-tabindex/index.js create mode 100644 packages/website/src/playground/roving-tabindex/playground.json diff --git a/packages/quill/src/modules/toolbar.ts b/packages/quill/src/modules/toolbar.ts index 25091592d7..d97c454ff2 100644 --- a/packages/quill/src/modules/toolbar.ts +++ b/packages/quill/src/modules/toolbar.ts @@ -50,6 +50,11 @@ class Toolbar extends Module { // 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 = {}; @@ -140,28 +145,45 @@ class Toolbar extends Module { 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') { diff --git a/packages/quill/src/ui/picker.ts b/packages/quill/src/ui/picker.ts index 5ba6390d09..7b74df72bc 100644 --- a/packages/quill/src/ui/picker.ts +++ b/packages/quill/src/ui/picker.ts @@ -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'); @@ -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'); diff --git a/packages/website/src/data/playground.tsx b/packages/website/src/data/playground.tsx index 0d304dc56a..e684aebc4d 100644 --- a/packages/website/src/data/playground.tsx +++ b/packages/website/src/data/playground.tsx @@ -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', diff --git a/packages/website/src/playground/roving-tabindex/index.html b/packages/website/src/playground/roving-tabindex/index.html new file mode 100644 index 0000000000..ed57b133fc --- /dev/null +++ b/packages/website/src/playground/roving-tabindex/index.html @@ -0,0 +1,5 @@ +
+
+
+ + diff --git a/packages/website/src/playground/roving-tabindex/index.js b/packages/website/src/playground/roving-tabindex/index.js new file mode 100644 index 0000000000..95c9d4701c --- /dev/null +++ b/packages/website/src/playground/roving-tabindex/index.js @@ -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' +}); diff --git a/packages/website/src/playground/roving-tabindex/playground.json b/packages/website/src/playground/roving-tabindex/playground.json new file mode 100644 index 0000000000..b3b4681b77 --- /dev/null +++ b/packages/website/src/playground/roving-tabindex/playground.json @@ -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" + ] +} From 48f0ec9dc6f511c183ef6e9f581e9f1b399c7386 Mon Sep 17 00:00:00 2001 From: Colin Finger Date: Tue, 14 Jan 2025 15:35:23 +0100 Subject: [PATCH 7/7] Added a11y tests for roving tabindex --- packages/quill/test/unit/core/quill.spec.ts | 58 +++++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/packages/quill/test/unit/core/quill.spec.ts b/packages/quill/test/unit/core/quill.spec.ts index c128c0dc12..eb83df6078 100644 --- a/packages/quill/test/unit/core/quill.spec.ts +++ b/packages/quill/test/unit/core/quill.spec.ts @@ -36,7 +36,7 @@ describe('Quill', () => { }); test('register(path, target)', () => { - class Counter {} + class Counter { } Quill.register('modules/counter', Counter); expect(Quill.imports).toHaveProperty('modules/counter', Counter); @@ -59,7 +59,7 @@ describe('Quill', () => { static blotName = 'a-blot'; static className = 'ql-a-blot'; } - class AModule {} + class AModule { } Quill.register({ 'formats/a-blot': ABlot, 'modules/a-module': AModule, @@ -833,7 +833,7 @@ describe('Quill', () => { }); test('toolbar custom handler, default container', () => { - const handler = () => {}; // eslint-disable-line func-style + const handler = () => { }; // eslint-disable-line func-style const config = expandConfig(`#${testContainerId}`, { modules: { toolbar: { @@ -1201,7 +1201,7 @@ describe('Quill', () => { observer.observe(element); // Firefox doesn't call IntersectionObserver callback unless // there are rafs. - requestAnimationFrame(() => {}); + requestAnimationFrame(() => { }); }); }; @@ -1377,4 +1377,54 @@ describe('Quill', () => { ).toEqual(0); }); }); + + describe('accessibility', () => { + describe('toolbar', () => { + test('tabbing - no roving tabindex', () => { + const container = createContainer('
'); + new Quill(container, { + modules: { + toolbar: [['bold', 'italic'], ['link', 'image']], + }, + }); + const toolbar = container?.parentElement?.querySelector('.ql-toolbar'); + const buttons = toolbar?.querySelectorAll('button'); + if (!buttons) { + throw new Error('No buttons found'); + } + expect(buttons.length).toBe(4); + + buttons.forEach((button) => { + expect(button.getAttribute('tabindex')).toBe(null); + }); + }); + + test('tabbing - roving tabindex', () => { + const container = createContainer('
'); + if (!container.parentElement) { + throw new Error('No parent element found'); + } + container.parentElement.classList.add('roving-tabindex'); + new Quill(container, { + modules: { + toolbar: [['bold', 'italic'], ['link', 'image']], + }, + }); + const toolbar = container?.parentElement?.querySelector('.ql-toolbar'); + const buttons = toolbar?.querySelectorAll('button'); + if (!buttons) { + throw new Error('No buttons found'); + } + expect(buttons.length).toBe(4); + + buttons.forEach((button, key) => { + if (key === 0) { + expect(button.getAttribute('tabindex')).toBe('0'); + } else { + expect(button.getAttribute('tabindex')).toBe('-1'); + } + }); + }); + }); + }); });