From e01f750f9ebe2e5f3b37712c872c4476328f1ff5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Mendon=C3=A7a?= Date: Fri, 29 May 2026 18:33:59 -0300 Subject: [PATCH 1/6] fix(drawer): share motion state and defer close for exit animation Centralize useDrawerMotionState in drawer-portal and inject it into overlay and content so enter/exit transitions stay in sync. Defer painting closed state on exit so the slide-out animation can run. Co-authored-by: Cursor --- .../composables/use-drawer-motion-state.ts | 16 ++++++++++++++-- .../components/overlay/drawer/drawer-overlay.vue | 7 +++---- .../components/overlay/drawer/drawer-portal.vue | 9 +++++++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/webkit/src/components/overlay/drawer/composables/use-drawer-motion-state.ts b/packages/webkit/src/components/overlay/drawer/composables/use-drawer-motion-state.ts index ed9bdae4a..c13f918a2 100644 --- a/packages/webkit/src/components/overlay/drawer/composables/use-drawer-motion-state.ts +++ b/packages/webkit/src/components/overlay/drawer/composables/use-drawer-motion-state.ts @@ -5,7 +5,9 @@ import type { DrawerMotionState } from '../presets/transitions' export type { DrawerMotionState } /** - * Drives `data-state` for CSS transitions: paints `closed` first, then flips to `open` on the next frame. + * Drives `data-state` for CSS transitions. + * Enter: paints `closed` first, then `open` on the next frame. + * Exit: keeps `open` for one frame, then `closed` so the browser can interpolate. */ export function useDrawerMotionState(isOpen: Ref) { const motionState = ref('closed') @@ -21,11 +23,21 @@ export function useDrawerMotionState(isOpen: Ref) { }) } + const paintClose = async () => { + await nextTick() + + globalThis.requestAnimationFrame(() => { + if (!isOpen.value) { + motionState.value = 'closed' + } + }) + } + watch( isOpen, (open) => { if (!open) { - motionState.value = 'closed' + void paintClose() return } diff --git a/packages/webkit/src/components/overlay/drawer/drawer-overlay.vue b/packages/webkit/src/components/overlay/drawer/drawer-overlay.vue index 9808e17a2..7f528e9da 100644 --- a/packages/webkit/src/components/overlay/drawer/drawer-overlay.vue +++ b/packages/webkit/src/components/overlay/drawer/drawer-overlay.vue @@ -2,8 +2,7 @@ import { computed, inject, useAttrs } from 'vue' import { cn } from '../../../utils/cn' - import { useDrawerMotionState } from './composables/use-drawer-motion-state' - import { DrawerInjectionKey } from './injection-key' + import { DrawerInjectionKey, DrawerMotionInjectionKey } from './injection-key' import { drawerOverlayTransitionClasses, getDrawerTransitionStyle } from './presets/transitions' defineOptions({ @@ -13,8 +12,8 @@ const attrs = useAttrs() const ctx = inject(DrawerInjectionKey) - const isOpen = computed(() => ctx?.isOpen.value ?? false) - const { motionState } = useDrawerMotionState(isOpen) + const motionCtx = inject(DrawerMotionInjectionKey) + const motionState = computed(() => motionCtx?.motionState.value ?? 'closed') const handleClick = () => { if (!ctx?.closeable) return diff --git a/packages/webkit/src/components/overlay/drawer/drawer-portal.vue b/packages/webkit/src/components/overlay/drawer/drawer-portal.vue index 02523f9c7..feb34e776 100644 --- a/packages/webkit/src/components/overlay/drawer/drawer-portal.vue +++ b/packages/webkit/src/components/overlay/drawer/drawer-portal.vue @@ -1,7 +1,8 @@