Enhance super tab

This commit is contained in:
Josh Perez 2023-05-09 11:52:03 -04:00 committed by GitHub
parent 8761bb8dae
commit 052aca3119
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 69 additions and 60 deletions

View file

@ -5182,6 +5182,10 @@
"messageformat": "(draft)", "messageformat": "(draft)",
"description": "Text shown in left pane as preview for conversation with saved a saved draft message" "description": "Text shown in left pane as preview for conversation with saved a saved draft message"
}, },
"icu:Keyboard--focus-most-recent-message": {
"messageformat": "Focus oldest unread or last message",
"description": "Shown in shortcuts guide"
},
"Keyboard--navigate-by-section": { "Keyboard--navigate-by-section": {
"message": "Navigate by section", "message": "Navigate by section",
"description": "(deleted 03/29/2023) Shown in the shortcuts guide" "description": "(deleted 03/29/2023) Shown in the shortcuts guide"

View file

@ -1354,13 +1354,15 @@ export async function startApp(): Promise<void> {
} }
// Super tab :) // Super tab :)
if (commandOrCtrl && key === 'F6') { if (
(commandOrCtrl && key === 'F6') ||
(commandOrCtrl && !shiftKey && (key === 't' || key === 'T'))
) {
window.enterKeyboardMode(); window.enterKeyboardMode();
const focusedElement = document.activeElement; const focusedElement = document.activeElement;
const targets: Array<HTMLElement> = Array.from( const targets: Array<HTMLElement> = Array.from(
document.querySelectorAll('[data-supertab="true"]') document.querySelectorAll('[data-supertab="true"]')
); );
const focusedIndex = targets.findIndex(target => { const focusedIndex = targets.findIndex(target => {
if (!target || !focusedElement) { if (!target || !focusedElement) {
return false; return false;
@ -1394,65 +1396,30 @@ export async function startApp(): Promise<void> {
} }
} }
targets[index] const node = targets[index];
.querySelectorAll<HTMLElement>(focusableSelectors.join(','))[0] const firstFocusableElement = node.querySelectorAll<HTMLElement>(
?.focus(); focusableSelectors.join(',')
} )[0];
// Navigate by section if (firstFocusableElement) {
if (commandOrCtrl && !shiftKey && (key === 't' || key === 'T')) { firstFocusableElement.focus();
window.enterKeyboardMode();
const focusedElement = document.activeElement;
const targets: Array<HTMLElement | null> = [
document.querySelector('.module-main-header .module-avatar-button'),
document.querySelector(
'.module-left-pane__header__contents__back-button'
),
document.querySelector('.LeftPaneSearchInput__input'),
document.querySelector('.module-main-header__compose-icon'),
document.querySelector(
'.module-left-pane__compose-search-form__input'
),
document.querySelector(
'.module-conversation-list__item--contact-or-conversation'
),
document.querySelector('.module-search-results'),
document.querySelector('.CompositionArea .ql-editor'),
];
const focusedIndex = targets.findIndex(target => {
if (!target || !focusedElement) {
return false;
}
if (target === focusedElement) {
return true;
}
if (target.contains(focusedElement)) {
return true;
}
return false;
});
const lastIndex = targets.length - 1;
let index;
if (focusedIndex < 0 || focusedIndex >= lastIndex) {
index = 0;
} else { } else {
index = focusedIndex + 1; const nodeInfo = Array.from(node.attributes)
} .map(attr => `${attr.name}=${attr.value}`)
.join(',');
while (!targets[index]) { log.warn(
index += 1; `supertab: could not find focus for DOM node ${node.nodeName}<${nodeInfo}>`
if (index > lastIndex) { );
index = 0; window.enterMouseMode();
const { activeElement } = document;
if (
activeElement &&
'blur' in activeElement &&
typeof activeElement.blur === 'function'
) {
activeElement.blur();
} }
} }
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
targets[index]!.focus();
} }
// Cancel out of keyboard shortcut screen - has first precedence // Cancel out of keyboard shortcut screen - has first precedence
@ -1611,6 +1578,23 @@ export async function startApp(): Promise<void> {
return; return;
} }
if (
conversation &&
commandOrCtrl &&
!shiftKey &&
(key === 'j' || key === 'J')
) {
window.enterKeyboardMode();
const item: HTMLElement | null =
document.querySelector(
'.module-last-seen-indicator ~ div .module-message'
) ||
document.querySelector(
'.module-timeline__last-message .module-message'
);
item?.focus();
}
// Open all media // Open all media
if ( if (
conversation && conversation &&

View file

@ -781,6 +781,7 @@ export function CompositionInput(props: Props): React.ReactElement {
{({ ref }) => ( {({ ref }) => (
<div <div
className={getClassName('__input')} className={getClassName('__input')}
data-supertab
ref={ref} ref={ref}
data-testid="CompositionInput" data-testid="CompositionInput"
data-enabled={disabled ? 'false' : 'true'} data-enabled={disabled ? 'false' : 'true'}

View file

@ -659,6 +659,7 @@ export function LeftPane({
<div <div
aria-live="polite" aria-live="polite"
className="module-left-pane__list" className="module-left-pane__list"
data-supertab
key={listKey} key={listKey}
role="presentation" role="presentation"
tabIndex={-1} tabIndex={-1}

View file

@ -118,6 +118,7 @@ export function MainHeader({
<div className="module-main-header"> <div className="module-main-header">
<div <div
className="module-main-header__avatar--container" className="module-main-header__avatar--container"
data-supertab
ref={setTargetElement} ref={setTargetElement}
> >
<Avatar <Avatar
@ -190,7 +191,7 @@ export function MainHeader({
</div>, </div>,
portalElement portalElement
)} )}
<div className="module-main-header__icon-container"> <div className="module-main-header__icon-container" data-supertab>
{areStoriesEnabled && ( {areStoriesEnabled && (
<button <button
aria-label={i18n('icu:stories')} aria-label={i18n('icu:stories')}

View file

@ -49,7 +49,7 @@ export const SearchInput = forwardRef<HTMLInputElement, PropTypes>(
) { ) {
const getClassName = getClassNamesFor(BASE_CLASS_NAME, moduleClassName); const getClassName = getClassNamesFor(BASE_CLASS_NAME, moduleClassName);
return ( return (
<div className={getClassName('__container')}> <div className={getClassName('__container')} data-supertab>
{hasSearchIcon && <i className={getClassName('__icon')} />} {hasSearchIcon && <i className={getClassName('__icon')} />}
{children} {children}
<input <input

View file

@ -23,6 +23,7 @@ type KeyType =
| 'enter' | 'enter'
| 'tab' | 'tab'
| 'ctrl' | 'ctrl'
| 'F6'
| '↑' | '↑'
| '↓' | '↓'
| ',' | ','
@ -60,7 +61,10 @@ function getNavigationShortcuts(i18n: LocalizerType): Array<ShortcutType> {
{ {
id: 'Keyboard--navigate-by-section', id: 'Keyboard--navigate-by-section',
description: i18n('icu:Keyboard--navigate-by-section'), description: i18n('icu:Keyboard--navigate-by-section'),
keys: [['commandOrCtrl', 'T']], keys: [
['commandOrCtrl', 'T'],
['commandOrCtrl', 'F6'],
],
}, },
{ {
id: 'Keyboard--previous-conversation', id: 'Keyboard--previous-conversation',
@ -93,6 +97,11 @@ function getNavigationShortcuts(i18n: LocalizerType): Array<ShortcutType> {
description: i18n('icu:Keyboard--conversation-by-index'), description: i18n('icu:Keyboard--conversation-by-index'),
keys: [['commandOrCtrl', '1 to 9']], keys: [['commandOrCtrl', '1 to 9']],
}, },
{
id: 'Keyboard--most-recent-message',
description: i18n('icu:Keyboard--focus-most-recent-message'),
keys: [['commandOrCtrl', 'J']],
},
{ {
id: 'Keyboard--preferences', id: 'Keyboard--preferences',
description: i18n('icu:Keyboard--preferences'), description: i18n('icu:Keyboard--preferences'),

View file

@ -885,6 +885,15 @@ export class Timeline extends React.Component<
messageNodes.push( messageNodes.push(
<div <div
key={messageId} key={messageId}
className={
itemIndex === items.length - 1
? 'module-timeline__last-message'
: undefined
}
data-supertab={
oldestUnseenIndex === itemIndex ||
(!oldestUnseenIndex && itemIndex === items.length - 1)
}
data-item-index={itemIndex} data-item-index={itemIndex}
data-message-id={messageId} data-message-id={messageId}
role="listitem" role="listitem"