From 303149b4d3e586ba32c1306d1a7d98542142d9d3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 8 Aug 2026 06:12:49 +0900 Subject: [PATCH 01/39] test(accessibility): require toolbar shortcut semantics --- src/toolbarShortcutAccessibility.test.tsx | 62 +++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/toolbarShortcutAccessibility.test.tsx diff --git a/src/toolbarShortcutAccessibility.test.tsx b/src/toolbarShortcutAccessibility.test.tsx new file mode 100644 index 00000000..b21e309d --- /dev/null +++ b/src/toolbarShortcutAccessibility.test.tsx @@ -0,0 +1,62 @@ +import { cleanup, render, screen } from '@testing-library/react'; +import { Editor } from '@tiptap/react'; +import { afterEach, describe, expect, it } from 'vitest'; + +import { buildExtensions } from './extensions/kit.js'; +import { Toolbar } from './components/Toolbar.js'; + +const openEditors: Editor[] = []; + +function makeEditor(): Editor { + const element = document.createElement('div'); + document.body.appendChild(element); + const editor = new Editor({ + element, + extensions: buildExtensions({ image: { maxDimension: 0 } }), + content: '

shortcut semantics

', + }); + openEditors.push(editor); + return editor; +} + +afterEach(() => { + cleanup(); + for (const editor of openEditors.splice(0)) { + if (!editor.isDestroyed) editor.destroy(); + } +}); + +describe('toolbar shortcut accessibility semantics', () => { + it('exposes every advertised cross-platform editor shortcut to assistive technology', () => { + const editor = makeEditor(); + editor.chain().focus().insertContent(' history').run(); + editor.commands.undo(); + + render(); + + expect(screen.getByRole('button', { name: /Bold/ })).toHaveAttribute( + 'aria-keyshortcuts', + 'Control+B Meta+B', + ); + expect(screen.getByRole('button', { name: /Italic/ })).toHaveAttribute( + 'aria-keyshortcuts', + 'Control+I Meta+I', + ); + expect(screen.getByRole('button', { name: /Insert\/edit link/ })).toHaveAttribute( + 'aria-keyshortcuts', + 'Control+K Meta+K', + ); + expect(screen.getByRole('button', { name: /Undo/ })).toHaveAttribute( + 'aria-keyshortcuts', + 'Control+Z Meta+Z', + ); + expect(screen.getByRole('button', { name: /Redo/ })).toHaveAttribute( + 'aria-keyshortcuts', + 'Control+Shift+Z Meta+Shift+Z', + ); + + expect(screen.getByRole('button', { name: /Strikethrough/ })).not.toHaveAttribute( + 'aria-keyshortcuts', + ); + }); +}); From 29d8c156e40eb0975687ee83053bedf3de7d77a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 8 Aug 2026 06:13:28 +0900 Subject: [PATCH 02/39] feat(accessibility): expose editor shortcut semantics --- src/components/Toolbar.tsx | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 5d5653eb..3731e427 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -24,6 +24,8 @@ interface ButtonProps { disabled?: boolean; title: string; label: string; + /** WAI-ARIA key shortcut tokens for shortcuts already implemented by the editor. */ + keyShortcuts?: string; } const TOOLBAR_ITEM_SELECTOR = 'button[data-cwl-toolbar-item="true"]'; @@ -45,7 +47,14 @@ function setRovingTabStop( } } -function ToolbarButton({ onClick, active, disabled, title, label }: ButtonProps) { +function ToolbarButton({ + onClick, + active, + disabled, + title, + label, + keyShortcuts, +}: ButtonProps) { return (