Bug 1944447 - Newtab shortcut refresh setting up arrow key navigation r=home-newtab-reviewers,reemhamz
Differential Revision: https://phabricator.services.mozilla.com/D235902
This commit is contained in:
@@ -59,6 +59,8 @@ export class ContextMenuButton extends React.PureComponent {
|
||||
onKeyDown={this.onKeyDown}
|
||||
onClick={this.onClick}
|
||||
ref={refFunction}
|
||||
tabIndex={this.props.tabIndex || 0}
|
||||
onFocus={this.props.onFocus}
|
||||
/>
|
||||
{showContextMenu
|
||||
? React.cloneElement(children, {
|
||||
|
||||
@@ -349,6 +349,7 @@ export class TopSiteLink extends React.PureComponent {
|
||||
onDragOver={this.onDragEvent}
|
||||
onDragEnter={this.onDragEvent}
|
||||
onDragLeave={this.onDragEvent}
|
||||
ref={this.props.setRef}
|
||||
{...draggableProps}
|
||||
>
|
||||
<div className="top-site-inner">
|
||||
@@ -357,12 +358,13 @@ export class TopSiteLink extends React.PureComponent {
|
||||
<a
|
||||
className="top-site-button"
|
||||
href={link.searchTopSite ? undefined : link.url}
|
||||
tabIndex="0"
|
||||
tabIndex={this.props.tabIndex}
|
||||
onKeyPress={this.onKeyPress}
|
||||
onClick={onClick}
|
||||
draggable={true}
|
||||
data-is-sponsored-link={!!link.sponsored_tile_id}
|
||||
title={title}
|
||||
onFocus={this.props.onFocus}
|
||||
>
|
||||
<div className="tile" aria-hidden={true}>
|
||||
<div
|
||||
@@ -619,12 +621,17 @@ export class TopSite extends React.PureComponent {
|
||||
isContextMenuOpen ? " active" : ""
|
||||
}`}
|
||||
title={title}
|
||||
setPref={this.props.setPref}
|
||||
tabIndex={this.props.tabIndex}
|
||||
onFocus={this.props.onFocus}
|
||||
>
|
||||
<div>
|
||||
<ContextMenuButton
|
||||
tooltip="newtab-menu-content-tooltip"
|
||||
tooltipArgs={{ title }}
|
||||
onUpdate={this.onMenuUpdate}
|
||||
tabIndex={this.props.tabIndex}
|
||||
onFocus={this.props.onFocus}
|
||||
>
|
||||
<LinkMenu
|
||||
dispatch={props.dispatch}
|
||||
@@ -676,7 +683,9 @@ export class TopSitePlaceholder extends React.PureComponent {
|
||||
className={`placeholder ${this.props.className || ""} ${
|
||||
this.props.isAddButton ? "add-button" : ""
|
||||
}`}
|
||||
setPref={this.props.setPref}
|
||||
isDraggable={false}
|
||||
tabIndex={this.props.tabIndex}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -690,6 +699,7 @@ export class _TopSiteList extends React.PureComponent {
|
||||
draggedSite: null,
|
||||
draggedTitle: null,
|
||||
topSitesPreview: null,
|
||||
focusedIndex: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -698,6 +708,10 @@ export class _TopSiteList extends React.PureComponent {
|
||||
this.state = _TopSiteList.DEFAULT_STATE;
|
||||
this.onDragEvent = this.onDragEvent.bind(this);
|
||||
this.onActivate = this.onActivate.bind(this);
|
||||
this.onWrapperFocus = this.onWrapperFocus.bind(this);
|
||||
this.onTopsiteFocus = this.onTopsiteFocus.bind(this);
|
||||
this.onWrapperBlur = this.onWrapperBlur.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
@@ -860,6 +874,44 @@ export class _TopSiteList extends React.PureComponent {
|
||||
this.setState({ activeIndex: index });
|
||||
}
|
||||
|
||||
onKeyDown(e) {
|
||||
if (this.state.activeIndex || this.state.activeIndex === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
// prevent the page from scrolling up/down while navigating.
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (
|
||||
this.focusedRef?.nextSibling?.querySelector("a") &&
|
||||
e.key === "ArrowDown"
|
||||
) {
|
||||
this.focusedRef.nextSibling.querySelector("a").tabIndex = 0;
|
||||
this.focusedRef.nextSibling.querySelector("a").focus();
|
||||
}
|
||||
if (
|
||||
this.focusedRef?.previousSibling?.querySelector("a") &&
|
||||
e.key === "ArrowUp"
|
||||
) {
|
||||
this.focusedRef.previousSibling.querySelector("a").tabIndex = 0;
|
||||
this.focusedRef.previousSibling.querySelector("a").focus();
|
||||
}
|
||||
}
|
||||
|
||||
onWrapperFocus() {
|
||||
this.focusRef?.addEventListener("keydown", this.onKeyDown);
|
||||
}
|
||||
onWrapperBlur() {
|
||||
this.focusRef?.removeEventListener("keydown", this.onKeyDown);
|
||||
}
|
||||
onTopsiteFocus(focusIndex) {
|
||||
this.setState(() => ({
|
||||
focusedIndex: focusIndex,
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { props } = this;
|
||||
const prefs = props.Prefs.values;
|
||||
@@ -909,6 +961,17 @@ export class _TopSiteList extends React.PureComponent {
|
||||
{...slotProps}
|
||||
{...commonProps}
|
||||
isAddButton={topSites[i] && topSites[i].isAddButton}
|
||||
setRef={
|
||||
i === this.state.focusedIndex
|
||||
? el => {
|
||||
this.focusedRef = el;
|
||||
}
|
||||
: () => {}
|
||||
}
|
||||
tabIndex={i === this.state.focusedIndex ? 0 : -1}
|
||||
onFocus={() => {
|
||||
this.onTopsiteFocus(i);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -922,6 +985,17 @@ export class _TopSiteList extends React.PureComponent {
|
||||
{...commonProps}
|
||||
colors={props.colors}
|
||||
shortcutsRefresh={shortcutsRefresh}
|
||||
setRef={
|
||||
i === this.state.focusedIndex
|
||||
? el => {
|
||||
this.focusedRef = el;
|
||||
}
|
||||
: () => {}
|
||||
}
|
||||
tabIndex={i === this.state.focusedIndex ? 0 : -1}
|
||||
onFocus={() => {
|
||||
this.onTopsiteFocus(i);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -931,6 +1005,13 @@ export class _TopSiteList extends React.PureComponent {
|
||||
return (
|
||||
<div className="top-sites-list-wrapper">
|
||||
<ul
|
||||
role="group"
|
||||
aria-label="Shortcuts"
|
||||
onFocus={this.onWrapperFocus}
|
||||
onBlur={this.onWrapperBlur}
|
||||
ref={el => {
|
||||
this.focusRef = el;
|
||||
}}
|
||||
className={`top-sites-list${
|
||||
this.state.draggedSite ? " dnd-active" : ""
|
||||
}`}
|
||||
|
||||
@@ -2265,7 +2265,9 @@ class ContextMenuButton extends (external_React_default()).PureComponent {
|
||||
className: "context-menu-button icon",
|
||||
onKeyDown: this.onKeyDown,
|
||||
onClick: this.onClick,
|
||||
ref: refFunction
|
||||
ref: refFunction,
|
||||
tabIndex: this.props.tabIndex || 0,
|
||||
onFocus: this.props.onFocus
|
||||
}), showContextMenu ? /*#__PURE__*/external_React_default().cloneElement(children, {
|
||||
keyboardAccess: contextMenuKeyboard,
|
||||
onUpdate: this.onUpdate
|
||||
@@ -8081,18 +8083,20 @@ class TopSiteLink extends (external_React_default()).PureComponent {
|
||||
onDrop: this.onDragEvent,
|
||||
onDragOver: this.onDragEvent,
|
||||
onDragEnter: this.onDragEvent,
|
||||
onDragLeave: this.onDragEvent
|
||||
onDragLeave: this.onDragEvent,
|
||||
ref: this.props.setRef
|
||||
}, draggableProps), /*#__PURE__*/external_React_default().createElement("div", {
|
||||
className: "top-site-inner"
|
||||
}, /*#__PURE__*/external_React_default().createElement("a", {
|
||||
className: "top-site-button",
|
||||
href: link.searchTopSite ? undefined : link.url,
|
||||
tabIndex: "0",
|
||||
tabIndex: this.props.tabIndex,
|
||||
onKeyPress: this.onKeyPress,
|
||||
onClick: onClick,
|
||||
draggable: true,
|
||||
"data-is-sponsored-link": !!link.sponsored_tile_id,
|
||||
title: title
|
||||
title: title,
|
||||
onFocus: this.props.onFocus
|
||||
}, /*#__PURE__*/external_React_default().createElement("div", {
|
||||
className: "tile",
|
||||
"aria-hidden": true
|
||||
@@ -8312,13 +8316,18 @@ class TopSite extends (external_React_default()).PureComponent {
|
||||
onClick: this.onLinkClick,
|
||||
onDragEvent: this.props.onDragEvent,
|
||||
className: `${props.className || ""}${isContextMenuOpen ? " active" : ""}`,
|
||||
title: title
|
||||
title: title,
|
||||
setPref: this.props.setPref,
|
||||
tabIndex: this.props.tabIndex,
|
||||
onFocus: this.props.onFocus
|
||||
}), /*#__PURE__*/external_React_default().createElement("div", null, /*#__PURE__*/external_React_default().createElement(ContextMenuButton, {
|
||||
tooltip: "newtab-menu-content-tooltip",
|
||||
tooltipArgs: {
|
||||
title
|
||||
},
|
||||
onUpdate: this.onMenuUpdate
|
||||
onUpdate: this.onMenuUpdate,
|
||||
tabIndex: this.props.tabIndex,
|
||||
onFocus: this.props.onFocus
|
||||
}, /*#__PURE__*/external_React_default().createElement(LinkMenu, {
|
||||
dispatch: props.dispatch,
|
||||
index: props.index,
|
||||
@@ -8360,7 +8369,9 @@ class TopSitePlaceholder extends (external_React_default()).PureComponent {
|
||||
...addButtonProps
|
||||
} : {}, {
|
||||
className: `placeholder ${this.props.className || ""} ${this.props.isAddButton ? "add-button" : ""}`,
|
||||
isDraggable: false
|
||||
setPref: this.props.setPref,
|
||||
isDraggable: false,
|
||||
tabIndex: this.props.tabIndex
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -8371,7 +8382,8 @@ class _TopSiteList extends (external_React_default()).PureComponent {
|
||||
draggedIndex: null,
|
||||
draggedSite: null,
|
||||
draggedTitle: null,
|
||||
topSitesPreview: null
|
||||
topSitesPreview: null,
|
||||
focusedIndex: 0
|
||||
};
|
||||
}
|
||||
constructor(props) {
|
||||
@@ -8379,6 +8391,10 @@ class _TopSiteList extends (external_React_default()).PureComponent {
|
||||
this.state = _TopSiteList.DEFAULT_STATE;
|
||||
this.onDragEvent = this.onDragEvent.bind(this);
|
||||
this.onActivate = this.onActivate.bind(this);
|
||||
this.onWrapperFocus = this.onWrapperFocus.bind(this);
|
||||
this.onTopsiteFocus = this.onTopsiteFocus.bind(this);
|
||||
this.onWrapperBlur = this.onWrapperBlur.bind(this);
|
||||
this.onKeyDown = this.onKeyDown.bind(this);
|
||||
}
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.state.draggedSite) {
|
||||
@@ -8520,6 +8536,34 @@ class _TopSiteList extends (external_React_default()).PureComponent {
|
||||
activeIndex: index
|
||||
});
|
||||
}
|
||||
onKeyDown(e) {
|
||||
if (this.state.activeIndex || this.state.activeIndex === 0) {
|
||||
return;
|
||||
}
|
||||
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
||||
// prevent the page from scrolling up/down while navigating.
|
||||
e.preventDefault();
|
||||
}
|
||||
if (this.focusedRef?.nextSibling?.querySelector("a") && e.key === "ArrowDown") {
|
||||
this.focusedRef.nextSibling.querySelector("a").tabIndex = 0;
|
||||
this.focusedRef.nextSibling.querySelector("a").focus();
|
||||
}
|
||||
if (this.focusedRef?.previousSibling?.querySelector("a") && e.key === "ArrowUp") {
|
||||
this.focusedRef.previousSibling.querySelector("a").tabIndex = 0;
|
||||
this.focusedRef.previousSibling.querySelector("a").focus();
|
||||
}
|
||||
}
|
||||
onWrapperFocus() {
|
||||
this.focusRef?.addEventListener("keydown", this.onKeyDown);
|
||||
}
|
||||
onWrapperBlur() {
|
||||
this.focusRef?.removeEventListener("keydown", this.onKeyDown);
|
||||
}
|
||||
onTopsiteFocus(focusIndex) {
|
||||
this.setState(() => ({
|
||||
focusedIndex: focusIndex
|
||||
}));
|
||||
}
|
||||
render() {
|
||||
const {
|
||||
props
|
||||
@@ -8558,7 +8602,14 @@ class _TopSiteList extends (external_React_default()).PureComponent {
|
||||
if (!link || props.App.isForStartupCache && isSponsored(link) || topSites[i]?.isAddButton) {
|
||||
if (link) {
|
||||
topSiteLink = /*#__PURE__*/external_React_default().createElement(TopSitePlaceholder, TopSite_extends({}, slotProps, commonProps, {
|
||||
isAddButton: topSites[i] && topSites[i].isAddButton
|
||||
isAddButton: topSites[i] && topSites[i].isAddButton,
|
||||
setRef: i === this.state.focusedIndex ? el => {
|
||||
this.focusedRef = el;
|
||||
} : () => {},
|
||||
tabIndex: i === this.state.focusedIndex ? 0 : -1,
|
||||
onFocus: () => {
|
||||
this.onTopsiteFocus(i);
|
||||
}
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
@@ -8568,7 +8619,14 @@ class _TopSiteList extends (external_React_default()).PureComponent {
|
||||
onActivate: this.onActivate
|
||||
}, slotProps, commonProps, {
|
||||
colors: props.colors,
|
||||
shortcutsRefresh: shortcutsRefresh
|
||||
shortcutsRefresh: shortcutsRefresh,
|
||||
setRef: i === this.state.focusedIndex ? el => {
|
||||
this.focusedRef = el;
|
||||
} : () => {},
|
||||
tabIndex: i === this.state.focusedIndex ? 0 : -1,
|
||||
onFocus: () => {
|
||||
this.onTopsiteFocus(i);
|
||||
}
|
||||
}));
|
||||
}
|
||||
topSitesUI.push(topSiteLink);
|
||||
@@ -8576,6 +8634,13 @@ class _TopSiteList extends (external_React_default()).PureComponent {
|
||||
return /*#__PURE__*/external_React_default().createElement("div", {
|
||||
className: "top-sites-list-wrapper"
|
||||
}, /*#__PURE__*/external_React_default().createElement("ul", {
|
||||
role: "group",
|
||||
"aria-label": "Shortcuts",
|
||||
onFocus: this.onWrapperFocus,
|
||||
onBlur: this.onWrapperBlur,
|
||||
ref: el => {
|
||||
this.focusRef = el;
|
||||
},
|
||||
className: `top-sites-list${this.state.draggedSite ? " dnd-active" : ""}`
|
||||
}, topSitesUI));
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ add_task(async function test_newtab_last_LinkMenu() {
|
||||
content.document.querySelector(
|
||||
".top-site-outer:nth-child(2n) .context-menu-button"
|
||||
),
|
||||
"Wait for the Pocket card and button"
|
||||
"Wait for the topsite card and button"
|
||||
);
|
||||
const topsiteOuter = content.document.querySelector(
|
||||
".top-site-outer:nth-child(2n)"
|
||||
@@ -105,7 +105,7 @@ add_task(async function test_newtab_last_LinkMenu() {
|
||||
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => topsiteOuter.classList.contains("active"),
|
||||
"Wait for the menu to be active"
|
||||
"Wait for the topsite menu to be active"
|
||||
);
|
||||
|
||||
is(
|
||||
@@ -124,7 +124,7 @@ add_task(async function test_newtab_last_LinkMenu() {
|
||||
content.document.querySelector(
|
||||
".ds-card:nth-child(1n) .context-menu-button"
|
||||
),
|
||||
"Wait for the Pocket card and button"
|
||||
"Wait for the story card and button"
|
||||
);
|
||||
|
||||
const dsCard = content.document.querySelector(".ds-card:nth-child(1n)");
|
||||
@@ -134,7 +134,7 @@ add_task(async function test_newtab_last_LinkMenu() {
|
||||
|
||||
await ContentTaskUtils.waitForCondition(
|
||||
() => dsCard.classList.contains("active"),
|
||||
"Wait for the menu to be active"
|
||||
"Wait for the story menu to be active"
|
||||
);
|
||||
|
||||
is(
|
||||
|
||||
Reference in New Issue
Block a user