Merge branch 'hotkey-nav' into 'develop'
Fix hotkey navigation? See merge request soapbox-pub/soapbox-fe!1409
This commit is contained in:
commit
f90f76ae02
6 changed files with 54 additions and 44 deletions
|
@ -42,6 +42,7 @@ interface IScrollableList extends VirtuosoProps<any, any> {
|
||||||
onRefresh?: () => Promise<any>,
|
onRefresh?: () => Promise<any>,
|
||||||
className?: string,
|
className?: string,
|
||||||
itemClassName?: string,
|
itemClassName?: string,
|
||||||
|
id?: string,
|
||||||
style?: React.CSSProperties,
|
style?: React.CSSProperties,
|
||||||
useWindowScroll?: boolean
|
useWindowScroll?: boolean
|
||||||
}
|
}
|
||||||
|
@ -60,6 +61,7 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
||||||
onLoadMore,
|
onLoadMore,
|
||||||
className,
|
className,
|
||||||
itemClassName,
|
itemClassName,
|
||||||
|
id,
|
||||||
hasMore,
|
hasMore,
|
||||||
placeholderComponent: Placeholder,
|
placeholderComponent: Placeholder,
|
||||||
placeholderCount = 0,
|
placeholderCount = 0,
|
||||||
|
@ -133,6 +135,7 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
||||||
const renderFeed = (): JSX.Element => (
|
const renderFeed = (): JSX.Element => (
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
id={id}
|
||||||
useWindowScroll={useWindowScroll}
|
useWindowScroll={useWindowScroll}
|
||||||
className={className}
|
className={className}
|
||||||
data={data}
|
data={data}
|
||||||
|
|
|
@ -282,13 +282,11 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHotkeyMoveUp = (e?: KeyboardEvent): void => {
|
handleHotkeyMoveUp = (e?: KeyboardEvent): void => {
|
||||||
// FIXME: what's going on here?
|
this.props.onMoveUp(this.props.status.id, this.props.featured);
|
||||||
// this.props.onMoveUp(this.props.status.id, e?.target?.getAttribute('data-featured'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHotkeyMoveDown = (e?: KeyboardEvent): void => {
|
handleHotkeyMoveDown = (e?: KeyboardEvent): void => {
|
||||||
// FIXME: what's going on here?
|
this.props.onMoveDown(this.props.status.id, this.props.featured);
|
||||||
// this.props.onMoveDown(this.props.status.id, e?.target?.getAttribute('data-featured'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHotkeyToggleHidden = (): void => {
|
handleHotkeyToggleHidden = (): void => {
|
||||||
|
@ -601,7 +599,7 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers} data-testid='status'>
|
<HotKeys handlers={handlers} data-testid='status'>
|
||||||
<div
|
<div
|
||||||
className='status cursor-pointer'
|
className={classNames('status cursor-pointer', { focusable: this.props.focusable })}
|
||||||
tabIndex={this.props.focusable && !this.props.muted ? 0 : undefined}
|
tabIndex={this.props.focusable && !this.props.muted ? 0 : undefined}
|
||||||
data-featured={featured ? 'true' : null}
|
data-featured={featured ? 'true' : null}
|
||||||
aria-label={textForScreenReader(intl, status, rebloggedByText)}
|
aria-label={textForScreenReader(intl, status, rebloggedByText)}
|
||||||
|
|
|
@ -77,18 +77,18 @@ export default class StatusList extends ImmutablePureComponent {
|
||||||
this.props.onLoadMore(loadMoreID);
|
this.props.onLoadMore(loadMoreID);
|
||||||
}, 300, { leading: true })
|
}, 300, { leading: true })
|
||||||
|
|
||||||
_selectChild(index, align_top) {
|
_selectChild(index) {
|
||||||
const container = this.node.node;
|
this.node.scrollIntoView({
|
||||||
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
index,
|
||||||
|
behavior: 'smooth',
|
||||||
|
done: () => {
|
||||||
|
const element = document.querySelector(`#status-list [data-index="${index}"] .focusable`);
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
if (align_top && container.scrollTop > element.offsetTop) {
|
|
||||||
element.scrollIntoView(true);
|
|
||||||
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
|
|
||||||
element.scrollIntoView(false);
|
|
||||||
}
|
|
||||||
element.focus();
|
element.focus();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDequeueTimeline = () => {
|
handleDequeueTimeline = () => {
|
||||||
|
@ -216,6 +216,7 @@ export default class StatusList extends ImmutablePureComponent {
|
||||||
message={messages.queue}
|
message={messages.queue}
|
||||||
/>,
|
/>,
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
|
id='status-list'
|
||||||
key='scrollable-list'
|
key='scrollable-list'
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
showLoading={isLoading && statusIds.size === 0}
|
showLoading={isLoading && statusIds.size === 0}
|
||||||
|
|
|
@ -20,26 +20,26 @@ export default class ConversationsList extends ImmutablePureComponent {
|
||||||
|
|
||||||
handleMoveUp = id => {
|
handleMoveUp = id => {
|
||||||
const elementIndex = this.getCurrentIndex(id) - 1;
|
const elementIndex = this.getCurrentIndex(id) - 1;
|
||||||
this._selectChild(elementIndex, true);
|
this._selectChild(elementIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMoveDown = id => {
|
handleMoveDown = id => {
|
||||||
const elementIndex = this.getCurrentIndex(id) + 1;
|
const elementIndex = this.getCurrentIndex(id) + 1;
|
||||||
this._selectChild(elementIndex, false);
|
this._selectChild(elementIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectChild(index, align_top) {
|
_selectChild(index) {
|
||||||
const container = this.node.node;
|
this.node.scrollIntoView({
|
||||||
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
index,
|
||||||
|
behavior: 'smooth',
|
||||||
|
done: () => {
|
||||||
|
const element = document.querySelector(`#direct-list [data-index="${index}"] .focusable`);
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
if (align_top && container.scrollTop > element.offsetTop) {
|
|
||||||
element.scrollIntoView(true);
|
|
||||||
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
|
|
||||||
element.scrollIntoView(false);
|
|
||||||
}
|
|
||||||
element.focus();
|
element.focus();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setRef = c => {
|
setRef = c => {
|
||||||
|
@ -58,7 +58,9 @@ export default class ConversationsList extends ImmutablePureComponent {
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
{...other}
|
{...other}
|
||||||
onLoadMore={onLoadMore && this.handleLoadOlder}
|
onLoadMore={onLoadMore && this.handleLoadOlder}
|
||||||
scrollKey='direct' ref={this.setRef}
|
id='direct-list'
|
||||||
|
scrollKey='direct'
|
||||||
|
ref={this.setRef}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
showLoading={isLoading && conversations.size === 0}
|
showLoading={isLoading && conversations.size === 0}
|
||||||
>
|
>
|
||||||
|
|
|
@ -111,32 +111,37 @@ class Notifications extends React.PureComponent {
|
||||||
this.props.dispatch(scrollTopNotifications(false));
|
this.props.dispatch(scrollTopNotifications(false));
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.node = c;
|
||||||
|
}
|
||||||
|
|
||||||
setColumnRef = c => {
|
setColumnRef = c => {
|
||||||
this.column = c;
|
this.column = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMoveUp = id => {
|
handleMoveUp = id => {
|
||||||
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1;
|
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) - 1;
|
||||||
this._selectChild(elementIndex, true);
|
this._selectChild(elementIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMoveDown = id => {
|
handleMoveDown = id => {
|
||||||
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1;
|
const elementIndex = this.props.notifications.findIndex(item => item !== null && item.get('id') === id) + 1;
|
||||||
this._selectChild(elementIndex, false);
|
this._selectChild(elementIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectChild(index, align_top) {
|
_selectChild(index) {
|
||||||
|
this.node.scrollIntoView({
|
||||||
|
index,
|
||||||
|
behavior: 'smooth',
|
||||||
|
done: () => {
|
||||||
const container = this.column;
|
const container = this.column;
|
||||||
const element = container.querySelector(`article:nth-of-type(${index + 1}) .focusable`);
|
const element = container.querySelector(`[data-index="${index}"] .focusable`);
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
if (align_top && container.scrollTop > element.offsetTop) {
|
|
||||||
element.scrollIntoView(true);
|
|
||||||
} else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
|
|
||||||
element.scrollIntoView(false);
|
|
||||||
}
|
|
||||||
element.focus();
|
element.focus();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDequeueNotifications = () => {
|
handleDequeueNotifications = () => {
|
||||||
|
@ -161,6 +166,7 @@ class Notifications extends React.PureComponent {
|
||||||
const scrollContainer = (
|
const scrollContainer = (
|
||||||
<PullToRefresh onRefresh={this.handleRefresh}>
|
<PullToRefresh onRefresh={this.handleRefresh}>
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
|
ref={this.setRef}
|
||||||
useWindowScroll
|
useWindowScroll
|
||||||
data={showLoading ? Array(20).fill() : notifications.toArray()}
|
data={showLoading ? Array(20).fill() : notifications.toArray()}
|
||||||
startReached={this.handleScrollToTop}
|
startReached={this.handleScrollToTop}
|
||||||
|
|
|
@ -715,7 +715,7 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
||||||
<HotKeys handlers={handlers}>
|
<HotKeys handlers={handlers}>
|
||||||
<div
|
<div
|
||||||
ref={this.setStatusRef}
|
ref={this.setStatusRef}
|
||||||
className={classNames('detailed-status__wrapper')}
|
className='detailed-status__wrapper focusable'
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
// FIXME: no "reblogged by" text is added for the screen reader
|
// FIXME: no "reblogged by" text is added for the screen reader
|
||||||
aria-label={textForScreenReader(intl, status)}
|
aria-label={textForScreenReader(intl, status)}
|
||||||
|
|
Loading…
Reference in a new issue