export function waitForCssTransition(node: HTMLElement, signal: AbortSignal, action?: () => void) { return new Promise((resolve) => { let resolved = false let sawTransitionRun = false let firstFrameId = 0 let secondFrameId = 0 function cleanup() { cancelAnimationFrame(firstFrameId) cancelAnimationFrame(secondFrameId) node.removeEventListener('transitionrun', handleTransitionRun) node.removeEventListener('transitionend', finish) node.removeEventListener('transitioncancel', finish) signal.removeEventListener('abort', finish) } function handleTransitionRun(event: TransitionEvent) { if (event.target !== node) { return } sawTransitionRun = true } function finish(event?: Event) { if (event instanceof TransitionEvent && event.target !== node) { return } if (resolved) { return } resolved = true cleanup() resolve() } node.addEventListener('transitionrun', handleTransitionRun) node.addEventListener('transitionend', finish) node.addEventListener('transitioncancel', finish) signal.addEventListener('abort', finish, { once: true }) if (action) { try { action() } catch (error) { cleanup() throw error } } if (signal.aborted) { finish() return } firstFrameId = requestAnimationFrame(() => { secondFrameId = requestAnimationFrame(() => { if (!sawTransitionRun) { finish() } }) }) }) }