Merge branch 'main' of https://github.com/vbenjs/vue-vben-admin into dev
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
|
@@ -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);
|
||||
}
|
||||
@@ -114,6 +123,15 @@ export class DrawerApi {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗关闭动画播放完毕后的回调
|
||||
*/
|
||||
onClosed() {
|
||||
if (!this.state.isOpen) {
|
||||
this.api.onClosed?.();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认操作
|
||||
*/
|
||||
@@ -121,6 +139,15 @@ export class DrawerApi {
|
||||
this.api.onConfirm?.();
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹窗打开动画播放完毕后的回调
|
||||
*/
|
||||
onOpened() {
|
||||
if (this.state.isOpen) {
|
||||
this.api.onOpened?.();
|
||||
}
|
||||
}
|
||||
|
||||
open() {
|
||||
this.store.setState((prev) => ({ ...prev, isOpen: true }));
|
||||
}
|
||||
|
@@ -6,6 +6,8 @@ import type { Component, Ref } from 'vue';
|
||||
|
||||
export type DrawerPlacement = 'bottom' | 'left' | 'right' | 'top';
|
||||
|
||||
export type CloseIconPlacement = 'left' | 'right';
|
||||
|
||||
export interface DrawerProps {
|
||||
/**
|
||||
* 是否挂载到内容区域
|
||||
@@ -18,10 +20,14 @@ export interface DrawerProps {
|
||||
cancelText?: string;
|
||||
class?: ClassType;
|
||||
/**
|
||||
* 是否显示右上角的关闭按钮
|
||||
* 是否显示关闭按钮
|
||||
* @default true
|
||||
*/
|
||||
closable?: boolean;
|
||||
/**
|
||||
* 关闭按钮的位置
|
||||
*/
|
||||
closeIconPlacement?: CloseIconPlacement;
|
||||
/**
|
||||
* 点击弹窗遮罩是否关闭弹窗
|
||||
* @default true
|
||||
@@ -126,9 +132,13 @@ export type ExtendedDrawerApi = {
|
||||
|
||||
export interface DrawerApiOptions extends DrawerState {
|
||||
/**
|
||||
* 独立的弹窗组件
|
||||
* 独立的抽屉组件
|
||||
*/
|
||||
connectedComponent?: Component;
|
||||
/**
|
||||
* 在关闭时销毁抽屉。仅在使用 connectedComponent 时有效
|
||||
*/
|
||||
destroyOnClose?: boolean;
|
||||
/**
|
||||
* 关闭前的回调,返回 false 可以阻止关闭
|
||||
* @returns
|
||||
@@ -138,6 +148,11 @@ export interface DrawerApiOptions extends DrawerState {
|
||||
* 点击取消按钮的回调
|
||||
*/
|
||||
onCancel?: () => void;
|
||||
/**
|
||||
* 弹窗关闭动画结束的回调
|
||||
* @returns
|
||||
*/
|
||||
onClosed?: () => void;
|
||||
/**
|
||||
* 点击确定按钮的回调
|
||||
*/
|
||||
@@ -148,4 +163,9 @@ export interface DrawerApiOptions extends DrawerState {
|
||||
* @returns
|
||||
*/
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
/**
|
||||
* 弹窗打开动画结束的回调
|
||||
* @returns
|
||||
*/
|
||||
onOpened?: () => void;
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
} from '@vben-core/composables';
|
||||
import { X } from '@vben-core/icons';
|
||||
import {
|
||||
Separator,
|
||||
Sheet,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
@@ -33,6 +34,7 @@ interface Props extends DrawerProps {
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
appendToMain: false,
|
||||
closeIconPlacement: 'right',
|
||||
drawerApi: undefined,
|
||||
zIndex: 1000,
|
||||
});
|
||||
@@ -139,10 +141,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
|
||||
@@ -153,11 +157,29 @@ const getAppendTo = computed(() => {
|
||||
headerClass,
|
||||
{
|
||||
'px-4 py-3': closable,
|
||||
'pl-2': closable && closeIconPlacement === 'left',
|
||||
},
|
||||
)
|
||||
"
|
||||
>
|
||||
<div>
|
||||
<div class="flex items-center">
|
||||
<SheetClose
|
||||
v-if="closable && closeIconPlacement === 'left'"
|
||||
as-child
|
||||
class="data-[state=open]:bg-secondary ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
||||
>
|
||||
<slot name="close-icon">
|
||||
<VbenIconButton>
|
||||
<X class="size-4" />
|
||||
</VbenIconButton>
|
||||
</slot>
|
||||
</SheetClose>
|
||||
<Separator
|
||||
v-if="closable && closeIconPlacement === 'left'"
|
||||
class="ml-1 mr-2 h-8"
|
||||
decorative
|
||||
orientation="vertical"
|
||||
/>
|
||||
<SheetTitle v-if="title" class="text-left">
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
@@ -182,13 +204,15 @@ const getAppendTo = computed(() => {
|
||||
<div class="flex-center">
|
||||
<slot name="extra"></slot>
|
||||
<SheetClose
|
||||
v-if="closable"
|
||||
v-if="closable && closeIconPlacement === 'right'"
|
||||
as-child
|
||||
class="data-[state=open]:bg-secondary ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
|
||||
>
|
||||
<VbenIconButton>
|
||||
<X class="size-4" />
|
||||
</VbenIconButton>
|
||||
<slot name="close-icon">
|
||||
<VbenIconButton>
|
||||
<X class="size-4" />
|
||||
</VbenIconButton>
|
||||
</slot>
|
||||
</SheetClose>
|
||||
</div>
|
||||
</SheetHeader>
|
||||
|
@@ -4,7 +4,15 @@ import type {
|
||||
ExtendedDrawerApi,
|
||||
} from './drawer';
|
||||
|
||||
import { defineComponent, h, inject, nextTick, provide, reactive } from 'vue';
|
||||
import {
|
||||
defineComponent,
|
||||
h,
|
||||
inject,
|
||||
nextTick,
|
||||
provide,
|
||||
reactive,
|
||||
ref,
|
||||
} from 'vue';
|
||||
|
||||
import { useStore } from '@vben-core/shared/store';
|
||||
|
||||
@@ -22,6 +30,7 @@ export function useVbenDrawer<
|
||||
const { connectedComponent } = options;
|
||||
if (connectedComponent) {
|
||||
const extendedApi = reactive({});
|
||||
const isDrawerReady = ref(true);
|
||||
const Drawer = defineComponent(
|
||||
(props: TParentDrawerProps, { attrs, slots }) => {
|
||||
provide(USER_DRAWER_INJECT_KEY, {
|
||||
@@ -31,13 +40,23 @@ export function useVbenDrawer<
|
||||
Object.setPrototypeOf(extendedApi, api);
|
||||
},
|
||||
options,
|
||||
async reCreateDrawer() {
|
||||
isDrawerReady.value = false;
|
||||
await nextTick();
|
||||
isDrawerReady.value = true;
|
||||
},
|
||||
});
|
||||
checkProps(extendedApi as ExtendedDrawerApi, {
|
||||
...props,
|
||||
...attrs,
|
||||
...slots,
|
||||
});
|
||||
return () => h(connectedComponent, { ...props, ...attrs }, slots);
|
||||
return () =>
|
||||
h(
|
||||
isDrawerReady.value ? connectedComponent : 'div',
|
||||
{ ...props, ...attrs },
|
||||
slots,
|
||||
);
|
||||
},
|
||||
{
|
||||
inheritAttrs: false,
|
||||
@@ -58,6 +77,14 @@ export function useVbenDrawer<
|
||||
options.onOpenChange?.(isOpen);
|
||||
injectData.options?.onOpenChange?.(isOpen);
|
||||
};
|
||||
|
||||
const onClosed = mergedOptions.onClosed;
|
||||
mergedOptions.onClosed = () => {
|
||||
onClosed?.();
|
||||
if (mergedOptions.destroyOnClose) {
|
||||
injectData.reCreateDrawer?.();
|
||||
}
|
||||
};
|
||||
const api = new DrawerApi(mergedOptions);
|
||||
|
||||
const extendedApi: ExtendedDrawerApi = api as never;
|
||||
|
Reference in New Issue
Block a user