Merge pull request #22 from github/will-close-event
Add cancellable details-dialog:will-close event
This commit is contained in:
Коммит
1f7086a9d7
19
README.md
19
README.md
|
@ -24,6 +24,25 @@ import 'details-dialog-element'
|
|||
</details>
|
||||
```
|
||||
|
||||
## Events
|
||||
|
||||
### `details-dialog:will-close`
|
||||
|
||||
A `details-dialog:will-close` event is fired when a request to close the dialog
|
||||
is made either by pressing escape, clicking a `data-close-dialog` element,
|
||||
clicking on the `<summary>` element, or when `.toggle(false)` is called on an
|
||||
open dialog.
|
||||
|
||||
This event can be cancelled to keep the dialog open.
|
||||
|
||||
```js
|
||||
document.addEventListener('details-dialog:will-close', function(event) {
|
||||
if (!confirm('Are you sure?')) {
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Browser support
|
||||
|
||||
Browsers without native [custom element support][support] require a [polyfill][].
|
||||
|
|
50
index.js
50
index.js
|
@ -59,6 +59,30 @@ function restrictTabBehavior(event: KeyboardEvent): void {
|
|||
elements[targetIndex].focus()
|
||||
}
|
||||
|
||||
function allowClosingDialog(details: Element): boolean {
|
||||
const dialog = details.querySelector('details-dialog')
|
||||
if (!(dialog instanceof DetailsDialogElement)) return true
|
||||
|
||||
return dialog.dispatchEvent(
|
||||
new CustomEvent('details-dialog:will-close', {
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function onSummaryClick(event: Event): void {
|
||||
if (!(event.currentTarget instanceof Element)) return
|
||||
const details = event.currentTarget.closest('details[open]')
|
||||
if (!details) return
|
||||
|
||||
// Prevent summary click events if details-dialog:will-close was cancelled
|
||||
if (!allowClosingDialog(details)) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
}
|
||||
|
||||
function toggle(event: Event): void {
|
||||
const details = event.currentTarget
|
||||
if (!(details instanceof Element)) return
|
||||
|
@ -95,13 +119,10 @@ function toggleDetails(details: Element, open: boolean) {
|
|||
// Don't update unless state is changing
|
||||
if (open === details.hasAttribute('open')) return
|
||||
|
||||
const summary = details.querySelector('summary')
|
||||
if (summary) {
|
||||
// Toggle via clicking summary so it can be canceled by listeners wanting
|
||||
// to prevent the toggle
|
||||
summary.click()
|
||||
} else {
|
||||
open ? details.setAttribute('open', 'open') : details.removeAttribute('open')
|
||||
if (open) {
|
||||
details.setAttribute('open', '')
|
||||
} else if (allowClosingDialog(details)) {
|
||||
details.removeAttribute('open')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,7 +164,10 @@ class DetailsDialogElement extends HTMLElement {
|
|||
if (!details) return
|
||||
|
||||
const summary = details.querySelector('summary')
|
||||
if (summary) summary.setAttribute('aria-haspopup', 'dialog')
|
||||
if (summary) {
|
||||
summary.setAttribute('aria-haspopup', 'dialog')
|
||||
summary.addEventListener('click', onSummaryClick, {capture: true})
|
||||
}
|
||||
|
||||
details.addEventListener('toggle', toggle)
|
||||
state.details = details
|
||||
|
@ -151,8 +175,14 @@ class DetailsDialogElement extends HTMLElement {
|
|||
|
||||
disconnectedCallback() {
|
||||
const state = initialized.get(this)
|
||||
if (!state || !state.details) return
|
||||
state.details.removeEventListener('toggle', toggle)
|
||||
if (!state) return
|
||||
const {details} = state
|
||||
if (!details) return
|
||||
details.removeEventListener('toggle', toggle)
|
||||
const summary = details.querySelector('summary')
|
||||
if (summary) {
|
||||
summary.removeEventListener('click', onSummaryClick, {capture: true})
|
||||
}
|
||||
state.details = null
|
||||
}
|
||||
|
||||
|
|
42
test/test.js
42
test/test.js
|
@ -75,15 +75,15 @@ describe('details-dialog-element', function() {
|
|||
assert(!details.open)
|
||||
})
|
||||
|
||||
it('supports canceling requests to close the dialog when a summary element is present', async function() {
|
||||
it('supports a cancellable details-dialog:will-close event when a summary element is present', async function() {
|
||||
dialog.toggle(true)
|
||||
await waitForToggleEvent(details)
|
||||
assert(details.open)
|
||||
|
||||
let closeRequestCount = 0
|
||||
let allowCloseToHappen = false
|
||||
summary.addEventListener(
|
||||
'click',
|
||||
dialog.addEventListener(
|
||||
'details-dialog:will-close',
|
||||
function(event) {
|
||||
closeRequestCount++
|
||||
if (!allowCloseToHappen) {
|
||||
|
@ -120,6 +120,42 @@ describe('details-dialog-element', function() {
|
|||
summary.remove()
|
||||
})
|
||||
|
||||
it('supports a cancellable details-dialog:will-close event', async function() {
|
||||
dialog.toggle(true)
|
||||
await waitForToggleEvent(details)
|
||||
assert(details.open)
|
||||
|
||||
let closeRequestCount = 0
|
||||
let allowCloseToHappen = false
|
||||
dialog.addEventListener(
|
||||
'details-dialog:will-close',
|
||||
function(event) {
|
||||
closeRequestCount++
|
||||
if (!allowCloseToHappen) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
},
|
||||
{capture: true}
|
||||
)
|
||||
|
||||
close.click()
|
||||
assert(details.open)
|
||||
assert.equal(closeRequestCount, 1)
|
||||
|
||||
pressEscape(details)
|
||||
assert(details.open)
|
||||
assert.equal(closeRequestCount, 2)
|
||||
|
||||
dialog.toggle(false)
|
||||
assert(details.open)
|
||||
assert.equal(closeRequestCount, 3)
|
||||
|
||||
allowCloseToHappen = true
|
||||
close.click()
|
||||
assert(!details.open)
|
||||
})
|
||||
|
||||
it('toggles open', function() {
|
||||
assert(!details.open)
|
||||
dialog.toggle(true)
|
||||
|
|
Загрузка…
Ссылка в новой задаче