Ensure Transition component completes if nothing is transitioning (#2318)

* make `disposables` consistent

Also added a `group` function, this allows us to spawn a _sub_
disposables group that can be disposed on its own, but will also be
disposed the moment the "parent" is disposed.

* ensure Transition component works when nothing is transitioning

* update changelog
This commit is contained in:
Robin Malfait
2023-02-28 12:38:11 +01:00
committed by GitHub
parent 7794d563e1
commit 213dd529bf
4 changed files with 65 additions and 25 deletions
+3 -1
View File
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
- Nothing yet!
### Fixed
- Ensure `Transition` component completes if nothing is transitioning ([#2318](https://github.com/tailwindlabs/headlessui/pull/2318))
## [1.7.12] - 2023-02-24
@@ -39,6 +39,23 @@ function waitForTransition(node: HTMLElement, done: () => void) {
dispose()
}, totalDuration)
} else {
d.group((d) => {
// Mark the transition as done when the timeout is reached. This is a fallback in case the
// transitionrun event is not fired.
d.setTimeout(() => {
done()
d.dispose()
}, totalDuration)
// The moment the transitionrun event fires, we should cleanup the timeout fallback, because
// then we know that we can use the native transition events because something is
// transitioning.
d.addEventListener(node, 'transitionrun', (event) => {
if (event.target !== event.currentTarget) return
d.dispose()
})
})
let dispose = d.addEventListener(node, 'transitionend', (event) => {
if (event.target !== event.currentTarget) return
done()
@@ -3,7 +3,7 @@ import { microTask } from './micro-task'
export type Disposables = ReturnType<typeof disposables>
export function disposables() {
let disposables: Function[] = []
let _disposables: Function[] = []
let api = {
addEventListener<TEventName extends keyof WindowEventMap>(
@@ -44,23 +44,6 @@ export function disposables() {
})
},
add(cb: () => void) {
disposables.push(cb)
return () => {
let idx = disposables.indexOf(cb)
if (idx >= 0) {
let [dispose] = disposables.splice(idx, 1)
dispose()
}
}
},
dispose() {
for (let dispose of disposables.splice(0)) {
dispose()
}
},
style(node: HTMLElement, property: string, value: string) {
let previous = node.style.getPropertyValue(property)
Object.assign(node.style, { [property]: value })
@@ -68,6 +51,30 @@ export function disposables() {
Object.assign(node.style, { [property]: previous })
})
},
group(cb: (d: typeof this) => void) {
let d = disposables()
cb(d)
return this.add(() => d.dispose())
},
add(cb: () => void) {
_disposables.push(cb)
return () => {
let idx = _disposables.indexOf(cb)
if (idx >= 0) {
for (let dispose of _disposables.splice(idx, 1)) {
dispose()
}
}
}
},
dispose() {
for (let dispose of _disposables.splice(0)) {
dispose()
}
},
}
return api
@@ -1,7 +1,7 @@
export type Disposables = ReturnType<typeof disposables>
export function disposables() {
let disposables: Function[] = []
let _disposables: Function[] = []
let api = {
addEventListener<TEventName extends keyof WindowEventMap>(
@@ -30,10 +30,6 @@ export function disposables() {
api.add(() => clearTimeout(timer))
},
add(cb: () => void) {
disposables.push(cb)
},
style(node: HTMLElement, property: string, value: string) {
let previous = node.style.getPropertyValue(property)
Object.assign(node.style, { [property]: value })
@@ -42,8 +38,26 @@ export function disposables() {
})
},
group(cb: (d: typeof this) => void) {
let d = disposables()
cb(d)
return this.add(() => d.dispose())
},
add(cb: () => void) {
_disposables.push(cb)
return () => {
let idx = _disposables.indexOf(cb)
if (idx >= 0) {
for (let dispose of _disposables.splice(idx, 1)) {
dispose()
}
}
}
},
dispose() {
for (let dispose of disposables.splice(0)) {
for (let dispose of _disposables.splice(0)) {
dispose()
}
},