fix: focus the textarea in the Compose dialog (#2227)

ComposeBox already specified autoFocus for the ComposeInput.
However, this didn't work because the dialog was disabled when the ComposeInput code tried to focus the textarea.
To fix this, tweak A11yDialog to look for the autofocus attribute when determining what to focus.
This is consistent with the behavior for native HTML dialogs.
Then, have ComposeInput set this attribute if it's in a dialog.
Fixes #1839.
This commit is contained in:
James Teh 2022-11-24 02:21:42 +10:00 committed by GitHub
parent a447b9535e
commit 8792d912bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 20 additions and 4 deletions

View file

@ -117,7 +117,15 @@
firstTime = false firstTime = false
const { autoFocus } = this.get() const { autoFocus } = this.get()
if (autoFocus) { if (autoFocus) {
requestAnimationFrame(() => textarea.focus({ preventScroll: true })) const { realm } = this.get()
if (realm === 'dialog') {
// If we're in a dialog, the dialog will be hidden at this
// point. Also, the dialog has its own initial focus behavior.
// Tell the dialog to focus the textarea.
textarea.setAttribute('autofocus', true)
} else {
requestAnimationFrame(() => textarea.focus({ preventScroll: true }))
}
} }
} }
}) })

View file

@ -108,7 +108,7 @@ A11yDialog.prototype.show = function (event) {
// it later, then set the focus to the first focusable child of the dialog // it later, then set the focus to the first focusable child of the dialog
// element // element
focusedBeforeDialog = document.activeElement focusedBeforeDialog = document.activeElement
setFocusToFirstItem(this.node) setInitialFocus(this.node)
// Bind a focus event listener to the body element to make sure the focus // Bind a focus event listener to the body element to make sure the focus
// stays trapped inside the dialog while open, and start listening for some // stays trapped inside the dialog while open, and start listening for some
@ -281,7 +281,7 @@ A11yDialog.prototype._maintainFocus = function (event) {
// If the dialog is shown and the focus is not within the dialog element, // If the dialog is shown and the focus is not within the dialog element,
// move it back to its first focusable child // move it back to its first focusable child
if (this.shown && !this.node.contains(event.target)) { if (this.shown && !this.node.contains(event.target)) {
setFocusToFirstItem(this.node) setInitialFocus(this.node)
} }
} }
@ -333,9 +333,17 @@ function collect (target) {
* *
* @param {Element} node * @param {Element} node
*/ */
function setFocusToFirstItem (node) { function setInitialFocus (node) {
const focusableChildren = getFocusableChildren(node) const focusableChildren = getFocusableChildren(node)
// If there's an element with an autofocus attribute, focus that.
for (const child of focusableChildren) {
if (child.getAttribute('autofocus')) {
child.focus()
return
}
}
// Otherwise, focus the first focusable element.
if (focusableChildren.length) { if (focusableChildren.length) {
focusableChildren[0].focus() focusableChildren[0].focus()
} }