diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index c653c907ea..5b28be2c15 100644 --- a/packages/@headlessui-react/CHANGELOG.md +++ b/packages/@headlessui-react/CHANGELOG.md @@ -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 diff --git a/packages/@headlessui-react/src/components/transitions/utils/transition.ts b/packages/@headlessui-react/src/components/transitions/utils/transition.ts index 108a0ac15d..d9f5878cfc 100644 --- a/packages/@headlessui-react/src/components/transitions/utils/transition.ts +++ b/packages/@headlessui-react/src/components/transitions/utils/transition.ts @@ -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() diff --git a/packages/@headlessui-react/src/utils/disposables.ts b/packages/@headlessui-react/src/utils/disposables.ts index 72dea1fe37..5e14bb9a20 100644 --- a/packages/@headlessui-react/src/utils/disposables.ts +++ b/packages/@headlessui-react/src/utils/disposables.ts @@ -3,7 +3,7 @@ import { microTask } from './micro-task' export type Disposables = ReturnType export function disposables() { - let disposables: Function[] = [] + let _disposables: Function[] = [] let api = { addEventListener( @@ -44,30 +44,37 @@ export function disposables() { }) }, + style(node: HTMLElement, property: string, value: string) { + let previous = node.style.getPropertyValue(property) + Object.assign(node.style, { [property]: value }) + return this.add(() => { + 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) + _disposables.push(cb) return () => { - let idx = disposables.indexOf(cb) + let idx = _disposables.indexOf(cb) if (idx >= 0) { - let [dispose] = disposables.splice(idx, 1) - dispose() + 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() } }, - - style(node: HTMLElement, property: string, value: string) { - let previous = node.style.getPropertyValue(property) - Object.assign(node.style, { [property]: value }) - return this.add(() => { - Object.assign(node.style, { [property]: previous }) - }) - }, } return api diff --git a/packages/@headlessui-vue/src/utils/disposables.ts b/packages/@headlessui-vue/src/utils/disposables.ts index 3b49798105..855c697180 100644 --- a/packages/@headlessui-vue/src/utils/disposables.ts +++ b/packages/@headlessui-vue/src/utils/disposables.ts @@ -1,7 +1,7 @@ export type Disposables = ReturnType export function disposables() { - let disposables: Function[] = [] + let _disposables: Function[] = [] let api = { addEventListener( @@ -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() } },