/**
* Shared CSS-transition cycle for overlay components (modal, drawer, ...).
*
* Contract (see CLAUDE.md "CSS transitions + Vue render cycle"): the initial
* render of an opening overlay must carry the hidden state class ("a-hide")
* first, and the visible state class ("a-show") must be applied one
* nextTick + requestAnimationFrame later — otherwise the browser skips the
* transition entirely.
*
* Usage:
* const { visible, closing, show, hide, cancel } = useOverlayTransition();
* // render nothing while !visible; class = closing ? "a-hide" : "a-show"
* // call show()/hide() from your open watcher, cancel() onBeforeUnmount.
*
* @param {Object} [options]
* @param {number} [options.duration=300] ms to keep the element mounted while
* the closing transition plays.
*/
import { nextTick, ref } from "vue";
export function useOverlayTransition({ duration = 300 } = {}) {
const visible = ref(false);
const closing = ref(false);
let closeTimer = null;
const show = () => {
window.clearTimeout(closeTimer);
closing.value = true;
visible.value = true;
nextTick(() => {
requestAnimationFrame(() => {
closing.value = false;
});
});
};
const hide = () => {
closing.value = true;
closeTimer = window.setTimeout(() => {
visible.value = false;
closing.value = false;
}, duration);
};
const cancel = () => {
window.clearTimeout(closeTimer);
};
return { visible, closing, show, hide, cancel };
}
export default useOverlayTransition;