feat: drawer support onOpened & onClosed

This commit is contained in:
Netfan 2024-12-30 21:18:31 +08:00
parent 4a8e6abc06
commit e86a7906fe
6 changed files with 80 additions and 9 deletions

View File

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

View File

@ -110,4 +110,19 @@ describe('drawerApi', () => {
expect(drawerApi.store.state.title).toBe('Batch Title'); expect(drawerApi.store.state.title).toBe('Batch Title');
expect(drawerApi.store.state.confirmText).toBe('Batch Confirm'); 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();
});
}); });

View File

@ -6,7 +6,12 @@ import { bindMethods, isFunction } from '@vben-core/shared/utils';
export class DrawerApi { export class DrawerApi {
private api: Pick< private api: Pick<
DrawerApiOptions, DrawerApiOptions,
'onBeforeClose' | 'onCancel' | 'onConfirm' | 'onOpenChange' | 'onBeforeClose'
| 'onCancel'
| 'onClosed'
| 'onConfirm'
| 'onOpenChange'
| 'onOpened'
>; >;
// private prevState!: DrawerState; // private prevState!: DrawerState;
private state!: DrawerState; private state!: DrawerState;
@ -23,8 +28,10 @@ export class DrawerApi {
connectedComponent: _, connectedComponent: _,
onBeforeClose, onBeforeClose,
onCancel, onCancel,
onClosed,
onConfirm, onConfirm,
onOpenChange, onOpenChange,
onOpened,
...storeState ...storeState
} = options; } = options;
@ -68,8 +75,10 @@ export class DrawerApi {
this.api = { this.api = {
onBeforeClose, onBeforeClose,
onCancel, onCancel,
onClosed,
onConfirm, onConfirm,
onOpenChange, onOpenChange,
onOpened,
}; };
bindMethods(this); 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?.(); this.api.onConfirm?.();
} }
/**
*
*/
onOpened() {
if (this.state.isOpen) {
this.api.onOpened?.();
}
}
open() { open() {
this.store.setState((prev) => ({ ...prev, isOpen: true })); this.store.setState((prev) => ({ ...prev, isOpen: true }));
} }

View File

@ -138,6 +138,11 @@ export interface DrawerApiOptions extends DrawerState {
* *
*/ */
onCancel?: () => void; onCancel?: () => void;
/**
*
* @returns
*/
onClosed?: () => void;
/** /**
* *
*/ */
@ -148,4 +153,9 @@ export interface DrawerApiOptions extends DrawerState {
* @returns * @returns
*/ */
onOpenChange?: (isOpen: boolean) => void; onOpenChange?: (isOpen: boolean) => void;
/**
*
* @returns
*/
onOpened?: () => void;
} }

View File

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

View File

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue'; import { computed, ref } from 'vue';
import { cn } from '@vben-core/shared/utils'; import { cn } from '@vben-core/shared/utils';
@ -32,7 +32,9 @@ const props = withDefaults(defineProps<SheetContentProps>(), {
zIndex: 1000, zIndex: 1000,
}); });
const emits = defineEmits<DialogContentEmits>(); const emits = defineEmits<
{ close: []; closed: []; opened: [] } & DialogContentEmits
>();
const delegatedProps = computed(() => { const delegatedProps = computed(() => {
const { const {
@ -59,6 +61,17 @@ const position = computed(() => {
}); });
const forwarded = useForwardPropsEmits(delegatedProps, emits); 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> </script>
<template> <template>
@ -67,8 +80,10 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
<SheetOverlay v-if="open && modal" :style="{ zIndex, position }" /> <SheetOverlay v-if="open && modal" :style="{ zIndex, position }" />
</Transition> </Transition>
<DialogContent <DialogContent
ref="contentRef"
:class="cn(sheetVariants({ side }), props.class)" :class="cn(sheetVariants({ side }), props.class)"
:style="{ zIndex, position }" :style="{ zIndex, position }"
@animationend="onAnimationEnd"
v-bind="{ ...forwarded, ...$attrs }" v-bind="{ ...forwarded, ...$attrs }"
> >
<slot></slot> <slot></slot>