vpat 66-68: fix focus within tagsBox popup (#4231)

- focus will always enter the tagsBox popup opened from the reader
- escape from within the popup with tagsBox will close the popup (vpat
  67)
- one should not be able to collapse the tagsBox. Added `collapsible`
  getter and setters to the collapsible panel to prevent the section
  from changing its open status. This may be also used in other cases,
  such as to prevent itemBox from being collapsed in duplicates mode.
- patched a glitch where tab from the last empty tab input would loose
  focus.
- changed the `menupopup` for `panel` to display the `tagsbox` because
  `menupopup` has implicit `role="menu"` which is not meant to contain
  inputs, so voiceover completely looses cursor when inputs are focused
  inside of the popup. Also, tweaked spacing a bit to avoid the
  focus-ring getting cutoff.

Addresses: #4222
Fixes: #4230
Fixes: #4226
Addresses: #4388
This commit is contained in:
Bogdan Abaev 2024-06-12 00:26:42 -04:00 committed by Dan Stillman
parent f227aeb6e0
commit a73035c848
5 changed files with 60 additions and 19 deletions

View file

@ -43,7 +43,7 @@
set open(newOpen) { set open(newOpen) {
newOpen = !!newOpen; newOpen = !!newOpen;
let oldOpen = this.open; let oldOpen = this.open;
if (oldOpen === newOpen || this.empty) return; if (oldOpen === newOpen || this.empty || !this.collapsible) return;
this.render(); this.render();
// Force open before getting scrollHeight, so we get the right value // Force open before getting scrollHeight, so we get the right value
@ -106,6 +106,19 @@
this.setAttribute('summary', val); this.setAttribute('summary', val);
} }
get collapsible() {
return !this.getAttribute("no-collapse");
}
set collapsible(val) {
if (val) {
this.removeAttribute('no-collapse');
}
else {
this.setAttribute('no-collapse', val);
}
}
static get observedAttributes() { static get observedAttributes() {
return ['open', 'empty', 'label', 'summary', 'extra-buttons']; return ['open', 'empty', 'label', 'summary', 'extra-buttons'];
} }

View file

@ -85,6 +85,14 @@ class ItemPaneSectionElementBase extends XULElementBase {
} }
} }
get collapsible() {
return this._section.collapsible;
}
set collapsible(val) {
this._section.collapsible = !!val;
}
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback();
if (!this.render && !this.asyncRender) { if (!this.render && !this.asyncRender) {

View file

@ -342,6 +342,18 @@
focusField.focus(); focusField.focus();
} }
} }
else if (event.key == "Tab" && !event.shiftKey) {
// On tab from the last empty tag row, the minus icon will be focused
// and the row will be immediately removed in this.saveTag, so focus will be lost.
// To avoid that, on tab from the last tag input that is empty, focus the next
// element after the tag row.
let allTags = [...this.querySelectorAll(".row")];
let isLastTag = target.closest(".row") == allTags[allTags.length - 1];
if (isLastTag && !target.closest("editable-text").value.length) {
Services.focus.moveFocus(window, target.closest(".row").lastChild, Services.focus.MOVEFOCUS_FORWARD, 0);
event.preventDefault();
}
}
}; };
// Intercept paste, check for newlines, and convert textbox // Intercept paste, check for newlines, and convert textbox

View file

@ -896,30 +896,43 @@ class ReaderInstance {
} }
_openTagsPopup(item, x, y) { _openTagsPopup(item, x, y) {
let menupopup = this._window.document.createXULElement('menupopup'); let tagsPopup = this._window.document.createXULElement('panel');
menupopup.addEventListener('popuphidden', function (event) { tagsPopup.addEventListener('popuphidden', function (event) {
if (event.target === menupopup) { if (event.target === tagsPopup) {
menupopup.remove(); tagsPopup.remove();
} }
}); });
menupopup.className = 'tags-popup'; tagsPopup.addEventListener('keydown', function (event) {
menupopup.setAttribute('ignorekeys', true); if (event.key == "Escape") {
tagsPopup.hidePopup();
}
});
tagsPopup.className = 'tags-popup';
let tagsbox = this._window.document.createXULElement('tags-box'); let tagsbox = this._window.document.createXULElement('tags-box');
menupopup.appendChild(tagsbox); tagsPopup.appendChild(tagsbox);
tagsbox.setAttribute('flex', '1'); tagsbox.setAttribute('flex', '1');
this._popupset.appendChild(menupopup); this._popupset.appendChild(tagsPopup);
let rect = this._iframe.getBoundingClientRect(); let rect = this._iframe.getBoundingClientRect();
x += rect.left; x += rect.left;
y += rect.top; y += rect.top;
tagsbox.editable = true; tagsbox.editable = true;
tagsbox.item = item; tagsbox.item = item;
tagsbox.render(); tagsbox.render();
menupopup.openPopup(null, 'before_start', x, y, true); // remove unnecessary tabstop from the section header
setTimeout(() => { tagsbox.querySelector(".head").removeAttribute("tabindex");
tagsPopup.addEventListener("popupshown", (_) => {
// Ensure tagsbox is open
tagsbox.open = true;
if (tagsbox.count == 0) { if (tagsbox.count == 0) {
tagsbox.newTag(); tagsbox.newTag();
} }
else {
// Focus + button
Services.focus.setFocus(tagsbox.querySelector("toolbarbutton"), Services.focus.FLAG_NOSHOWRING);
}
tagsbox.collapsible = false;
}); });
tagsPopup.openPopup(null, 'before_start', x, y, true);
} }
async _openContextMenu({ x, y, itemGroups }) { async _openContextMenu({ x, y, itemGroups }) {

View file

@ -1,12 +1,7 @@
menupopup.tags-popup { .tags-popup {
font: inherit; font: inherit;
min-width: 300px; min-width: 300px;
padding-inline: 8px;
@media (-moz-platform: windows) {
& > tags-box { & > tags-box {
// padding-inline does not work on Windows padding-inline: 8px;
margin-inline: 8px;
}
} }
} }