Skip to content

Commit 4a5ccb8

Browse files
committed
♿️(frontend) announce formatting shortcuts for screen readers
Announce formatting shortcuts (headings, lists, paragraph, code block).
1 parent 99c4865 commit 4a5ccb8

File tree

2 files changed

+74
-1
lines changed

2 files changed

+74
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to
1111
- 💫(frontend) fix the help button to the bottom in tree #2073
1212
- ♿️(frontend) improve version history list accessibility #2033
1313
- ♿️(frontend) fix more options menu feedback for screen readers #2071
14+
- ♿️(frontend) announce formatting shortcuts for screen readers #2070
1415

1516
## [v4.8.2] - 2026-03-19
1617

src/frontend/apps/impress/src/features/docs/doc-editor/hook/useShortcuts.tsx

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,83 @@
1-
import { useEffect } from 'react';
1+
import { announce } from '@react-aria/live-announcer';
2+
import { useCallback, useEffect } from 'react';
3+
import { useTranslation } from 'react-i18next';
24

35
import { DocsBlockNoteEditor } from '../types';
46

7+
const getFormattingShortcutLabel = (
8+
event: KeyboardEvent,
9+
t: (key: string) => string,
10+
): string | null => {
11+
const isMod = event.ctrlKey || event.metaKey;
12+
if (!isMod) {
13+
return null;
14+
}
15+
16+
if (event.altKey) {
17+
switch (event.code) {
18+
case 'Digit1':
19+
return t('Heading 1 applied');
20+
case 'Digit2':
21+
return t('Heading 2 applied');
22+
case 'Digit3':
23+
return t('Heading 3 applied');
24+
default:
25+
return null;
26+
}
27+
}
28+
29+
if (event.shiftKey) {
30+
switch (event.code) {
31+
case 'Digit0':
32+
return t('Paragraph applied');
33+
case 'Digit6':
34+
return t('Toggle list applied');
35+
case 'Digit7':
36+
return t('Numbered list applied');
37+
case 'Digit8':
38+
return t('Bulleted list applied');
39+
case 'Digit9':
40+
return t('Checklist applied');
41+
case 'KeyC':
42+
return t('Code block applied');
43+
default:
44+
return null;
45+
}
46+
}
47+
48+
return null;
49+
};
50+
551
export const useShortcuts = (
652
editor: DocsBlockNoteEditor,
753
el: HTMLDivElement | null,
854
) => {
55+
const { t } = useTranslation();
56+
57+
const handleFormattingShortcut = useCallback(
58+
(event: KeyboardEvent) => {
59+
if (!editor?.isFocused()) {
60+
return;
61+
}
62+
63+
const label = getFormattingShortcutLabel(event, t);
64+
if (label) {
65+
setTimeout(() => {
66+
announce(label, 'assertive');
67+
}, 150);
68+
}
69+
},
70+
[editor, t],
71+
);
72+
73+
useEffect(() => {
74+
el?.addEventListener('keydown', handleFormattingShortcut, true);
75+
76+
return () => {
77+
el?.removeEventListener('keydown', handleFormattingShortcut, true);
78+
};
79+
}, [el, handleFormattingShortcut]);
80+
981
useEffect(() => {
1082
// Check if editor and its view are mounted
1183
if (!editor || !el) {

0 commit comments

Comments
 (0)