Browse Source

feat: drawer support `onOpened` & `onClosed`

Netfan 2 months ago
parent
commit
e86a7906fe

+ 8 - 6
docs/src/components/common-ui/vben-drawer.md

@@ -108,12 +108,14 @@ const [Drawer, drawerApi] = useVbenDrawer({
 
 以下事件,只有在 `useVbenDrawer({onCancel:()=>{}})` 中传入才会生效。
 
-| 事件名 | 描述 | 类型 |
-| --- | --- | --- |
-| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` |
-| onCancel | 点击取消按钮触发 | `()=>void` |
-| onConfirm | 点击确认按钮触发 | `()=>void` |
-| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` |
+| 事件名 | 描述 | 类型 | 版本限制 |
+| --- | --- | --- | --- |
+| onBeforeClose | 关闭前触发,返回 `false`则禁止关闭 | `()=>boolean` | --- |
+| onCancel | 点击取消按钮触发 | `()=>void` | --- |
+| onClosed | 关闭动画播放完毕时触发 | `()=>void` | >5.5.2 |
+| onConfirm | 点击确认按钮触发 | `()=>void` | --- |
+| onOpenChange | 关闭或者打开弹窗时触发 | `(isOpen:boolean)=>void` | --- |
+| onOpened | 打开动画播放完毕时触发 | `()=>void` | >5.5.2 |
 
 ### Slots
 

+ 15 - 0
packages/@core/ui-kit/popup-ui/src/drawer/__tests__/drawer-api.test.ts

@@ -110,4 +110,19 @@ describe('drawerApi', () => {
     expect(drawerApi.store.state.title).toBe('Batch Title');
     expect(drawerApi.store.state.confirmText).toBe('Batch Confirm');
   });
+
+  it('should call onClosed callback when provided', () => {
+    const onClosed = vi.fn();
+    const drawerApiWithHook = new DrawerApi({ onClosed });
+    drawerApiWithHook.onClosed();
+    expect(onClosed).toHaveBeenCalled();
+  });
+
+  it('should call onOpened callback when provided', () => {
+    const onOpened = vi.fn();
+    const drawerApiWithHook = new DrawerApi({ onOpened });
+    drawerApiWithHook.open();
+    drawerApiWithHook.onOpened();
+    expect(onOpened).toHaveBeenCalled();
+  });
 });

+ 28 - 1
packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts

@@ -6,7 +6,12 @@ import { bindMethods, isFunction } from '@vben-core/shared/utils';
 export class DrawerApi {
   private api: Pick<
     DrawerApiOptions,
-    'onBeforeClose' | 'onCancel' | 'onConfirm' | 'onOpenChange'
+    | 'onBeforeClose'
+    | 'onCancel'
+    | 'onClosed'
+    | 'onConfirm'
+    | 'onOpenChange'
+    | 'onOpened'
   >;
   // private prevState!: DrawerState;
   private state!: DrawerState;
@@ -23,8 +28,10 @@ export class DrawerApi {
       connectedComponent: _,
       onBeforeClose,
       onCancel,
+      onClosed,
       onConfirm,
       onOpenChange,
+      onOpened,
       ...storeState
     } = options;
 
@@ -68,8 +75,10 @@ export class DrawerApi {
     this.api = {
       onBeforeClose,
       onCancel,
+      onClosed,
       onConfirm,
       onOpenChange,
+      onOpened,
     };
     bindMethods(this);
   }
@@ -106,6 +115,15 @@ export class DrawerApi {
     }
   }
 
+  /**
+   * 弹窗关闭动画播放完毕后的回调
+   */
+  onClosed() {
+    if (!this.state.isOpen) {
+      this.api.onClosed?.();
+    }
+  }
+
   /**
    * 确认操作
    */
@@ -113,6 +131,15 @@ export class DrawerApi {
     this.api.onConfirm?.();
   }
 
+  /**
+   * 弹窗打开动画播放完毕后的回调
+   */
+  onOpened() {
+    if (this.state.isOpen) {
+      this.api.onOpened?.();
+    }
+  }
+
   open() {
     this.store.setState((prev) => ({ ...prev, isOpen: true }));
   }

+ 10 - 0
packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts

@@ -138,6 +138,11 @@ export interface DrawerApiOptions extends DrawerState {
    * 点击取消按钮的回调
    */
   onCancel?: () => void;
+  /**
+   * 弹窗关闭动画结束的回调
+   * @returns
+   */
+  onClosed?: () => void;
   /**
    * 点击确定按钮的回调
    */
@@ -148,4 +153,9 @@ export interface DrawerApiOptions extends DrawerState {
    * @returns
    */
   onOpenChange?: (isOpen: boolean) => void;
+  /**
+   * 弹窗打开动画结束的回调
+   * @returns
+   */
+  onOpened?: () => void;
 }

+ 2 - 0
packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue

@@ -139,10 +139,12 @@ const getAppendTo = computed(() => {
       :side="placement"
       :z-index="zIndex"
       @close-auto-focus="handleFocusOutside"
+      @closed="() => drawerApi?.onClosed()"
       @escape-key-down="escapeKeyDown"
       @focus-outside="handleFocusOutside"
       @interact-outside="interactOutside"
       @open-auto-focus="handerOpenAutoFocus"
+      @opened="() => drawerApi?.onOpened()"
       @pointer-down-outside="pointerDownOutside"
     >
       <SheetHeader

+ 17 - 2
packages/@core/ui-kit/shadcn-ui/src/ui/sheet/SheetContent.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 
 import { cn } from '@vben-core/shared/utils';
 
@@ -32,7 +32,9 @@ const props = withDefaults(defineProps<SheetContentProps>(), {
   zIndex: 1000,
 });
 
-const emits = defineEmits<DialogContentEmits>();
+const emits = defineEmits<
+  { close: []; closed: []; opened: [] } & DialogContentEmits
+>();
 
 const delegatedProps = computed(() => {
   const {
@@ -59,6 +61,17 @@ const position = computed(() => {
 });
 
 const forwarded = useForwardPropsEmits(delegatedProps, emits);
+const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
+function onAnimationEnd(event: AnimationEvent) {
+  // 只有在 contentRef 的动画结束时才触发 opened/closed 事件
+  if (event.target === contentRef.value?.$el) {
+    if (props.open) {
+      emits('opened');
+    } else {
+      emits('closed');
+    }
+  }
+}
 </script>
 
 <template>
@@ -67,8 +80,10 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
       <SheetOverlay v-if="open && modal" :style="{ zIndex, position }" />
     </Transition>
     <DialogContent
+      ref="contentRef"
       :class="cn(sheetVariants({ side }), props.class)"
       :style="{ zIndex, position }"
+      @animationend="onAnimationEnd"
       v-bind="{ ...forwarded, ...$attrs }"
     >
       <slot></slot>