tabs menu update and minor refactoring

- tabs menu button is disabled when no reader tabs are opened
- tabs menu popup will be hidden if all tabs are closed
- keypress event handling moved from tabBar.jsx to zoteroPane.js
because all other keyboard navigation events are handled there
and it already has the functionality to skip disabled or hidden components
(e.g. tabs menu button, sync error)
- minor tweaks to tests to wait for collection search bar to hide
to get keyboard navigation tests passing
This commit is contained in:
abaevbog 2024-01-04 05:54:22 -05:00 committed by Dan Stillman
parent a2e8294389
commit 8c0116d1db
4 changed files with 52 additions and 51 deletions

View file

@ -273,46 +273,6 @@ const TabBar = forwardRef(function (props, ref) {
event.preventDefault();
}
function handleKeyDown(event) {
event = event.nativeEvent;
let key = event.key;
if (key == "Tab") {
if (event.shiftKey) {
// On shift-tab, wrap focus back to itemTree/itemPane
props.wrapFocusAround();
}
else {
// On tab go back to opened tabs menu
document.getElementById('zotero-tb-tabs-menu').focus();
}
event.preventDefault();
event.stopPropagation();
}
// Move focus between tabs with arrows
if (["ArrowLeft", "ArrowRight"].includes(key)) {
let direction = key == "ArrowLeft" ? "left" : "right";
const cmdOrCtrlOnly = Zotero.isMac
? (event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey)
: (event.ctrlKey && !event.shiftKey && !event.altKey);
// Ctrl/CMD and an arrow shifts focus
if (cmdOrCtrlOnly) {
props.moveFocus(direction);
}
// Just an arrow selects the tab
else if (direction == "left") {
props.selectPrev({ keepTabFocused: true });
}
else {
props.selectNext({ keepTabFocused: true });
};
}
// Select focused tab on space or enter and focus on content pane
if (key == " " || key == "Enter") {
let tabID = event.target.getAttribute('data-id');
props.onTabSelect(tabID, false, { keepTabFocused: false });
}
}
function renderTab({ id, title, selected, icon }, index) {
return (
<div
@ -326,7 +286,6 @@ const TabBar = forwardRef(function (props, ref) {
onAuxClick={(event) => handleTabClick(event, id)}
onDragStart={(event) => handleDragStart(event, id, index)}
onDragEnd={handleDragEnd}
onKeyDown={handleKeyDown}
tabIndex="-1"
>
{icon}

View file

@ -136,6 +136,12 @@ var Zotero_Tabs = new function () {
focusTabsMenuEntry();
}
}
// Disable tabs menu button when no reader tabs are present
document.getElementById("zotero-tb-tabs-menu").disabled = this._tabs.length == 1;
// Close tabs menu if all tabs are closed
if (this._tabs.length == 1 && this.isTabsMenuVisible()) {
this.tabsMenuPanel.hidePopup();
}
};
this.getTabIDByItemID = function (itemID) {
@ -157,10 +163,6 @@ var Zotero_Tabs = new function () {
onTabMove={this.move.bind(this)}
onTabClose={this.close.bind(this)}
onContextMenu={this._openMenu.bind(this)}
moveFocus={this.moveFocus.bind(this)}
wrapFocusAround={this.focusWrapAround.bind(this)}
selectNext={this.selectNext.bind(this)}
selectPrev={this.selectPrev.bind(this)}
/>,
document.getElementById('tab-bar-container'),
() => {

View file

@ -150,7 +150,7 @@ var ZoteroPane = new function()
function setUpKeyboardNavigation() {
let collectionTreeToolbar = this.document.getElementById("zotero-toolbar-collection-tree");
let itemTreeToolbar = this.document.getElementById("zotero-toolbar-item-tree");
let tabsToolbar = this.document.getElementById("zotero-tabs-toolbar");
let titleBar = this.document.getElementById("zotero-title-bar");
let itemTree = this.document.getElementById("zotero-items-tree");
let collectionsTree = this.document.getElementById("zotero-collections-tree");
let tagSelector = this.document.getElementById("zotero-tag-selector");
@ -181,14 +181,22 @@ var ZoteroPane = new function()
else if (verticalArrowIsTab && key == 'ArrowDown' && !onInput) {
key = 'Tab';
}
// Fetch the focusFunction by target id
let focusFunction = actionsMap[event.target.id]?.[key];
// If no function found by target id, try to search by class names
if (focusFunction === undefined) {
for (let className of event.target.classList) {
focusFunction = actionsMap[className]?.[key];
if (focusFunction) break;
}
}
// If the focusFunction is undefined, nothing was found
// for this combination of keys, so do nothing
if (focusFunction === undefined) {
return;
}
// Otherwise, fetch the target to focus on
let target = focusFunction();
let target = focusFunction(event);
// If returned target is false, focusing was not handled,
// so fallback to default focus target
if (target === false) {
@ -218,7 +226,9 @@ var ZoteroPane = new function()
event.stopPropagation();
};
tabsToolbar.addEventListener("keydown", (event) => {
titleBar.addEventListener("keydown", (event) => {
let cmdOrCtrlOnly = e => (Zotero.isMac ? (e.metaKey && !e.ctrlKey) : e.ctrlKey) && !e.shiftKey && !e.altKey;
// Mapping of target ids and possible key presses to desired focus outcomes
let actionsMap = {
'zotero-tb-tabs-menu': {
@ -249,6 +259,33 @@ var ZoteroPane = new function()
.dispatchEvent(new MouseEvent("click", { target: event.target })),
' ': () => document.getElementById("zotero-tb-sync-error")
.dispatchEvent(new MouseEvent("click", { target: event.target }))
},
tab: {
// keyboard navigation for tabs. 'tab' is the class, not the id
Tab: () => document.getElementById('zotero-tb-tabs-menu'),
ShiftTab: Zotero_Tabs.focusWrapAround,
ArrowRight: (e) => {
if (cmdOrCtrlOnly(e)) {
Zotero_Tabs.moveFocus("right");
}
else {
Zotero_Tabs.selectNext({ keepTabFocused: true });
}
},
ArrowLeft: (e) => {
if (cmdOrCtrlOnly(e)) {
Zotero_Tabs.moveFocus("left");
}
else {
Zotero_Tabs.selectPrev({ keepTabFocused: true });
}
},
Enter: (e) => {
Zotero_Tabs.select(e.target.getAttribute('data-id'), false, { keepTabFocused: false });
},
' ': (e) => {
Zotero_Tabs.select(e.target.getAttribute('data-id'), false, { keepTabFocused: false });
}
}
};
moveFocus(actionsMap, event, true);

View file

@ -1533,11 +1533,14 @@ describe("ZoteroPane", function() {
doc.activeElement.dispatchEvent(shiftTab);
// Wait for collection search to be revealed
if (id === "zotero-collections-search") {
await Zotero.Promise.delay(300);
await Zotero.Promise.delay(250);
}
assert.equal(doc.activeElement.id, id);
// Wait for collection search to be hidden for subsequent tests
if (id === "zotero-tb-collection-add") {
await Zotero.Promise.delay(50);
}
}
doc.activeElement.dispatchEvent(shiftTab);
assert.equal(doc.activeElement.className, "tab selected");
@ -1561,7 +1564,7 @@ describe("ZoteroPane", function() {
doc.activeElement.dispatchEvent(tab);
// Wait for collection search to be revealed
if (id === "zotero-collections-search") {
await Zotero.Promise.delay(300);
await Zotero.Promise.delay(250);
}
assert.equal(doc.activeElement.id, id);
}