1
0
Эх сурвалжийг харах

feat: add opened and closed events for dialog (#4775)

Netfan 5 сар өмнө
parent
commit
06ba7cb224

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

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

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

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

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

@@ -6,7 +6,12 @@ import { bindMethods, isFunction } from '@vben-core/shared/utils';
 export class ModalApi {
   private api: Pick<
     ModalApiOptions,
-    'onBeforeClose' | 'onCancel' | 'onConfirm' | 'onOpenChange'
+    | 'onBeforeClose'
+    | 'onCancel'
+    | 'onClosed'
+    | 'onConfirm'
+    | 'onOpenChange'
+    | 'onOpened'
   >;
   // private prevState!: ModalState;
   private state!: ModalState;
@@ -23,8 +28,10 @@ export class ModalApi {
       connectedComponent: _,
       onBeforeClose,
       onCancel,
+      onClosed,
       onConfirm,
       onOpenChange,
+      onOpened,
       ...storeState
     } = options;
 
@@ -77,8 +84,10 @@ export class ModalApi {
     this.api = {
       onBeforeClose,
       onCancel,
+      onClosed,
       onConfirm,
       onOpenChange,
+      onOpened,
     };
     bindMethods(this);
   }
@@ -115,6 +124,15 @@ export class ModalApi {
     }
   }
 
+  /**
+   * 弹窗关闭动画播放完毕后的回调
+   */
+  onClosed() {
+    if (!this.state.isOpen) {
+      this.api.onClosed?.();
+    }
+  }
+
   /**
    * 确认操作
    */
@@ -122,6 +140,15 @@ export class ModalApi {
     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/modal/modal.ts

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

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

@@ -188,10 +188,12 @@ function handleFocusOutside(e: Event) {
       :show-close="closable"
       close-class="top-3"
       @close-auto-focus="handleFocusOutside"
+      @closed="() => modalApi?.onClosed()"
       @escape-key-down="escapeKeyDown"
       @focus-outside="handleFocusOutside"
       @interact-outside="interactOutside"
       @open-auto-focus="handerOpenAutoFocus"
+      @opened="() => modalApi?.onOpened()"
       @pointer-down-outside="pointerDownOutside"
     >
       <DialogHeader

+ 11 - 2
packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue

@@ -27,7 +27,9 @@ const props = withDefaults(
   >(),
   { showClose: true },
 );
-const emits = defineEmits<{ close: [] } & DialogContentEmits>();
+const emits = defineEmits<
+  { close: []; closed: []; opened: [] } & DialogContentEmits
+>();
 
 const delegatedProps = computed(() => {
   const {
@@ -44,7 +46,13 @@ const delegatedProps = computed(() => {
 const forwarded = useForwardPropsEmits(delegatedProps, emits);
 
 const contentRef = ref<InstanceType<typeof DialogContent> | null>(null);
-
+function onAnimationEnd() {
+  if (props.open) {
+    emits('opened');
+  } else {
+    emits('closed');
+  }
+}
 defineExpose({
   getContentRef: () => contentRef.value,
 });
@@ -57,6 +65,7 @@ defineExpose({
     </Transition>
     <DialogContent
       ref="contentRef"
+      @animationend="onAnimationEnd"
       v-bind="forwarded"
       :class="
         cn(

+ 6 - 0
playground/src/views/examples/modal/base-demo.vue

@@ -7,10 +7,16 @@ const [Modal, modalApi] = useVbenModal({
   onCancel() {
     modalApi.close();
   },
+  onClosed() {
+    message.info('onClosed:关闭动画结束');
+  },
   onConfirm() {
     message.info('onConfirm');
     // modalApi.close();
   },
+  onOpened() {
+    message.info('onOpened:打开动画结束');
+  },
 });
 </script>
 <template>