[Accessibility] Add hotkeys PageUp and PageDown for list navigation (#39252)
This commit is contained in:
parent
65622a52e1
commit
ce3fdf73f3
@ -109,8 +109,8 @@ const hotkeyMatcherMap = {
|
|||||||
mention: just('m'),
|
mention: just('m'),
|
||||||
open: any('enter', 'o'),
|
open: any('enter', 'o'),
|
||||||
openProfile: just('p'),
|
openProfile: just('p'),
|
||||||
moveDown: just('j'),
|
moveDown: any('j', 'pagedown'),
|
||||||
moveUp: just('k'),
|
moveUp: any('k', 'pageup'),
|
||||||
moveToTop: just('0'),
|
moveToTop: just('0'),
|
||||||
toggleHidden: just('x'),
|
toggleHidden: just('x'),
|
||||||
toggleSensitive: just('h'),
|
toggleSensitive: just('h'),
|
||||||
@ -147,9 +147,15 @@ const hotkeyMatcherMap = {
|
|||||||
|
|
||||||
type HotkeyName = keyof typeof hotkeyMatcherMap;
|
type HotkeyName = keyof typeof hotkeyMatcherMap;
|
||||||
|
|
||||||
export type HandlerMap = Partial<
|
type HandlerFunction =
|
||||||
Record<HotkeyName, (event: KeyboardEvent) => void>
|
// When a handler returns a boolean, it should indicate whether the
|
||||||
>;
|
// hotkey was handled (i.e. it resulted in an action).
|
||||||
|
// If `false` is returned, `preventDefault` and `stopPropagation`
|
||||||
|
// will not be called on the keyboard event, restoring the key's
|
||||||
|
// native behaviour.
|
||||||
|
((event: KeyboardEvent) => boolean) | ((event: KeyboardEvent) => void);
|
||||||
|
|
||||||
|
export type HandlerMap = Partial<Record<HotkeyName, HandlerFunction>>;
|
||||||
|
|
||||||
export function useHotkeys<T extends HTMLElement>(handlers: HandlerMap) {
|
export function useHotkeys<T extends HTMLElement>(handlers: HandlerMap) {
|
||||||
const ref = useRef<T>(null);
|
const ref = useRef<T>(null);
|
||||||
@ -184,7 +190,7 @@ export function useHotkeys<T extends HTMLElement>(handlers: HandlerMap) {
|
|||||||
const matchCandidates: {
|
const matchCandidates: {
|
||||||
// A candidate will be have an undefined handler if it's matched,
|
// A candidate will be have an undefined handler if it's matched,
|
||||||
// but handled in a parent component rather than this one.
|
// but handled in a parent component rather than this one.
|
||||||
handler: ((event: KeyboardEvent) => void) | undefined;
|
handler: HandlerFunction | undefined;
|
||||||
priority: number;
|
priority: number;
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
@ -209,9 +215,11 @@ export function useHotkeys<T extends HTMLElement>(handlers: HandlerMap) {
|
|||||||
|
|
||||||
const bestMatchingHandler = matchCandidates.at(0)?.handler;
|
const bestMatchingHandler = matchCandidates.at(0)?.handler;
|
||||||
if (bestMatchingHandler) {
|
if (bestMatchingHandler) {
|
||||||
bestMatchingHandler(event);
|
const wasHandled = bestMatchingHandler(event);
|
||||||
event.stopPropagation();
|
if (wasHandled !== false) {
|
||||||
event.preventDefault();
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add last keypress to buffer
|
// Add last keypress to buffer
|
||||||
|
|||||||
@ -68,7 +68,7 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
|||||||
<td><FormattedMessage id='keyboard_shortcuts.quote' defaultMessage='Quote post' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.quote' defaultMessage='Quote post' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>enter</kbd>, <kbd>o</kbd></td>
|
<td><FormattedMessage id='keyboard_shortcuts.keys.enter' defaultMessage='Enter' tagName='kbd' />, <kbd>o</kbd></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.enter' defaultMessage='to open status' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.enter' defaultMessage='to open status' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -88,11 +88,11 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
|||||||
<td><FormattedMessage id='keyboard_shortcuts.toggle_sensitivity' defaultMessage='to show/hide media' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.toggle_sensitivity' defaultMessage='to show/hide media' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>k</kbd></td>
|
<td><kbd>k</kbd>, <FormattedMessage id='keyboard_shortcuts.keys.page_up' defaultMessage='Page Up' tagName='kbd' /></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.up' defaultMessage='to move up in the list' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.up' defaultMessage='to move up in the list' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>j</kbd></td>
|
<td><kbd>j</kbd>, <FormattedMessage id='keyboard_shortcuts.keys.page_down' defaultMessage='Page Down' tagName='kbd' /></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.down' defaultMessage='to move down in the list' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.down' defaultMessage='to move down in the list' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -112,15 +112,15 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
|||||||
<td><FormattedMessage id='keyboard_shortcuts.compose' defaultMessage='to focus the compose textarea' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.compose' defaultMessage='to focus the compose textarea' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>alt</kbd>+<kbd>n</kbd></td>
|
<td><FormattedMessage id='keyboard_shortcuts.keys.alt' defaultMessage='Alt' tagName='kbd' />+<kbd>n</kbd></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.toot' defaultMessage='to start a brand new post' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.toot' defaultMessage='to start a brand new post' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>alt</kbd>+<kbd>x</kbd></td>
|
<td><FormattedMessage id='keyboard_shortcuts.keys.alt' defaultMessage='Alt' tagName='kbd' />+<kbd>x</kbd></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.spoilers' defaultMessage='to show/hide CW field' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.spoilers' defaultMessage='to show/hide CW field' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>backspace</kbd></td>
|
<td><FormattedMessage id='keyboard_shortcuts.keys.backspace' defaultMessage='Backspace' tagName='kbd' /></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.back' defaultMessage='to navigate back' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.back' defaultMessage='to navigate back' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
@ -128,7 +128,7 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
|||||||
<td><FormattedMessage id='keyboard_shortcuts.search' defaultMessage='to focus search' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.search' defaultMessage='to focus search' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><kbd>esc</kbd></td>
|
<td><FormattedMessage id='keyboard_shortcuts.keys.esc' defaultMessage='Esc' tagName='kbd' /></td>
|
||||||
<td><FormattedMessage id='keyboard_shortcuts.unfocus' defaultMessage='to un-focus compose textarea/search' /></td>
|
<td><FormattedMessage id='keyboard_shortcuts.unfocus' defaultMessage='to un-focus compose textarea/search' /></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@ -508,7 +508,8 @@ class UI extends PureComponent {
|
|||||||
if (currentItemIndex === -1) {
|
if (currentItemIndex === -1) {
|
||||||
focusColumn(1);
|
focusColumn(1);
|
||||||
} else {
|
} else {
|
||||||
focusItemSibling(currentItemIndex, -1);
|
const wasHandled = focusItemSibling(currentItemIndex, -1);
|
||||||
|
return wasHandled;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -517,7 +518,8 @@ class UI extends PureComponent {
|
|||||||
if (currentItemIndex === -1) {
|
if (currentItemIndex === -1) {
|
||||||
focusColumn(1);
|
focusColumn(1);
|
||||||
} else {
|
} else {
|
||||||
focusItemSibling(currentItemIndex, 1);
|
const wasHandled = focusItemSibling(currentItemIndex, 1);
|
||||||
|
return wasHandled;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -159,13 +159,12 @@ export function focusItemSibling(index: number, direction: 1 | -1) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!siblingItem) {
|
if (!siblingItem) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If sibling element is empty, we skip it
|
// If sibling element is empty, we skip it
|
||||||
if (siblingItem.matches(':empty')) {
|
if (siblingItem.matches(':empty')) {
|
||||||
focusItemSibling(index + direction, direction);
|
return focusItemSibling(index + direction, direction);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the sibling is a post or a 'follow suggestions' widget
|
// Check if the sibling is a post or a 'follow suggestions' widget
|
||||||
@ -184,5 +183,8 @@ export function focusItemSibling(index: number, direction: 1 | -1) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
targetElement.focus();
|
targetElement.focus();
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -819,6 +819,12 @@
|
|||||||
"keyboard_shortcuts.heading": "Keyboard shortcuts",
|
"keyboard_shortcuts.heading": "Keyboard shortcuts",
|
||||||
"keyboard_shortcuts.home": "Open home timeline",
|
"keyboard_shortcuts.home": "Open home timeline",
|
||||||
"keyboard_shortcuts.hotkey": "Hotkey",
|
"keyboard_shortcuts.hotkey": "Hotkey",
|
||||||
|
"keyboard_shortcuts.keys.alt": "Alt",
|
||||||
|
"keyboard_shortcuts.keys.backspace": "Backspace",
|
||||||
|
"keyboard_shortcuts.keys.enter": "Enter",
|
||||||
|
"keyboard_shortcuts.keys.esc": "Esc",
|
||||||
|
"keyboard_shortcuts.keys.page_down": "Page Down",
|
||||||
|
"keyboard_shortcuts.keys.page_up": "Page Up",
|
||||||
"keyboard_shortcuts.legend": "Display this legend",
|
"keyboard_shortcuts.legend": "Display this legend",
|
||||||
"keyboard_shortcuts.load_more": "Focus \"Load more\" button",
|
"keyboard_shortcuts.load_more": "Focus \"Load more\" button",
|
||||||
"keyboard_shortcuts.local": "Open local timeline",
|
"keyboard_shortcuts.local": "Open local timeline",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user