зеркало из
1
0
Форкнуть 0

Trap focus when connected in `<details open>`

When the `<details-dialog>` element is connected to the document as a
child of a `<details>` element rendered with `[open]`, trap focus
immediately.

In support of this change, extract the `trapFocus` and `releaseFocus`
functions, then call `trapFocus` within the `connectedCallback` if the
ancestor detail is `[open]`.
This commit is contained in:
Sean Doyle 2022-08-09 15:20:53 -04:00
Родитель 3539591c5a
Коммит 56499c5cd0
2 изменённых файлов: 64 добавлений и 13 удалений

Просмотреть файл

@ -95,6 +95,25 @@ function onSummaryClick(event: Event): void {
} }
} }
function trapFocus(dialog: DetailsDialogElement, details: Element): void {
const root = 'getRootNode' in dialog ? (dialog.getRootNode() as Document | ShadowRoot) : document
if (root.activeElement instanceof HTMLElement) {
initialized.set(dialog, {details, activeElement: root.activeElement})
}
autofocus(dialog)
;(details as HTMLElement).addEventListener('keydown', keydown)
}
function releaseFocus(dialog: DetailsDialogElement, details: Element): void {
for (const form of dialog.querySelectorAll('form')) {
form.reset()
}
const focusElement = findFocusElement(details, dialog)
if (focusElement) focusElement.focus()
;(details as HTMLElement).removeEventListener('keydown', keydown)
}
function toggle(event: Event): void { function toggle(event: Event): void {
const details = event.currentTarget const details = event.currentTarget
if (!(details instanceof Element)) return if (!(details instanceof Element)) return
@ -102,20 +121,9 @@ function toggle(event: Event): void {
if (!(dialog instanceof DetailsDialogElement)) return if (!(dialog instanceof DetailsDialogElement)) return
if (details.hasAttribute('open')) { if (details.hasAttribute('open')) {
const root = 'getRootNode' in dialog ? (dialog.getRootNode() as Document | ShadowRoot) : document trapFocus(dialog, details)
if (root.activeElement instanceof HTMLElement) {
initialized.set(dialog, {details, activeElement: root.activeElement})
}
autofocus(dialog)
;(details as HTMLElement).addEventListener('keydown', keydown)
} else { } else {
for (const form of dialog.querySelectorAll('form')) { releaseFocus(dialog, details)
form.reset()
}
const focusElement = findFocusElement(details, dialog)
if (focusElement) focusElement.focus()
;(details as HTMLElement).removeEventListener('keydown', keydown)
} }
} }
@ -249,6 +257,7 @@ class DetailsDialogElement extends HTMLElement {
details.addEventListener('toggle', toggle) details.addEventListener('toggle', toggle)
state.details = details state.details = details
if (details.hasAttribute('open')) trapFocus(this, details)
updateIncludeFragmentEventListeners(details, this.src, this.preload) updateIncludeFragmentEventListeners(details, this.src, this.preload)
} }

Просмотреть файл

@ -289,6 +289,48 @@ describe('details-dialog-element', function() {
}) })
}) })
describe('connected as a child of an already [open] <details> element', function () {
let details
let dialog
let summary
let close
beforeEach(function() {
const container = document.createElement('div')
container.innerHTML = `
<details open>
<summary>Click</summary>
<details-dialog>
<button data-button>Button</button>
<button ${CLOSE_ATTR}>Goodbye</button>
</details-dialog>
</details>
`
document.body.append(container)
details = document.querySelector('details')
dialog = details.querySelector('details-dialog')
summary = details.querySelector('summary')
close = dialog.querySelector(CLOSE_SELECTOR)
})
afterEach(function() {
document.body.innerHTML = ''
})
it('manages focus', async function() {
assert.equal(document.activeElement, dialog)
triggerKeydownEvent(document.activeElement, 'Tab', true)
assert.equal(document.activeElement, document.querySelector(`[${CLOSE_ATTR}]`))
triggerKeydownEvent(document.activeElement, 'Tab')
assert.equal(document.activeElement, document.querySelector(`[data-button]`))
triggerKeydownEvent(document.activeElement, 'Tab')
assert.equal(document.activeElement, document.querySelector(`[${CLOSE_ATTR}]`))
triggerKeydownEvent(document.activeElement, 'Tab')
assert.equal(document.activeElement, document.querySelector(`[data-button]`))
})
})
describe('shadow DOM context', function() { describe('shadow DOM context', function() {
let shadowRoot, details, summary, dialog let shadowRoot, details, summary, dialog
beforeEach(function() { beforeEach(function() {