This commit is contained in:
Minh Ha Duong 2026-09-26 12:24:07 +08:00 • committed by GitHub
commit 03c9c0be19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 99 additions and 2 deletions

View file

@ -174,6 +174,11 @@ var ZoteroPane = new function () {
function setUpKeyboardNavigation() {
let collectionTreeToolbar = document.getElementById("zotero-toolbar-collection-tree");
let itemTreeToolbar = document.getElementById("zotero-toolbar-item-tree");
// Plugins can opt into keyboard focus with tabindex=0 on an appended
// toolbarbutton. Read the current buttons for each key event.
let pluginButtons = () => [...document.querySelectorAll(
'#zotero-items-toolbar > toolbarbutton[tabindex="0"]'
)];
let titleBar = document.getElementById("zotero-title-bar");
let itemTree = document.getElementById("zotero-items-tree");
let collectionsTree = document.getElementById("zotero-collections-tree");
@ -367,6 +372,11 @@ var ZoteroPane = new function () {
});
itemTreeToolbar.addEventListener("keydown", (event) => {
let adjacentPluginButton = (button, direction) => {
let buttons = pluginButtons();
let index = buttons.indexOf(button);
return index === -1 ? null : buttons[index + direction];
};
let actionsMap = {
'zotero-tb-add': {
ArrowNext: () => document.getElementById("zotero-tb-lookup"),
@ -420,10 +430,20 @@ var ZoteroPane = new function () {
ShiftTab: () => document.getElementById("zotero-tb-search-textbox")
},
'zotero-tb-toggle-item-pane-stacked': {
Tab: () => itemTree.querySelector(".virtualized-table"),
Tab: () => pluginButtons()[0] || itemTree.querySelector(".virtualized-table"),
ShiftTab: () => document.getElementById("zotero-tb-search-advanced-button")
},
};
if (pluginButtons().includes(event.target)) {
actionsMap[event.target.id] = {
ArrowNext: () => adjacentPluginButton(event.target, 1) || null,
ArrowPrevious: () => adjacentPluginButton(event.target, -1) || null,
Tab: () => adjacentPluginButton(event.target, 1)
|| itemTree.querySelector(".virtualized-table"),
ShiftTab: () => adjacentPluginButton(event.target, -1)
|| document.getElementById('zotero-tb-toggle-item-pane-stacked')
};
}
moveFocus(actionsMap, event, true);
});
@ -468,7 +488,8 @@ var ZoteroPane = new function () {
if (advancedSearchDeck?.state === 'collapsed') {
return document.querySelector('#zotero-tb-search .advanced-close-button');
}
return document.getElementById('zotero-tb-toggle-item-pane-stacked');
return pluginButtons().at(-1)
|| document.getElementById('zotero-tb-toggle-item-pane-stacked');
}
}
};

View file

@ -1898,6 +1898,82 @@ describe("ZoteroPane", function () {
doc.activeElement.dispatchEvent(leftArrow);
assert.equal(doc.activeElement.id, "zotero-tb-add");
});
it("should include plugin toolbarbuttons in the Tab and arrow routes", async function () {
if (!await activatePaneWindow()) {
this.skip();
}
let toolbar = doc.getElementById('zotero-items-toolbar');
let buttons = ['first', 'second'].map(name => {
let button = doc.createXULElement('toolbarbutton');
button.id = `test-plugin-${name}`;
button.setAttribute('tabindex', '0');
toolbar.append(button);
return button;
});
try {
// The plugin buttons are appended after the search and pane-toggle
// controls, so keyboard traversal must reach them in that order.
doc.getElementById('zotero-tb-add').focus();
doc.activeElement.dispatchEvent(tab);
assert.equal(doc.activeElement.id, 'zotero-tb-search-textbox');
let paneToggle = doc.getElementById('zotero-tb-toggle-item-pane-stacked');
paneToggle.dispatchEvent(tab);
assert.equal(doc.activeElement, buttons[0]);
doc.activeElement.dispatchEvent(tab);
assert.equal(doc.activeElement, buttons[1]);
doc.activeElement.dispatchEvent(tab);
assert.equal(doc.activeElement,
doc.getElementById('zotero-items-tree').querySelector('.virtualized-table'));
doc.activeElement.dispatchEvent(shiftTab);
assert.equal(doc.activeElement, buttons[1]);
doc.activeElement.dispatchEvent(shiftTab);
assert.equal(doc.activeElement, buttons[0]);
doc.activeElement.dispatchEvent(shiftTab);
let toggleHidden = paneToggle.disabled || paneToggle.hidden
|| paneToggle.parentNode.hidden
|| win.getComputedStyle(paneToggle).display === 'none';
assert.equal(doc.activeElement.id, toggleHidden
? 'zotero-tb-search-advanced-button' : paneToggle.id);
doc.getElementById('zotero-tb-lookup').focus();
doc.activeElement.dispatchEvent(tab);
assert.equal(doc.activeElement.id, 'zotero-tb-search-textbox');
doc.getElementById('zotero-tb-attachment-add').focus();
doc.activeElement.dispatchEvent(tab);
assert.equal(doc.activeElement.id, 'zotero-tb-search-textbox');
doc.getElementById('zotero-tb-note-add').focus();
doc.activeElement.dispatchEvent(rightArrow);
assert.equal(doc.activeElement.id, 'zotero-tb-note-add');
buttons[0].focus();
doc.activeElement.dispatchEvent(leftArrow);
assert.equal(doc.activeElement, buttons[0]);
doc.activeElement.dispatchEvent(rightArrow);
assert.equal(doc.activeElement, buttons[1]);
doc.activeElement.dispatchEvent(leftArrow);
assert.equal(doc.activeElement, buttons[0]);
buttons[0].hidden = true;
paneToggle.dispatchEvent(tab);
assert.equal(doc.activeElement, buttons[1]);
buttons[0].hidden = false;
buttons[0].disabled = true;
paneToggle.dispatchEvent(tab);
assert.equal(doc.activeElement, buttons[1]);
buttons[0].disabled = false;
buttons[0].style.display = 'none';
paneToggle.dispatchEvent(tab);
assert.equal(doc.activeElement, buttons[1]);
buttons[0].style.display = '';
buttons[0].disabled = true;
doc.getElementById('zotero-tb-add').focus();
buttons[0].dispatchEvent(tab);
assert.equal(doc.activeElement, buttons[1]);
}
finally {
buttons.forEach(button => button.remove());
}
});
});
describe("#addAttachmentFromDialog()", function () {