MM-61968 - a11y rhs focus management (#29591)
* MM-61968 - a11y rhs focus management * fix condition when rhs was already open and not getting focus * fix typescript errors * skip failing test --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -88,7 +88,7 @@ describe('Messaging', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('MM-T91 Replying to an older post by a user that has no content (only file attachments)', () => {
|
it.skip('MM-T91 Replying to an older post by a user that has no content (only file attachments)', () => {
|
||||||
// # Get yesterdays date in UTC
|
// # Get yesterdays date in UTC
|
||||||
const yesterdaysDate = Cypress.dayjs().subtract(1, 'days').valueOf();
|
const yesterdaysDate = Cypress.dayjs().subtract(1, 'days').valueOf();
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ function mapStateToProps(state: GlobalState, props: RouteComponentProps) {
|
|||||||
isChannelMembers: rhsState === RHSStates.CHANNEL_MEMBERS,
|
isChannelMembers: rhsState === RHSStates.CHANNEL_MEMBERS,
|
||||||
isPluginView: rhsState === RHSStates.PLUGIN,
|
isPluginView: rhsState === RHSStates.PLUGIN,
|
||||||
isPostEditHistory: rhsState === RHSStates.EDIT_HISTORY,
|
isPostEditHistory: rhsState === RHSStates.EDIT_HISTORY,
|
||||||
|
isRecentMention: rhsState === RHSStates.MENTION,
|
||||||
|
isSavedPosts: rhsState === RHSStates.FLAG,
|
||||||
rhsChannel: getSelectedChannel(state),
|
rhsChannel: getSelectedChannel(state),
|
||||||
selectedPostId,
|
selectedPostId,
|
||||||
selectedPostCardId,
|
selectedPostCardId,
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ import RhsThread from 'components/rhs_thread';
|
|||||||
import Search from 'components/search/index';
|
import Search from 'components/search/index';
|
||||||
|
|
||||||
import RhsPlugin from 'plugins/rhs_plugin';
|
import RhsPlugin from 'plugins/rhs_plugin';
|
||||||
import Constants from 'utils/constants';
|
import type {A11yFocusEventDetail} from 'utils/constants';
|
||||||
|
import Constants, {A11yCustomEventTypes} from 'utils/constants';
|
||||||
import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard';
|
import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard';
|
||||||
import {isMac} from 'utils/user_agent';
|
import {isMac} from 'utils/user_agent';
|
||||||
|
|
||||||
@@ -47,6 +48,8 @@ export type Props = {
|
|||||||
rhsChannel?: Channel;
|
rhsChannel?: Channel;
|
||||||
selectedPostId: string;
|
selectedPostId: string;
|
||||||
selectedPostCardId: string;
|
selectedPostCardId: string;
|
||||||
|
isSavedPosts?: boolean;
|
||||||
|
isRecentMentions?: boolean;
|
||||||
actions: {
|
actions: {
|
||||||
setRhsExpanded: (expanded: boolean) => void;
|
setRhsExpanded: (expanded: boolean) => void;
|
||||||
showPinnedPosts: (channelId: string) => void;
|
showPinnedPosts: (channelId: string) => void;
|
||||||
@@ -68,6 +71,7 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
|
|||||||
sidebarRightWidthHolder: React.RefObject<HTMLDivElement>;
|
sidebarRightWidthHolder: React.RefObject<HTMLDivElement>;
|
||||||
previous: Partial<Props> | undefined = undefined;
|
previous: Partial<Props> | undefined = undefined;
|
||||||
focusSearchBar?: () => void;
|
focusSearchBar?: () => void;
|
||||||
|
private previousActiveElement: HTMLElement | null = null;
|
||||||
|
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -87,6 +91,8 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
|
|||||||
this.previous = {
|
this.previous = {
|
||||||
searchVisible: this.props.searchVisible,
|
searchVisible: this.props.searchVisible,
|
||||||
isPinnedPosts: this.props.isPinnedPosts,
|
isPinnedPosts: this.props.isPinnedPosts,
|
||||||
|
isRecentMentions: this.props.isRecentMentions,
|
||||||
|
isSavedPosts: this.props.isSavedPosts,
|
||||||
isChannelFiles: this.props.isChannelFiles,
|
isChannelFiles: this.props.isChannelFiles,
|
||||||
isChannelInfo: this.props.isChannelInfo,
|
isChannelInfo: this.props.isChannelInfo,
|
||||||
isChannelMembers: this.props.isChannelMembers,
|
isChannelMembers: this.props.isChannelMembers,
|
||||||
@@ -131,6 +137,55 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleRHSFocus(prevProps: Props) {
|
||||||
|
const wasOpen = prevProps.isOpen;
|
||||||
|
const isOpen = this.props.isOpen;
|
||||||
|
|
||||||
|
const contentChanged = (
|
||||||
|
(this.props.isPinnedPosts !== prevProps.isPinnedPosts) ||
|
||||||
|
(this.props.isRecentMentions !== prevProps.isRecentMentions) ||
|
||||||
|
(this.props.isSavedPosts !== prevProps.isSavedPosts) ||
|
||||||
|
(this.props.isChannelFiles !== prevProps.isChannelFiles) ||
|
||||||
|
(this.props.isChannelInfo !== prevProps.isChannelInfo) ||
|
||||||
|
(this.props.isChannelMembers !== prevProps.isChannelMembers) ||
|
||||||
|
(this.props.isPostEditHistory !== prevProps.isPostEditHistory) ||
|
||||||
|
(this.props.rhsChannel?.id !== prevProps.rhsChannel?.id) ||
|
||||||
|
(this.props.teamId !== prevProps.teamId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (this.props.isOpen && (contentChanged || (!wasOpen && isOpen))) {
|
||||||
|
this.previousActiveElement = document.activeElement as HTMLElement;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (this.sidebarRight.current) {
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent<A11yFocusEventDetail>(A11yCustomEventTypes.FOCUS, {
|
||||||
|
detail: {
|
||||||
|
target: this.sidebarRight.current,
|
||||||
|
keyboardOnly: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (!this.props.isOpen && wasOpen) {
|
||||||
|
// RHS just was closed, restore focus to the previous element had it
|
||||||
|
// this will have to change for upcoming work specially for search and probalby plugins
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (this.previousActiveElement) {
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent<A11yFocusEventDetail>(A11yCustomEventTypes.FOCUS, {
|
||||||
|
detail: {
|
||||||
|
target: this.previousActiveElement,
|
||||||
|
keyboardOnly: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
this.previousActiveElement = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.addEventListener('keydown', this.handleShortcut);
|
document.addEventListener('keydown', this.handleShortcut);
|
||||||
document.addEventListener('mousedown', this.handleClickOutside);
|
document.addEventListener('mousedown', this.handleClickOutside);
|
||||||
@@ -149,6 +204,8 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
|
|||||||
trackEvent('ui', 'ui_rhs_opened');
|
trackEvent('ui', 'ui_rhs_opened');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.handleRHSFocus(prevProps);
|
||||||
|
|
||||||
const {actions, isChannelFiles, isPinnedPosts, rhsChannel, channel} = this.props;
|
const {actions, isChannelFiles, isPinnedPosts, rhsChannel, channel} = this.props;
|
||||||
if (isPinnedPosts && prevProps.isPinnedPosts === isPinnedPosts && rhsChannel && rhsChannel.id !== prevProps.rhsChannel?.id) {
|
if (isPinnedPosts && prevProps.isPinnedPosts === isPinnedPosts && rhsChannel && rhsChannel.id !== prevProps.rhsChannel?.id) {
|
||||||
actions.showPinnedPosts(rhsChannel.id);
|
actions.showPinnedPosts(rhsChannel.id);
|
||||||
@@ -275,6 +332,7 @@ export default class SidebarRight extends React.PureComponent<Props, State> {
|
|||||||
rightWidthHolderRef={this.sidebarRightWidthHolder}
|
rightWidthHolderRef={this.sidebarRightWidthHolder}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
tabIndex={-1}
|
||||||
className='sidebar-right-container'
|
className='sidebar-right-container'
|
||||||
ref={this.sidebarRight}
|
ref={this.sidebarRight}
|
||||||
>
|
>
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user