️(frontend) announce formatting shortcuts for screen readers

Announce formatting shortcuts (headings, lists, paragraph, code block).
This commit is contained in:
Cyril
2026-03-18 13:29:19 +01:00
parent 2a708d6e46
commit aadabf8d3c
2 changed files with 74 additions and 1 deletions
+1
View File
@@ -14,6 +14,7 @@ and this project adheres to
- ♿(frontend) focus skip link on headings and skip grid dropzone #1983
- ♿️(frontend) fix search modal accessibility issues #2054
- ♿️(frontend) add sr-only format to export download button #2088
- ♿️(frontend) announce formatting shortcuts for screen readers #2070
## [v4.8.2] - 2026-03-19
@@ -1,11 +1,83 @@
import { useEffect } from 'react';
import { announce } from '@react-aria/live-announcer';
import { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { DocsBlockNoteEditor } from '../types';
const getFormattingShortcutLabel = (
event: KeyboardEvent,
t: (key: string) => string,
): string | null => {
const isMod = event.ctrlKey || event.metaKey;
if (!isMod) {
return null;
}
if (event.altKey) {
switch (event.code) {
case 'Digit1':
return t('Heading 1 applied');
case 'Digit2':
return t('Heading 2 applied');
case 'Digit3':
return t('Heading 3 applied');
default:
return null;
}
}
if (event.shiftKey) {
switch (event.code) {
case 'Digit0':
return t('Paragraph applied');
case 'Digit6':
return t('Toggle list applied');
case 'Digit7':
return t('Numbered list applied');
case 'Digit8':
return t('Bulleted list applied');
case 'Digit9':
return t('Checklist applied');
case 'KeyC':
return t('Code block applied');
default:
return null;
}
}
return null;
};
export const useShortcuts = (
editor: DocsBlockNoteEditor,
el: HTMLDivElement | null,
) => {
const { t } = useTranslation();
const handleFormattingShortcut = useCallback(
(event: KeyboardEvent) => {
if (!editor?.isFocused()) {
return;
}
const label = getFormattingShortcutLabel(event, t);
if (label) {
setTimeout(() => {
announce(label, 'assertive');
}, 150);
}
},
[editor, t],
);
useEffect(() => {
el?.addEventListener('keydown', handleFormattingShortcut, true);
return () => {
el?.removeEventListener('keydown', handleFormattingShortcut, true);
};
}, [el, handleFormattingShortcut]);
useEffect(() => {
// Check if editor and its view are mounted
if (!editor || !el) {