Skip to content

Commit 213dd52

Browse files
authored
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
1 parent 7794d56 commit 213dd52

File tree

4 files changed

+61
-21
lines changed

4 files changed

+61
-21
lines changed

packages/@headlessui-react/CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
- Nothing yet!
10+
### Fixed
11+
12+
- Ensure `Transition` component completes if nothing is transitioning ([#2318](https://github.com/tailwindlabs/headlessui/pull/2318))
1113

1214
## [1.7.12] - 2023-02-24
1315

packages/@headlessui-react/src/components/transitions/utils/transition.ts

+17
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,23 @@ function waitForTransition(node: HTMLElement, done: () => void) {
3939
dispose()
4040
}, totalDuration)
4141
} else {
42+
d.group((d) => {
43+
// Mark the transition as done when the timeout is reached. This is a fallback in case the
44+
// transitionrun event is not fired.
45+
d.setTimeout(() => {
46+
done()
47+
d.dispose()
48+
}, totalDuration)
49+
50+
// The moment the transitionrun event fires, we should cleanup the timeout fallback, because
51+
// then we know that we can use the native transition events because something is
52+
// transitioning.
53+
d.addEventListener(node, 'transitionrun', (event) => {
54+
if (event.target !== event.currentTarget) return
55+
d.dispose()
56+
})
57+
})
58+
4259
let dispose = d.addEventListener(node, 'transitionend', (event) => {
4360
if (event.target !== event.currentTarget) return
4461
done()

packages/@headlessui-react/src/utils/disposables.ts

+21-14
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { microTask } from './micro-task'
33
export type Disposables = ReturnType<typeof disposables>
44

55
export function disposables() {
6-
let disposables: Function[] = []
6+
let _disposables: Function[] = []
77

88
let api = {
99
addEventListener<TEventName extends keyof WindowEventMap>(
@@ -44,30 +44,37 @@ export function disposables() {
4444
})
4545
},
4646

47+
style(node: HTMLElement, property: string, value: string) {
48+
let previous = node.style.getPropertyValue(property)
49+
Object.assign(node.style, { [property]: value })
50+
return this.add(() => {
51+
Object.assign(node.style, { [property]: previous })
52+
})
53+
},
54+
55+
group(cb: (d: typeof this) => void) {
56+
let d = disposables()
57+
cb(d)
58+
return this.add(() => d.dispose())
59+
},
60+
4761
add(cb: () => void) {
48-
disposables.push(cb)
62+
_disposables.push(cb)
4963
return () => {
50-
let idx = disposables.indexOf(cb)
64+
let idx = _disposables.indexOf(cb)
5165
if (idx >= 0) {
52-
let [dispose] = disposables.splice(idx, 1)
53-
dispose()
66+
for (let dispose of _disposables.splice(idx, 1)) {
67+
dispose()
68+
}
5469
}
5570
}
5671
},
5772

5873
dispose() {
59-
for (let dispose of disposables.splice(0)) {
74+
for (let dispose of _disposables.splice(0)) {
6075
dispose()
6176
}
6277
},
63-
64-
style(node: HTMLElement, property: string, value: string) {
65-
let previous = node.style.getPropertyValue(property)
66-
Object.assign(node.style, { [property]: value })
67-
return this.add(() => {
68-
Object.assign(node.style, { [property]: previous })
69-
})
70-
},
7178
}
7279

7380
return api

packages/@headlessui-vue/src/utils/disposables.ts

+20-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
export type Disposables = ReturnType<typeof disposables>
22

33
export function disposables() {
4-
let disposables: Function[] = []
4+
let _disposables: Function[] = []
55

66
let api = {
77
addEventListener<TEventName extends keyof WindowEventMap>(
@@ -30,10 +30,6 @@ export function disposables() {
3030
api.add(() => clearTimeout(timer))
3131
},
3232

33-
add(cb: () => void) {
34-
disposables.push(cb)
35-
},
36-
3733
style(node: HTMLElement, property: string, value: string) {
3834
let previous = node.style.getPropertyValue(property)
3935
Object.assign(node.style, { [property]: value })
@@ -42,8 +38,26 @@ export function disposables() {
4238
})
4339
},
4440

41+
group(cb: (d: typeof this) => void) {
42+
let d = disposables()
43+
cb(d)
44+
return this.add(() => d.dispose())
45+
},
46+
47+
add(cb: () => void) {
48+
_disposables.push(cb)
49+
return () => {
50+
let idx = _disposables.indexOf(cb)
51+
if (idx >= 0) {
52+
for (let dispose of _disposables.splice(idx, 1)) {
53+
dispose()
54+
}
55+
}
56+
}
57+
},
58+
4559
dispose() {
46-
for (let dispose of disposables.splice(0)) {
60+
for (let dispose of _disposables.splice(0)) {
4761
dispose()
4862
}
4963
},

0 commit comments

Comments
 (0)