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:
Родитель
3539591c5a
Коммит
56499c5cd0
35
src/index.ts
35
src/index.ts
|
@ -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 {
|
||||
const details = event.currentTarget
|
||||
if (!(details instanceof Element)) return
|
||||
|
@ -102,20 +121,9 @@ function toggle(event: Event): void {
|
|||
if (!(dialog instanceof DetailsDialogElement)) return
|
||||
|
||||
if (details.hasAttribute('open')) {
|
||||
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)
|
||||
trapFocus(dialog, details)
|
||||
} else {
|
||||
for (const form of dialog.querySelectorAll('form')) {
|
||||
form.reset()
|
||||
}
|
||||
const focusElement = findFocusElement(details, dialog)
|
||||
if (focusElement) focusElement.focus()
|
||||
;(details as HTMLElement).removeEventListener('keydown', keydown)
|
||||
releaseFocus(dialog, details)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,6 +257,7 @@ class DetailsDialogElement extends HTMLElement {
|
|||
|
||||
details.addEventListener('toggle', toggle)
|
||||
state.details = details
|
||||
if (details.hasAttribute('open')) trapFocus(this, details)
|
||||
|
||||
updateIncludeFragmentEventListeners(details, this.src, this.preload)
|
||||
}
|
||||
|
|
42
test/test.js
42
test/test.js
|
@ -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() {
|
||||
let shadowRoot, details, summary, dialog
|
||||
beforeEach(function() {
|
||||
|
|
Загрузка…
Ссылка в новой задаче