diff --git a/chrome/content/scaffold/scaffold.js b/chrome/content/scaffold/scaffold.js
index 08b79a1948..f5d7d1f8dc 100644
--- a/chrome/content/scaffold/scaffold.js
+++ b/chrome/content/scaffold/scaffold.js
@@ -103,6 +103,15 @@ var Scaffold = new function () {
});
document.getElementById('tabpanels').addEventListener('select', event => Scaffold.handleTabSelect(event));
+ document.getElementById('tabs').addEventListener('mousedown', (event) => {
+ // Record if tab selection will happen due to a mouse click vs keyboard nav.
+ if (event.clientX === 0 && event.clientY === 0) return;
+ document.getElementById('tabs').setAttribute("clicked", true);
+ }, true);
+ // Record that click has happened for better focus-ring handling in the stylesheet
+ document.addEventListener("mouseup", (_) => {
+ document.getElementById('tabs').removeAttribute("clicked");
+ });
let lastTranslatorID = Zotero.Prefs.get('scaffold.lastTranslatorID');
if (lastTranslatorID) {
@@ -157,6 +166,10 @@ var Scaffold = new function () {
this.initCodeEditor();
this.initTestsEditor();
+ this.addEditorKeydownHandlers(_editors.import);
+ this.addEditorKeydownHandlers(_editors.code);
+ this.addEditorKeydownHandlers(_editors.tests);
+
// Set font size from general pref
Zotero.UIProperties.registerRoot(document.getElementById('scaffold-pane'));
@@ -864,24 +877,31 @@ var Scaffold = new function () {
return;
}
- // Focus editor when switching to tab
var tab = document.getElementById('tabs').selectedItem.id.match(/^tab-(.+)$/)[1];
- switch (tab) {
- case 'import':
- case 'code':
- case 'tests':
- // the select event's default behavior is to focus the selected tab.
- // we don't want to prevent *all* of the event's default behavior,
- // but we do want to focus the editor instead of the tab.
- // so this stupid hack waits 10 ms for event processing to finish
- // before focusing the editor.
- setTimeout(() => {
- document.getElementById(`editor-${tab}`).focus();
- _editors[tab].focus();
- }, 10);
- break;
+ let tabPanel = document.getElementById("left-tabbox").selectedPanel;
+ // The select event's default behavior is to focus the selected tab.
+ // we don't want to prevent *all* of the event's default behavior,
+ // but we do want to focus an element inside of tabpanel instead of the tab
+ // (unless tabs are being navigated via keyboard)
+ // so this stupid hack focuses the desired element after skipping a tick
+ if (document.getElementById('tabs').getAttribute("clicked")) {
+ setTimeout(() => {
+ let toFocus = tabPanel.querySelector("[focus-on-tab-select]");
+ if (toFocus) {
+ toFocus.focus();
+ // activate editor that is being focused, if any
+ if (toFocus.src.includes("monaco.html")) {
+ _editors[tab].focus();
+ }
+ }
+ else {
+ // if no specific element set, just tab into the panel
+ setTimeout(() => {
+ Services.focus.moveFocus(window, document.getElementById('tabs').selectedItem, Services.focus.MOVEFOCUS_FORWARD, 0);
+ });
+ }
+ });
}
-
let codeTabBroadcaster = document.getElementById('code-tab-only');
if (tab == 'code') {
codeTabBroadcaster.removeAttribute('disabled');
@@ -907,6 +927,31 @@ var Scaffold = new function () {
}
};
+ // Add special keydown handling for the editors
+ this.addEditorKeydownHandlers = function (editor) {
+ let doc = editor.getDomNode().ownerDocument;
+ let tabbox = document.getElementById("left-tabbox");
+ // On shift-tab from the start of the first line, tab out of the editor.
+ // Use capturing listener, since Shift-Tab keydown events do not propagate to the document.
+ doc.addEventListener("keydown", (event) => {
+ if (event.key == "Tab" && event.shiftKey) {
+ let position = editor.getPosition();
+ if (position.column == 1 && position.lineNumber == 1) {
+ Services.focus.moveFocus(window, event.target, Services.focus.MOVEFOCUS_BACKWARD, 0);
+ event.preventDefault();
+ }
+ }
+ }, true);
+ // On Escape, focus the selected tab. Use non-capturing listener to not
+ // do anything on Escape events handled by the editor (e.g. to dismiss autocomplete popup)
+ doc.addEventListener("keydown", (event) => {
+ if (event.key == "Escape") {
+ tabbox.selectedTab.focus();
+ }
+ });
+ };
+
+
this.listFieldsForItemType = function (itemType) {
var outputObject = {};
outputObject.itemType = Zotero.ItemTypes.getName(itemType);
diff --git a/chrome/content/scaffold/scaffold.xhtml b/chrome/content/scaffold/scaffold.xhtml
index ce9224326f..e2b8ae08c0 100644
--- a/chrome/content/scaffold/scaffold.xhtml
+++ b/chrome/content/scaffold/scaffold.xhtml
@@ -468,7 +468,7 @@
-
+
@@ -490,6 +490,7 @@
seltype="multiple"
onselect="Scaffold.handleTestSelect(event)"
context="testing-context-menu"
+ focus-on-tab-select="true"
/>
@@ -533,7 +534,7 @@
-
+
diff --git a/scss/scaffold.scss b/scss/scaffold.scss
index 1bfd690ecb..677c5c91cc 100644
--- a/scss/scaffold.scss
+++ b/scss/scaffold.scss
@@ -82,7 +82,8 @@ browser,
#left-tabbox {
flex: 1;
min-width: 500px;
- margin: 5px;
+ margin: 0 5px 5px 5px;
+ padding-top: 5px; // padding at the top for focus-ring of tabs to not cutoff tabs focusring
overflow: clip;
tabpanel {
@@ -212,3 +213,9 @@ browser,
}
}
}
+
+// show focusring only during keyboard navigation
+#tabs:not([clicked]) tab:focus-visible {
+ outline: none;
+ box-shadow: 0 0 0 var(--width-focus-border) var(--color-focus-border);
+}
\ No newline at end of file