Keyboard shortcuts and accessibility
This commit is contained in:
parent
8590a047c7
commit
20a892247f
87 changed files with 3652 additions and 711 deletions
|
@ -40,10 +40,15 @@ type PropsHousekeepingType = {
|
|||
id: string;
|
||||
unreadCount?: number;
|
||||
typingContact?: Object;
|
||||
selectedMessageId?: string;
|
||||
|
||||
i18n: LocalizerType;
|
||||
|
||||
renderItem: (id: string, actions: Object) => JSX.Element;
|
||||
renderItem: (
|
||||
id: string,
|
||||
conversationId: string,
|
||||
actions: Object
|
||||
) => JSX.Element;
|
||||
renderLastSeenIndicator: (id: string) => JSX.Element;
|
||||
renderLoadingRow: (id: string) => JSX.Element;
|
||||
renderTypingBubble: (id: string) => JSX.Element;
|
||||
|
@ -60,8 +65,10 @@ type PropsActionsType = {
|
|||
loadAndScroll: (messageId: string) => unknown;
|
||||
loadOlderMessages: (messageId: string) => unknown;
|
||||
loadNewerMessages: (messageId: string) => unknown;
|
||||
loadNewestMessages: (messageId: string) => unknown;
|
||||
loadNewestMessages: (messageId: string, setFocus?: boolean) => unknown;
|
||||
markMessageRead: (messageId: string) => unknown;
|
||||
selectMessage: (messageId: string, conversationId: string) => unknown;
|
||||
clearSelectedMessage: () => unknown;
|
||||
} & MessageActionsType &
|
||||
SafetyNumberActionsType;
|
||||
|
||||
|
@ -547,7 +554,7 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
style={styleWithWidth}
|
||||
role="row"
|
||||
>
|
||||
{renderItem(messageId, this.props)}
|
||||
{renderItem(messageId, id, this.props)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -662,7 +669,15 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
}
|
||||
};
|
||||
|
||||
public scrollToBottom = () => {
|
||||
public scrollToBottom = (setFocus?: boolean) => {
|
||||
const { selectMessage, id, items } = this.props;
|
||||
|
||||
if (setFocus && items && items.length > 0) {
|
||||
const lastIndex = items.length - 1;
|
||||
const lastMessageId = items[lastIndex];
|
||||
selectMessage(lastMessageId, id);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
propScrollToIndex: undefined,
|
||||
oneTimeScrollRow: undefined,
|
||||
|
@ -671,20 +686,31 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
};
|
||||
|
||||
public onClickScrollDownButton = () => {
|
||||
this.scrollDown(false);
|
||||
};
|
||||
|
||||
public scrollDown = (setFocus?: boolean) => {
|
||||
const {
|
||||
haveNewest,
|
||||
id,
|
||||
isLoadingMessages,
|
||||
items,
|
||||
loadNewestMessages,
|
||||
oldestUnreadIndex,
|
||||
selectMessage,
|
||||
} = this.props;
|
||||
if (!items || items.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lastId = items[items.length - 1];
|
||||
const lastSeenIndicatorRow = this.getLastSeenIndicatorRow();
|
||||
|
||||
if (!this.visibleRows) {
|
||||
if (haveNewest) {
|
||||
this.scrollToBottom();
|
||||
this.scrollToBottom(setFocus);
|
||||
} else if (!isLoadingMessages) {
|
||||
loadNewestMessages(lastId);
|
||||
loadNewestMessages(lastId, setFocus);
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -697,13 +723,17 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
isNumber(lastSeenIndicatorRow) &&
|
||||
newest.row < lastSeenIndicatorRow
|
||||
) {
|
||||
if (setFocus && isNumber(oldestUnreadIndex)) {
|
||||
const messageId = items[oldestUnreadIndex];
|
||||
selectMessage(messageId, id);
|
||||
}
|
||||
this.setState({
|
||||
oneTimeScrollRow: lastSeenIndicatorRow,
|
||||
});
|
||||
} else if (haveNewest) {
|
||||
this.scrollToBottom();
|
||||
this.scrollToBottom(setFocus);
|
||||
} else if (!isLoadingMessages) {
|
||||
loadNewestMessages(lastId);
|
||||
loadNewestMessages(lastId, setFocus);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -743,6 +773,10 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
prevProps.items.length === 0 ||
|
||||
resetCounter !== prevProps.resetCounter
|
||||
) {
|
||||
if (prevProps.items && prevProps.items.length > 0) {
|
||||
this.resize();
|
||||
}
|
||||
|
||||
const oneTimeScrollRow = this.getLastSeenIndicatorRow();
|
||||
this.setState({
|
||||
oneTimeScrollRow,
|
||||
|
@ -751,10 +785,6 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
prevPropScrollToIndex: scrollToIndex,
|
||||
});
|
||||
|
||||
if (prevProps.items && prevProps.items.length > 0) {
|
||||
this.resize();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -881,6 +911,93 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
return scrollToBottom;
|
||||
};
|
||||
|
||||
public handleBlur = (event: React.FocusEvent) => {
|
||||
const { clearSelectedMessage } = this.props;
|
||||
|
||||
const { currentTarget } = event;
|
||||
|
||||
// Thanks to https://gist.github.com/pstoica/4323d3e6e37e8a23dd59
|
||||
setTimeout(() => {
|
||||
if (!currentTarget.contains(document.activeElement)) {
|
||||
clearSelectedMessage();
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
|
||||
public handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
const { selectMessage, selectedMessageId, items, id } = this.props;
|
||||
const commandOrCtrl = event.metaKey || event.ctrlKey;
|
||||
|
||||
if (!items || items.length < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedMessageId && !commandOrCtrl && event.key === 'ArrowUp') {
|
||||
const selectedMessageIndex = items.findIndex(
|
||||
item => item === selectedMessageId
|
||||
);
|
||||
if (selectedMessageIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetIndex = selectedMessageIndex - 1;
|
||||
if (targetIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageId = items[targetIndex];
|
||||
selectMessage(messageId, id);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedMessageId && !commandOrCtrl && event.key === 'ArrowDown') {
|
||||
const selectedMessageIndex = items.findIndex(
|
||||
item => item === selectedMessageId
|
||||
);
|
||||
if (selectedMessageIndex < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const targetIndex = selectedMessageIndex + 1;
|
||||
if (targetIndex >= items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageId = items[targetIndex];
|
||||
selectMessage(messageId, id);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (commandOrCtrl && event.key === 'ArrowUp') {
|
||||
this.setState({ oneTimeScrollRow: 0 });
|
||||
|
||||
const firstMessageId = items[0];
|
||||
selectMessage(firstMessageId, id);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (commandOrCtrl && event.key === 'ArrowDown') {
|
||||
this.scrollDown(true);
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { i18n, id, items } = this.props;
|
||||
const {
|
||||
|
@ -896,7 +1013,13 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="module-timeline">
|
||||
<div
|
||||
className="module-timeline"
|
||||
role="group"
|
||||
tabIndex={-1}
|
||||
onBlur={this.handleBlur}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
>
|
||||
<AutoSizer>
|
||||
{({ height, width }) => {
|
||||
if (this.mostRecentWidth && this.mostRecentWidth !== width) {
|
||||
|
@ -925,6 +1048,7 @@ export class Timeline extends React.PureComponent<Props, State> {
|
|||
rowRenderer={this.rowRenderer}
|
||||
scrollToAlignment="start"
|
||||
scrollToIndex={scrollToIndex}
|
||||
tabIndex={-1}
|
||||
width={width}
|
||||
/>
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue