vpat 44: scaffold keyboard tab selection focus (#4069)
Do not move focus from the tab onto the editor/input during keyboard navigation to not change context per https://www.w3.org/WAI/WCAG21/Understanding/on-input. Focus will still shift if tab selection changed on mouse click. Also: - added focus ring to tabs. Additional mouseup handling to prevent the focus ring from briefly appearing on click. - on Escape from within the editor, focus the current tab. - on shift-tab from the beginning of the editor, tab out of the editor to previous element.
This commit is contained in:
parent
15ccf28fb4
commit
c14896a640
3 changed files with 72 additions and 19 deletions
|
@ -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);
|
||||
|
|
|
@ -468,7 +468,7 @@
|
|||
</tabpanel>
|
||||
<tabpanel flex="1" id="tabpanel-code">
|
||||
<vbox flex="1">
|
||||
<iframe src="monaco/monaco.html" id="editor-code" flex="1" onmousedown="this.focus()"/>
|
||||
<iframe src="monaco/monaco.html" id="editor-code" focus-on-tab-select="true" flex="1" onmousedown="this.focus()"/>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
<tabpanel flex="1" id="tabpanel-tests">
|
||||
|
@ -490,6 +490,7 @@
|
|||
seltype="multiple"
|
||||
onselect="Scaffold.handleTestSelect(event)"
|
||||
context="testing-context-menu"
|
||||
focus-on-tab-select="true"
|
||||
/>
|
||||
</vbox>
|
||||
<hbox id="testing-buttons">
|
||||
|
@ -533,7 +534,7 @@
|
|||
<button observes="validate-tests" label="&scaffold.testing.create.import;" tooltiptext="Create a new test from the current import data" oncommand="Scaffold.saveTestFromCurrent('import')" />
|
||||
<button observes="validate-tests" label="&scaffold.testing.create.search;" tooltiptext="Create a new test from the current search data" oncommand="Scaffold.saveTestFromCurrent('search')" />
|
||||
</hbox>
|
||||
<iframe src="monaco/monaco.html" id="editor-import" flex="1" onmousedown="this.focus()"/>
|
||||
<iframe src="monaco/monaco.html" id="editor-import" flex="1" focus-on-tab-select="true" onmousedown="this.focus()"/>
|
||||
</vbox>
|
||||
</tabpanel>
|
||||
</tabpanels>
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Add table
Reference in a new issue