Fix supertab
This commit is contained in:
parent
698cd59693
commit
e5333546db
5 changed files with 44 additions and 28 deletions
|
@ -234,6 +234,7 @@ export function NavTabs({
|
|||
return (
|
||||
<Tabs orientation="vertical" className="NavTabs__Container">
|
||||
<nav
|
||||
data-supertab
|
||||
className={classNames('NavTabs', {
|
||||
'NavTabs--collapsed': navTabsCollapsed,
|
||||
})}
|
||||
|
@ -356,7 +357,6 @@ export function NavTabs({
|
|||
<button
|
||||
type="button"
|
||||
className="NavTabs__Item NavTabs__Item--Profile"
|
||||
data-supertab
|
||||
onClick={() => {
|
||||
onToggleProfileEditor();
|
||||
}}
|
||||
|
|
|
@ -61,7 +61,7 @@ import {
|
|||
import { DurationInSeconds } from '../util/durations';
|
||||
import { useEscapeHandling } from '../hooks/useEscapeHandling';
|
||||
import { useUniqueId } from '../hooks/useUniqueId';
|
||||
import { focusableSelectors } from '../util/focusableSelectors';
|
||||
import { focusableSelector } from '../util/focusableSelectors';
|
||||
import { Modal } from './Modal';
|
||||
import { SearchInput } from './SearchInput';
|
||||
import { removeDiacritics } from '../util/removeDiacritics';
|
||||
|
@ -400,7 +400,6 @@ export function Preferences({
|
|||
[onSelectedMicrophoneChange, availableMicrophones]
|
||||
);
|
||||
|
||||
const selectors = useMemo(() => focusableSelectors.join(','), []);
|
||||
const settingsPaneRef = useRef<HTMLDivElement | null>(null);
|
||||
useEffect(() => {
|
||||
const settingsPane = settingsPaneRef.current;
|
||||
|
@ -414,12 +413,12 @@ export function Preferences({
|
|||
| HTMLInputElement
|
||||
| HTMLSelectElement
|
||||
| HTMLTextAreaElement
|
||||
>(selectors);
|
||||
>(focusableSelector);
|
||||
if (!elements.length) {
|
||||
return;
|
||||
}
|
||||
elements[0]?.focus();
|
||||
}, [page, selectors]);
|
||||
}, [page]);
|
||||
|
||||
const onAudioOutputSelectChange = useCallback(
|
||||
(value: string) => {
|
||||
|
|
|
@ -6,7 +6,7 @@ import * as log from '../logging/log';
|
|||
import { PanelType } from '../types/Panels';
|
||||
import { clearConversationDraftAttachments } from '../util/clearConversationDraftAttachments';
|
||||
import { drop } from '../util/drop';
|
||||
import { focusableSelectors } from '../util/focusableSelectors';
|
||||
import { matchOrQueryFocusable } from '../util/focusableSelectors';
|
||||
import { getQuotedMessageSelector } from '../state/selectors/composer';
|
||||
import { removeLinkPreview } from './LinkPreview';
|
||||
|
||||
|
@ -48,25 +48,32 @@ export function addGlobalKeyboardShortcuts(): void {
|
|||
) {
|
||||
window.enterKeyboardMode();
|
||||
const focusedElement = document.activeElement;
|
||||
const targets: Array<HTMLElement> = Array.from(
|
||||
document.querySelectorAll('[data-supertab="true"]')
|
||||
const targets = Array.from(
|
||||
document.querySelectorAll<HTMLElement>('[data-supertab="true"]')
|
||||
);
|
||||
const focusedIndex = targets.findIndex(target => {
|
||||
if (!target || !focusedElement) {
|
||||
return false;
|
||||
}
|
||||
const focusedIndexes: Array<number> = [];
|
||||
|
||||
if (target === focusedElement) {
|
||||
return true;
|
||||
targets.forEach((target, index) => {
|
||||
if (
|
||||
(focusedElement != null && target === focusedElement) ||
|
||||
target.contains(focusedElement)
|
||||
) {
|
||||
focusedIndexes.push(index);
|
||||
}
|
||||
|
||||
if (target.contains(focusedElement)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (focusedIndexes.length > 1) {
|
||||
log.error(
|
||||
`supertab: found multiple supertab elements containing the current active element: ${focusedIndexes.join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
// Default to the last focusable element to avoid cycles when multiple
|
||||
// elements match (generally going to be a parent element)
|
||||
const focusedIndex = focusedIndexes.at(-1) ?? -1;
|
||||
|
||||
const lastIndex = targets.length - 1;
|
||||
const increment = shiftKey ? -1 : 1;
|
||||
|
||||
|
@ -85,9 +92,7 @@ export function addGlobalKeyboardShortcuts(): void {
|
|||
}
|
||||
|
||||
const node = targets[index];
|
||||
const firstFocusableElement = node.querySelectorAll<HTMLElement>(
|
||||
focusableSelectors.join(',')
|
||||
)[0];
|
||||
const firstFocusableElement = matchOrQueryFocusable(node);
|
||||
|
||||
if (firstFocusableElement) {
|
||||
firstFocusableElement.focus();
|
||||
|
|
|
@ -6,7 +6,6 @@ import React, {
|
|||
forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
@ -32,7 +31,7 @@ import {
|
|||
getPanelInformation,
|
||||
getWasPanelAnimated,
|
||||
} from '../selectors/conversations';
|
||||
import { focusableSelectors } from '../../util/focusableSelectors';
|
||||
import { focusableSelector } from '../../util/focusableSelectors';
|
||||
import { missingCaseError } from '../../util/missingCaseError';
|
||||
import { useConversationsActions } from '../ducks/conversations';
|
||||
import { useReducedMotion } from '../../hooks/useReducedMotion';
|
||||
|
@ -269,7 +268,6 @@ const PanelContainer = forwardRef<
|
|||
const { popPanelForConversation } = useConversationsActions();
|
||||
const conversationTitle = getConversationTitleForPanelType(i18n, panel.type);
|
||||
|
||||
const selectors = useMemo(() => focusableSelectors.join(','), []);
|
||||
const focusRef = useRef<HTMLDivElement | null>(null);
|
||||
useEffect(() => {
|
||||
if (!isActive) {
|
||||
|
@ -281,12 +279,12 @@ const PanelContainer = forwardRef<
|
|||
return;
|
||||
}
|
||||
|
||||
const elements = focusNode.querySelectorAll<HTMLElement>(selectors);
|
||||
const elements = focusNode.querySelectorAll<HTMLElement>(focusableSelector);
|
||||
if (!elements.length) {
|
||||
return;
|
||||
}
|
||||
elements[0]?.focus();
|
||||
}, [isActive, panel, selectors]);
|
||||
}, [isActive, panel]);
|
||||
|
||||
return (
|
||||
<div className="ConversationPanel" ref={ref}>
|
||||
|
|
|
@ -28,3 +28,17 @@ export const focusableSelectors = [
|
|||
`[contenteditable]${not.inert}${not.negTabIndex}`,
|
||||
`[tabindex]${not.inert}${not.negTabIndex}`,
|
||||
];
|
||||
|
||||
export const focusableSelector = focusableSelectors.join(', ');
|
||||
|
||||
/**
|
||||
* Matches the first focusable element within the given element or itself if it
|
||||
* is focusable.
|
||||
*/
|
||||
export function matchOrQueryFocusable(
|
||||
element: HTMLElement
|
||||
): HTMLElement | null {
|
||||
return element.matches(focusableSelector)
|
||||
? element
|
||||
: element.querySelector(focusableSelector);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue