diff --git a/packages/@headlessui-react/CHANGELOG.md b/packages/@headlessui-react/CHANGELOG.md index c653c90..5b28be2 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 108a0ac..d9f5878 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 72dea1f..5e14bb9 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,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 diff --git a/packages/@headlessui-vue/src/utils/disposables.ts b/packages/@headlessui-vue/src/utils/disposables.ts index 3b49798..855c697 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() } },