refactor: drawer

pull/6299/head
tangjinzhou 2023-02-20 23:26:23 +08:00
parent c7b15a96a8
commit 8fcb3fdfe3
23 changed files with 610 additions and 757 deletions

View File

@ -27,7 +27,7 @@ export const getTransitionProps = (transitionName: string, opt: TransitionProps
// appearFromClass: `${transitionName}-appear ${transitionName}-appear-prepare`,
// appearActiveClass: `antdv-base-transtion`,
// appearToClass: `${transitionName}-appear ${transitionName}-appear-active`,
enterFromClass: `${transitionName}-enter ${transitionName}-enter-prepare`,
enterFromClass: `${transitionName}-enter ${transitionName}-enter-prepare ${transitionName}-enter-start`,
enterActiveClass: `${transitionName}-enter ${transitionName}-enter-prepare`,
enterToClass: `${transitionName}-enter ${transitionName}-enter-active`,
leaveFromClass: ` ${transitionName}-leave`,

View File

@ -19,12 +19,14 @@ Basic drawer.
<template>
<a-button type="primary" @click="showDrawer">Open</a-button>
<a-drawer
v-model:visible="visible"
v-model:open="open"
class="custom-class"
root-class-name="root-class-name"
:root-style="{ color: 'blue' }"
style="color: red"
title="Basic Drawer"
placement="right"
@after-visible-change="afterVisibleChange"
@after-open-change="afterOpenChange"
>
<p>Some contents...</p>
<p>Some contents...</p>
@ -35,19 +37,19 @@ Basic drawer.
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const visible = ref<boolean>(false);
const open = ref<boolean>(false);
const afterVisibleChange = (bool: boolean) => {
console.log('visible', bool);
const afterOpenChange = (bool: boolean) => {
console.log('open', bool);
};
const showDrawer = () => {
visible.value = true;
open.value = true;
};
return {
visible,
afterVisibleChange,
open,
afterOpenChange,
showDrawer,
};
},

View File

@ -24,13 +24,7 @@ Extra actions should be placed at corner of drawer in Ant Design, you can using
<a-radio value="left">left</a-radio>
</a-radio-group>
<a-button type="primary" @click="showDrawer">Open</a-button>
<a-drawer
:width="500"
title="Basic Drawer"
:placement="placement"
:visible="visible"
@close="onClose"
>
<a-drawer :width="500" title="Basic Drawer" :placement="placement" :open="open" @close="onClose">
<template #extra>
<a-button style="margin-right: 8px" @click="onClose">Cancel</a-button>
<a-button type="primary" @click="onClose">Submit</a-button>
@ -46,18 +40,18 @@ import type { DrawerProps } from 'ant-design-vue';
export default defineComponent({
setup() {
const placement = ref<DrawerProps['placement']>('left');
const visible = ref<boolean>(false);
const open = ref<boolean>(false);
const showDrawer = () => {
visible.value = true;
open.value = true;
};
const onClose = () => {
visible.value = false;
open.value = false;
};
return {
placement,
visible,
open,
showDrawer,
onClose,
};

View File

@ -24,7 +24,7 @@ Use form in drawer with submit button.
<a-drawer
title="Create a new account"
:width="720"
:visible="visible"
:open="open"
:body-style="{ paddingBottom: '80px' }"
:footer-style="{ textAlign: 'right' }"
@close="onClose"
@ -134,19 +134,19 @@ export default defineComponent({
description: [{ required: true, message: 'Please enter url description' }],
};
const visible = ref<boolean>(false);
const open = ref<boolean>(false);
const showDrawer = () => {
visible.value = true;
open.value = true;
};
const onClose = () => {
visible.value = false;
open.value = false;
};
return {
form,
rules,
visible,
open,
showDrawer,
onClose,
};

View File

@ -19,7 +19,7 @@ Open a new drawer on top of an existing drawer to handle multi branch tasks.
<template>
<a-button type="primary" @click="showDrawer">Open</a-button>
<a-drawer
v-model:visible="visible"
v-model:open="open"
title="Multi-level drawer"
width="520"
:closable="false"
@ -27,12 +27,7 @@ Open a new drawer on top of an existing drawer to handle multi branch tasks.
@close="onClose"
>
<a-button type="primary" @click="showChildrenDrawer">Two-level drawer</a-button>
<a-drawer
v-model:visible="childrenDrawer"
title="Two-level Drawer"
width="320"
:closable="false"
>
<a-drawer v-model:open="childrenDrawer" title="Two-level Drawer" width="320" :closable="false">
<a-button type="primary" @click="showChildrenDrawer">This is two-level drawer</a-button>
</a-drawer>
@ -47,21 +42,21 @@ import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const visible = ref<boolean>(false);
const open = ref<boolean>(false);
const childrenDrawer = ref<boolean>(false);
const showDrawer = () => {
visible.value = true;
open.value = true;
};
const onClose = () => {
visible.value = false;
open.value = false;
};
const showChildrenDrawer = () => {
childrenDrawer.value = true;
};
return {
visible,
open,
childrenDrawer,
showDrawer,
onClose,

View File

@ -28,7 +28,7 @@ The Drawer can appear from any edge of the screen.
title="Basic Drawer"
:placement="placement"
:closable="false"
:visible="visible"
:open="open"
@close="onClose"
>
<p>Some contents...</p>
@ -42,18 +42,18 @@ import type { DrawerProps } from 'ant-design-vue';
export default defineComponent({
setup() {
const placement = ref<DrawerProps['placement']>('left');
const visible = ref<boolean>(false);
const open = ref<boolean>(false);
const showDrawer = () => {
visible.value = true;
open.value = true;
};
const onClose = () => {
visible.value = false;
open.value = false;
};
return {
placement,
visible,
open,
showDrawer,
onClose,
};

View File

@ -38,7 +38,7 @@ Render in current dom. custom container, check `getContainer`.
title="Basic Drawer"
placement="right"
:closable="false"
:visible="visible"
:open="open"
:get-container="false"
:style="{ position: 'absolute' }"
@close="onClose"
@ -51,23 +51,23 @@ Render in current dom. custom container, check `getContainer`.
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const visible = ref(false);
const open = ref(false);
const afterVisibleChange = (bool: boolean) => {
console.log('visible', bool);
const afterOpenChange = (bool: boolean) => {
console.log('open', bool);
};
const showDrawer = () => {
visible.value = true;
open.value = true;
};
const onClose = () => {
visible.value = false;
open.value = false;
};
return {
visible,
afterVisibleChange,
open,
afterOpenChange,
showDrawer,
onClose,
};

View File

@ -21,7 +21,7 @@ The default width (or height) of Drawer is `378px`, and there is a presetted lar
Open Default Size (378px)
</a-button>
<a-button type="primary" @click="showDrawer('large')">Open Large Size (736px)</a-button>
<a-drawer title="Basic Drawer" :size="size" :visible="visible" @close="onClose">
<a-drawer title="Basic Drawer" :size="size" :open="open" @close="onClose">
<template #extra>
<a-button style="margin-right: 8px" @click="onClose">Cancel</a-button>
<a-button type="primary" @click="onClose">Submit</a-button>
@ -36,19 +36,19 @@ import { defineComponent, ref } from 'vue';
import type { DrawerProps } from 'ant-design-vue';
export default defineComponent({
setup() {
const visible = ref<boolean>(false);
const open = ref<boolean>(false);
const size = ref<DrawerProps['size']>('default');
const showDrawer = (val: DrawerProps['size']) => {
size.value = val;
visible.value = true;
open.value = true;
};
const onClose = () => {
visible.value = false;
open.value = false;
};
return {
visible,
open,
size,
showDrawer,
onClose,

View File

@ -42,7 +42,7 @@ Use Drawer to quickly preview details of an object, such as those in a list.
</a-list-item>
</template>
</a-list>
<a-drawer width="640" placement="right" :closable="false" :visible="visible" @close="onClose">
<a-drawer width="640" placement="right" :closable="false" :open="open" @close="onClose">
<p :style="[pStyle, pStyle2]">User Profile</p>
<p :style="pStyle">Personal</p>
<a-row>
@ -136,7 +136,7 @@ export default defineComponent({
descriptionItem,
},
setup() {
const visible = ref<boolean>(false);
const open = ref<boolean>(false);
const pStyle = {
fontSize: '16px',
color: 'rgba(0,0,0,0.85)',
@ -149,13 +149,13 @@ export default defineComponent({
};
const showDrawer = () => {
visible.value = true;
open.value = true;
};
const onClose = () => {
visible.value = false;
open.value = false;
};
return {
visible,
open,
pStyle,
pStyle2,
showDrawer,

View File

@ -2,7 +2,7 @@
category: Components
type: Feedback
title: Drawer
cover: https://img.alicdn.com/imgextra/i4/O1CN019djdZP1OHwXSRGCOW_!!6000000001681-55-tps-161-117.svg
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*v3TvSq2E0HAAAAAAAAAAAAAADrJ8AQ/original
---
A panel which slides in from the edge of the screen.
@ -17,16 +17,17 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
## API
**🚨 Note:** v4 use `rootClassName` & `rootStyle` to config wrapper style instead of `class` & `style` in v4 to align the API with Modal.
| Props | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| autofocus | Whether Drawer should get focused after open | boolean | true | 3.0.0 |
| bodyStyle | Style of the drawer content part | CSSProperties | - | |
| class | The class name of the container of the Drawer dialog | string | - | |
| class | Config Drawer Panel className. Use `rootClassName` if want to config top dom style | string | - | |
| closable | Whether a close (x) button is visible on top left of the Drawer dialog or not | boolean | true | |
| closeIcon | Custom close icon | VNode \| slot | `<CloseOutlined />` | 3.0.0 |
| contentWrapperStyle | Style of the drawer wrapper of content part | CSSProperties | - | 3.0.0 |
| destroyOnClose | Whether to unmount child components on closing drawer or not | boolean | false | |
| drawerStyle | Style of the popup layer element | CSSProperties | - | |
| extra | Extra actions area at corner | VNode \| slot | - | 3.0.0 |
| footer | The footer for Drawer | VNode \| slot | - | 3.0.0 |
| footerStyle | Style of the drawer footer part | CSSProperties | - | 3.0.0 |
@ -40,10 +41,13 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
| maskStyle | Style for Drawer's mask element | CSSProperties | {} | |
| placement | The placement of the Drawer | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | |
| push | Nested drawers push behavior | boolean \| {distance: string \| number} | { distance: 180 } | 3.0.0 |
| rootClassName | The class name of the container of the Drawer dialog | string | - | 4.0 |
| rootStyle | Style of wrapper element which **contains mask** compare to `style` | CSSProperties | - | 4.0 |
| style | Style of Drawer panel. Use `bodyStyle` if want to config body only | CSSProperties | - | |
| size | presetted size of drawer, default `378px` and large `736px` | `default` \| `large` | `default` | 3.0.0 |
| style | Style of wrapper element which contains mask compare to drawerStyle | CSSProperties | - | |
| title | The title for Drawer | string \| slot | - | |
| visible(v-model) | Whether the Drawer dialog is visible or not | boolean | - | |
| open(v-model) | Whether the Drawer dialog is visible or not | boolean | - | 4.0 |
| width | Width of the Drawer dialog | string \| number | 378 | |
| zIndex | The `z-index` of the Drawer | Number | 1000 | |
@ -51,5 +55,5 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
| Name | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| afterVisibleChange | Callback after the animation ends when switching drawers. | function(visible) | - | |
| afterOpenChange | Callback after the animation ends when switching drawers. | function(open) | - | 4.0 |
| close | Specify a callback that will be called when a user clicks mask, close button or Cancel button. | function(e) | - | |

View File

@ -16,17 +16,22 @@ import VcDrawer from '../vc-drawer';
import PropTypes from '../_util/vue-types';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import { tuple, withInstall } from '../_util/type';
import { objectType, withInstall } from '../_util/type';
import omit from '../_util/omit';
import devWarning from '../vc-util/devWarning';
import type { KeyboardEventHandler, MouseEventHandler } from '../_util/EventInterface';
import useStyle from './style';
import { NoCompactStyle } from '../space/Compact';
import isNumeric from '../_util/isNumeric';
import { getTransitionName, getTransitionProps } from '../_util/transition';
type ILevelMove = number | [number, number];
const PlacementTypes = tuple('top', 'right', 'bottom', 'left');
const PlacementTypes = ['top', 'right', 'bottom', 'left'] as const;
export type placementType = (typeof PlacementTypes)[number];
const SizeTypes = tuple('default', 'large');
const SizeTypes = ['default', 'large'] as const;
export type sizeType = (typeof SizeTypes)[number];
export interface PushState {
@ -49,25 +54,23 @@ export const drawerProps = () => ({
},
maskClosable: { type: Boolean, default: undefined },
mask: { type: Boolean, default: undefined },
maskStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
/** @deprecated Use `style` instead */
wrapStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
style: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
class: PropTypes.any,
/** @deprecated Use `class` instead */
wrapClassName: String,
maskStyle: objectType<CSSProperties>(),
rootClassName: String,
rootStyle: objectType<CSSProperties>(),
size: {
type: String as PropType<sizeType>,
},
drawerStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
headerStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
bodyStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
drawerStyle: objectType<CSSProperties>(),
headerStyle: objectType<CSSProperties>(),
bodyStyle: objectType<CSSProperties>(),
contentWrapperStyle: {
type: Object as PropType<CSSProperties>,
default: undefined as CSSProperties,
},
title: PropTypes.any,
/** @deprecated Please use `open` instead */
visible: { type: Boolean, default: undefined },
open: { type: Boolean, default: undefined },
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
zIndex: Number,
@ -77,7 +80,7 @@ export const drawerProps = () => ({
keyboard: { type: Boolean, default: undefined },
extra: PropTypes.any,
footer: PropTypes.any,
footerStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
footerStyle: objectType<CSSProperties>(),
level: PropTypes.any,
levelMove: {
type: [Number, Array, Function] as PropType<
@ -87,8 +90,12 @@ export const drawerProps = () => ({
handle: PropTypes.any,
/** @deprecated Use `@afterVisibleChange` instead */
afterVisibleChange: Function as PropType<(visible: boolean) => void>,
/** @deprecated Please use `@afterOpenChange` instead */
onAfterVisibleChange: Function as PropType<(visible: boolean) => void>,
onAfterOpenChange: Function as PropType<(open: boolean) => void>,
/** @deprecated Please use `onUpdate:open` instead */
'onUpdate:visible': Function as PropType<(visible: boolean) => void>,
'onUpdate:open': Function as PropType<(open: boolean) => void>,
onClose: Function as PropType<MouseEventHandler | KeyboardEventHandler>,
});
@ -100,7 +107,7 @@ const Drawer = defineComponent({
inheritAttrs: false,
props: initDefaultProps(drawerProps(), {
closable: true,
placement: 'right' as placementType,
placement: 'right',
maskClosable: true,
mask: true,
level: null,
@ -115,11 +122,11 @@ const Drawer = defineComponent({
const vcDrawer = ref(null);
const load = ref(false);
const visible = ref(false);
const mergedOpen = computed(() => props.open ?? props.visible);
watch(
() => props.visible,
propsVisible => {
if (propsVisible) {
mergedOpen,
() => {
if (mergedOpen.value) {
load.value = true;
} else {
visible.value = false;
@ -128,9 +135,9 @@ const Drawer = defineComponent({
{ immediate: true },
);
watch(
[() => props.visible, load],
([propsVisible]) => {
if (propsVisible && load.value) {
[mergedOpen, load],
() => {
if (mergedOpen.value && load.value) {
visible.value = true;
}
},
@ -138,6 +145,7 @@ const Drawer = defineComponent({
);
const parentDrawerOpts = inject('parentDrawerOpts', null);
const { prefixCls, getPopupContainer, direction } = useConfigInject('drawer', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const getContainer = computed(() =>
// false
props.getContainer === undefined && getPopupContainer.value
@ -150,16 +158,21 @@ const Drawer = defineComponent({
'Drawer',
'`afterVisibleChange` prop is deprecated, please use `@afterVisibleChange` event instead',
);
devWarning(
props.wrapStyle === undefined,
'Drawer',
'`wrapStyle` prop is deprecated, please use `style` instead',
);
devWarning(
props.wrapClassName === undefined,
'Drawer',
'`wrapClassName` prop is deprecated, please use `class` instead',
);
// ========================== Warning ===========================
if (process.env.NODE_ENV !== 'production') {
[
['visible', 'open'],
['onUpdate:visible', 'onUpdate:open'],
['onAfterVisibleChange', 'onAfterOpenChange'],
].forEach(([deprecatedName, newName]) => {
devWarning(
!props[deprecatedName],
'Drawer',
`\`${deprecatedName}\` is deprecated, please use \`${newName}\` instead.`,
);
});
}
const setPush = () => {
sPush.value = true;
};
@ -176,7 +189,7 @@ const Drawer = defineComponent({
});
onMounted(() => {
if (props.visible && parentDrawerOpts) {
if (mergedOpen.value && parentDrawerOpts) {
parentDrawerOpts.setPush();
}
});
@ -207,6 +220,7 @@ const Drawer = defineComponent({
const close = (e: Event) => {
emit('update:visible', false);
emit('update:open', false);
emit('close', e);
};
@ -222,6 +236,7 @@ const Drawer = defineComponent({
}
props.afterVisibleChange?.(open);
emit('afterVisibleChange', open);
emit('afterOpenChange', open);
};
const pushTransform = computed(() => {
@ -243,36 +258,28 @@ const Drawer = defineComponent({
}
return null;
});
// ============================ Size ============================
const mergedWidth = computed(() => props.width ?? (props.size === 'large' ? 736 : 378));
const mergedHeight = computed(() => props.height ?? (props.size === 'large' ? 736 : 378));
const offsetStyle = computed(() => {
// https://github.com/ant-design/ant-design/issues/24287
const { mask, placement, size = 'default', width, height } = props;
const { mask, placement } = props;
if (!visible.value && !mask) {
return {};
}
const val: CSSProperties = {};
if (placement === 'left' || placement === 'right') {
const defaultWidth = size === 'large' ? 736 : 378;
val.width = typeof width === 'undefined' ? defaultWidth : width;
val.width = typeof val.width === 'string' ? val.width : `${val.width}px`;
val.width = isNumeric(mergedWidth.value) ? `${mergedWidth.value}px` : mergedWidth.value;
} else {
const defaultHeight = size === 'large' ? 736 : 378;
val.height = typeof height === 'undefined' ? defaultHeight : height;
val.height = typeof val.height === 'string' ? val.height : `${val.height}px`;
val.height = isNumeric(mergedHeight.value) ? `${mergedHeight.value}px` : mergedHeight.value;
}
return val;
});
const drawerStyle = computed(() => {
const { zIndex, wrapStyle, mask, style } = props;
const val = mask ? {} : offsetStyle.value;
return {
zIndex,
transform: sPush.value ? pushTransform.value : undefined,
...val,
...wrapStyle,
...style,
};
const wrapperStyle = computed(() => {
const { zIndex } = props;
const val = offsetStyle.value;
return [{ zIndex, transform: sPush.value ? pushTransform.value : undefined }, val];
});
const renderHeader = (prefixCls: string) => {
@ -342,21 +349,29 @@ const Drawer = defineComponent({
</div>
);
};
const drawerClassName = computed(() =>
classnames(
{
'no-mask': !props.mask,
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
},
props.rootClassName,
hashId.value,
),
);
// =========================== Motion ===========================
const maskMotion = computed(() => {
return getTransitionProps(getTransitionName(prefixCls.value, 'mask-motion'));
});
const panelMotion = (motionPlacement: string) => {
return getTransitionProps(
getTransitionName(prefixCls.value, `panel-motion-${motionPlacement}`),
);
};
return () => {
const {
width,
height,
placement,
mask,
wrapClassName,
class: className,
forceRender,
...rest
} = props;
const { width, height, placement, mask, forceRender, ...rest } = props;
const val = mask ? offsetStyle.value : {};
const haveMask = mask ? '' : 'no-mask';
const vcDrawerProps: any = {
...attrs,
...omit(rest, [
@ -369,13 +384,12 @@ const Drawer = defineComponent({
'bodyStyle',
'title',
'push',
'wrapStyle',
'onAfterVisibleChange',
'onClose',
'onUpdate:visible',
'onUpdate:open',
'visible',
]),
...val,
forceRender,
onClose: close,
afterVisibleChange,
@ -384,24 +398,26 @@ const Drawer = defineComponent({
open: visible.value,
showMask: mask,
placement,
class: classnames({
[className]: className,
[wrapClassName]: !!wrapClassName,
[haveMask]: !!haveMask,
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
}),
style: drawerStyle.value,
ref: vcDrawer,
};
return (
<VcDrawer
{...vcDrawerProps}
getContainer={getContainer.value}
v-slots={{
handler: props.handle ? () => props.handle : slots.handle,
default: () => renderBody(prefixCls.value),
}}
></VcDrawer>
return wrapSSR(
<NoCompactStyle>
<VcDrawer
{...vcDrawerProps}
maskMotion={maskMotion.value}
motion={panelMotion}
width={mergedWidth.value}
height={mergedHeight.value}
getContainer={getContainer.value}
rootClassName={drawerClassName.value}
rootStyle={props.rootStyle}
contentWrapperStyle={wrapperStyle.value}
v-slots={{
handler: props.handle ? () => props.handle : slots.handle,
default: () => renderBody(prefixCls.value),
}}
></VcDrawer>
</NoCompactStyle>,
);
};
},

View File

@ -3,7 +3,7 @@ category: Components
type: 反馈
title: Drawer
subtitle: 抽屉
cover: https://img.alicdn.com/imgextra/i4/O1CN019djdZP1OHwXSRGCOW_!!6000000001681-55-tps-161-117.svg
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*v3TvSq2E0HAAAAAAAAAAAAAADrJ8AQ/original
---
屏幕边缘滑出的浮层面板。
@ -17,16 +17,17 @@ cover: https://img.alicdn.com/imgextra/i4/O1CN019djdZP1OHwXSRGCOW_!!600000000168
## API
**🚨 注意:** v4 使用 `rootClassName``rootStyle` 来配置最外层元素样式。原 v4 `class``style` 改至配置 Drawer 窗体样式以和 Modal 对齐。
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| autofocus | 抽屉展开后是否将焦点切换至其 Dom 节点 | boolean | true | 3.0.0 |
| bodyStyle | 可用于设置 Drawer 内容部分的样式 | CSSProperties | - | |
| class | 对话框外层容器的类名 | string | - | |
| class | Drawer 容器外层 className 设置,如果需要设置最外层,请使用 rootClassName | string | - | |
| closable | 是否显示左上角的关闭按钮 | boolean | true | |
| closeIcon | 自定义关闭图标 | VNode \| slot | `<CloseOutlined />` | 3.0.0 |
| contentWrapperStyle | 可用于设置 Drawer 包裹内容部分的样式 | CSSProperties | - | 3.0.0 |
| destroyOnClose | 关闭时销毁 Drawer 里的子元素 | boolean | false | |
| drawerStyle | 用于设置 Drawer 弹出层的样式 | object | - | |
| extra | 抽屉右上角的操作区域 | VNode \| slot | - | 3.0.0 |
| footer | 抽屉的页脚 | VNode \| slot | - | 3.0.0 |
| footerStyle | 抽屉页脚部件的样式 | CSSProperties | - | 3.0.0 |
@ -40,16 +41,18 @@ cover: https://img.alicdn.com/imgextra/i4/O1CN019djdZP1OHwXSRGCOW_!!600000000168
| maskStyle | 遮罩样式 | CSSProperties | {} | |
| placement | 抽屉的方向 | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | |
| push | 用于设置多层 Drawer 的推动行为 | boolean \| {distance: string \| number} | { distance: 180 } | 3.0.0 |
| rootClassName | 对话框外层容器的类名 | string | - | 4.0 |
| rootStyle | 可用于设置 Drawer 最外层容器的样式,和 `style` 的区别是作用节点包括 `mask` | CSSProperties | - | 4.0 |
| size | 预设抽屉宽度或高度default `378px` 和 large `736px` | `default` \| `large` | `default` | 3.0.0 |
| style | 可用于设置 Drawer 最外层容器的样式,和 `drawerStyle` 的区别是作用节点包括 `mask` | CSSProperties | - | |
| style | 设计 Drawer 容器样式,如果你只需要设置内容部分请使用 `bodyStyle` | CSSProperties | - | |
| title | 标题 | string \| slot | - | |
| visible(v-model) | Drawer 是否可见 | boolean | - | |
| open(v-model) | Drawer 是否可见 | boolean | - | 4.0 |
| width | 宽度 | string \| number | 378 | |
| zIndex | 设置 Drawer 的 `z-index` | Number | 1000 | |
## 事件
| 名称 | 描述 | 类型 | 默认值 | 版本 |
| ------------------ | ------------------------------------ | ----------------- | ------ | ---- |
| afterVisibleChange | 切换抽屉时动画结束后的回调 | function(visible) | 无 | |
| close | 点击遮罩层或左上角叉或取消按钮的回调 | function(e) | 无 | |
| 名称 | 描述 | 类型 | 默认值 | 版本 |
| --------------- | ------------------------------------ | -------------- | ------ | ---- |
| afterOpenChange | 切换抽屉时动画结束后的回调 | function(open) | 无 | 4.0 |
| close | 点击遮罩层或左上角叉或取消按钮的回调 | function(e) | 无 | |

View File

@ -1,249 +0,0 @@
@import '../../style/themes/index';
@drawer-prefix-cls: ~'@{ant-prefix}-drawer';
@picker-prefix-cls: ~'@{ant-prefix}-picker';
@drawer-animation-ease: @ease-out-quint;
.@{drawer-prefix-cls} {
@drawer-header-close-padding: ceil(((@drawer-header-close-size - @font-size-lg) / 2));
position: fixed;
z-index: @zindex-modal;
width: 0%;
height: 100%;
transition: width 0s ease @animation-duration-slow, height 0s ease @animation-duration-slow;
&-content-wrapper {
position: absolute;
width: 100%;
height: 100%;
transition: transform @animation-duration-slow @drawer-animation-ease,
box-shadow @animation-duration-slow @drawer-animation-ease;
}
.@{drawer-prefix-cls}-content {
width: 100%;
height: 100%;
}
&-left,
&-right {
top: 0;
width: 0%;
height: 100%;
.@{drawer-prefix-cls}-content-wrapper {
height: 100%;
}
&.@{drawer-prefix-cls}-open {
width: 100%;
transition: transform @animation-duration-slow @drawer-animation-ease;
}
}
&-left {
left: 0;
.@{drawer-prefix-cls} {
&-content-wrapper {
left: 0;
}
}
&.@{drawer-prefix-cls}-open {
.@{drawer-prefix-cls}-content-wrapper {
box-shadow: @shadow-1-right;
}
}
}
&-right {
right: 0;
.@{drawer-prefix-cls} {
&-content-wrapper {
right: 0;
}
}
&.@{drawer-prefix-cls}-open {
.@{drawer-prefix-cls}-content-wrapper {
box-shadow: @shadow-1-left;
}
// https://github.com/ant-design/ant-design/issues/18607, Avoid edge alignment bug.
&.no-mask {
right: 1px;
transform: translateX(1px);
}
}
}
&-top,
&-bottom {
left: 0;
width: 100%;
height: 0%;
.@{drawer-prefix-cls}-content-wrapper {
width: 100%;
}
&.@{drawer-prefix-cls}-open {
height: 100%;
transition: transform @animation-duration-slow @drawer-animation-ease;
}
}
&-top {
top: 0;
&.@{drawer-prefix-cls}-open {
.@{drawer-prefix-cls}-content-wrapper {
box-shadow: @shadow-1-down;
}
}
}
&-bottom {
bottom: 0;
.@{drawer-prefix-cls} {
&-content-wrapper {
bottom: 0;
}
}
&.@{drawer-prefix-cls}-open {
.@{drawer-prefix-cls}-content-wrapper {
box-shadow: @shadow-1-up;
}
&.no-mask {
bottom: 1px;
transform: translateY(1px);
}
}
}
&.@{drawer-prefix-cls}-open .@{drawer-prefix-cls}-mask {
height: 100%;
opacity: 1;
transition: none;
animation: antdDrawerFadeIn @animation-duration-slow @drawer-animation-ease;
pointer-events: auto;
}
&-title {
flex: 1;
margin: 0;
color: @heading-color;
font-weight: 500;
font-size: @drawer-title-font-size;
line-height: @drawer-title-line-height;
}
&-content {
position: relative;
z-index: 1;
overflow: auto;
background-color: @drawer-bg;
background-clip: padding-box;
border: 0;
}
&-close {
display: inline-block;
margin-right: 12px;
color: @modal-close-color;
font-weight: 700;
font-size: @font-size-lg;
font-style: normal;
line-height: 1;
text-align: center;
text-transform: none;
text-decoration: none;
background: transparent;
border: 0;
outline: 0;
cursor: pointer;
transition: color @animation-duration-slow;
text-rendering: auto;
&:focus,
&:hover {
color: @icon-color-hover;
text-decoration: none;
}
}
&-header {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: @drawer-header-padding;
color: @text-color;
background: @drawer-bg;
border-bottom: @border-width-base @border-style-base @border-color-split;
border-radius: @border-radius-base @border-radius-base 0 0;
&-title {
display: flex;
flex: 1;
align-items: center;
justify-content: space-between;
}
&-close-only {
padding-bottom: 0;
border: none;
}
}
&-wrapper-body {
display: flex;
flex-flow: column nowrap;
width: 100%;
height: 100%;
}
&-body {
flex-grow: 1;
padding: @drawer-body-padding;
overflow: auto;
font-size: @font-size-base;
line-height: @line-height-base;
word-wrap: break-word;
}
&-footer {
flex-shrink: 0;
padding: @drawer-footer-padding-vertical @drawer-footer-padding-horizontal;
border-top: @border-width-base @border-style-base @border-color-split;
}
&-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 0;
background-color: @modal-mask-bg;
opacity: 0;
transition: opacity @animation-duration-slow linear, height 0s ease @animation-duration-slow;
pointer-events: none;
}
// =================== Hook Components ===================
.@{picker-prefix-cls} {
&-clear {
background: @popover-background;
}
}
}
@keyframes antdDrawerFadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

View File

@ -1,6 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './drawer';
@import './rtl';
.popover-customize-bg(@drawer-prefix-cls, @popover-background);

View File

@ -0,0 +1,230 @@
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import genMotionStyle from './motion';
export interface ComponentToken {
zIndexPopup: number;
}
export interface DrawerToken extends FullToken<'Drawer'> {
drawerFooterPaddingVertical: number;
drawerFooterPaddingHorizontal: number;
}
// =============================== Base ===============================
const genDrawerStyle: GenerateStyle<DrawerToken> = (token: DrawerToken) => {
const {
componentCls,
zIndexPopup,
colorBgMask,
colorBgElevated,
motionDurationSlow,
motionDurationMid,
padding,
paddingLG,
fontSizeLG,
lineHeightLG,
lineWidth,
lineType,
colorSplit,
marginSM,
colorIcon,
colorIconHover,
colorText,
fontWeightStrong,
drawerFooterPaddingVertical,
drawerFooterPaddingHorizontal,
} = token;
const wrapperCls = `${componentCls}-content-wrapper`;
return {
[componentCls]: {
position: 'fixed',
inset: 0,
zIndex: zIndexPopup,
pointerEvents: 'none',
'&-pure': {
position: 'relative',
background: colorBgElevated,
[`&${componentCls}-left`]: {
boxShadow: token.boxShadowDrawerLeft,
},
[`&${componentCls}-right`]: {
boxShadow: token.boxShadowDrawerRight,
},
[`&${componentCls}-top`]: {
boxShadow: token.boxShadowDrawerUp,
},
[`&${componentCls}-bottom`]: {
boxShadow: token.boxShadowDrawerDown,
},
},
'&-inline': {
position: 'absolute',
},
// ====================== Mask ======================
[`${componentCls}-mask`]: {
position: 'absolute',
inset: 0,
zIndex: zIndexPopup,
background: colorBgMask,
pointerEvents: 'auto',
},
// ==================== Content =====================
[wrapperCls]: {
position: 'absolute',
zIndex: zIndexPopup,
transition: `all ${motionDurationSlow}`,
'&-hidden': {
display: 'none',
},
},
// Placement
[`&-left > ${wrapperCls}`]: {
top: 0,
bottom: 0,
left: {
_skip_check_: true,
value: 0,
},
boxShadow: token.boxShadowDrawerLeft,
},
[`&-right > ${wrapperCls}`]: {
top: 0,
right: {
_skip_check_: true,
value: 0,
},
bottom: 0,
boxShadow: token.boxShadowDrawerRight,
},
[`&-top > ${wrapperCls}`]: {
top: 0,
insetInline: 0,
boxShadow: token.boxShadowDrawerUp,
},
[`&-bottom > ${wrapperCls}`]: {
bottom: 0,
insetInline: 0,
boxShadow: token.boxShadowDrawerDown,
},
[`${componentCls}-content`]: {
width: '100%',
height: '100%',
overflow: 'auto',
background: colorBgElevated,
pointerEvents: 'auto',
},
// ===================== Panel ======================
[`${componentCls}-wrapper-body`]: {
display: 'flex',
flexDirection: 'column',
width: '100%',
height: '100%',
},
// Header
[`${componentCls}-header`]: {
display: 'flex',
flex: 0,
alignItems: 'center',
padding: `${padding}px ${paddingLG}px`,
fontSize: fontSizeLG,
lineHeight: lineHeightLG,
borderBottom: `${lineWidth}px ${lineType} ${colorSplit}`,
'&-title': {
display: 'flex',
flex: 1,
alignItems: 'center',
minWidth: 0,
minHeight: 0,
},
},
[`${componentCls}-extra`]: {
flex: 'none',
},
[`${componentCls}-close`]: {
display: 'inline-block',
marginInlineEnd: marginSM,
color: colorIcon,
fontWeight: fontWeightStrong,
fontSize: fontSizeLG,
fontStyle: 'normal',
lineHeight: 1,
textAlign: 'center',
textTransform: 'none',
textDecoration: 'none',
background: 'transparent',
border: 0,
outline: 0,
cursor: 'pointer',
transition: `color ${motionDurationMid}`,
textRendering: 'auto',
'&:focus, &:hover': {
color: colorIconHover,
textDecoration: 'none',
},
},
[`${componentCls}-title`]: {
flex: 1,
margin: 0,
color: colorText,
fontWeight: token.fontWeightStrong,
fontSize: fontSizeLG,
lineHeight: lineHeightLG,
},
// Body
[`${componentCls}-body`]: {
flex: 1,
minWidth: 0,
minHeight: 0,
padding: paddingLG,
overflow: 'auto',
},
// Footer
[`${componentCls}-footer`]: {
flexShrink: 0,
padding: `${drawerFooterPaddingVertical}px ${drawerFooterPaddingHorizontal}px`,
borderTop: `${lineWidth}px ${lineType} ${colorSplit}`,
},
// ====================== RTL =======================
'&-rtl': {
direction: 'rtl',
},
},
};
};
// ============================== Export ==============================
export default genComponentStyleHook(
'Drawer',
token => {
const drawerToken = mergeToken<DrawerToken>(token, {
drawerFooterPaddingVertical: token.paddingXS,
drawerFooterPaddingHorizontal: token.padding,
});
return [genDrawerStyle(drawerToken), genMotionStyle(drawerToken)];
},
token => ({
zIndexPopup: token.zIndexPopupBase,
}),
);

View File

@ -1,3 +0,0 @@
// deps-lint-skip: empty
import '../../style/index.less';
import './index.less';

View File

@ -0,0 +1,134 @@
import type { DrawerToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
const genMotionStyle: GenerateStyle<DrawerToken> = (token: DrawerToken) => {
const { componentCls, motionDurationSlow } = token;
const sharedPanelMotion = {
'&-enter, &-appear, &-leave': {
'&-start': {
transition: 'none',
},
'&-active': {
transition: `all ${motionDurationSlow}`,
},
},
};
return {
[componentCls]: {
// ======================== Mask ========================
[`${componentCls}-mask-motion`]: {
'&-enter, &-appear, &-leave': {
'&-active': {
transition: `all ${motionDurationSlow}`,
},
},
'&-enter, &-appear': {
opacity: 0,
'&-active': {
opacity: 1,
},
},
'&-leave': {
opacity: 1,
'&-active': {
opacity: 0,
},
},
},
// ======================= Panel ========================
[`${componentCls}-panel-motion`]: {
// Left
'&-left': [
sharedPanelMotion,
{
'&-enter, &-appear': {
'&-start': {
transform: 'translateX(-100%) !important',
},
'&-active': {
transform: 'translateX(0)',
},
},
'&-leave': {
transform: 'translateX(0)',
'&-active': {
transform: 'translateX(-100%)',
},
},
},
],
// Right
'&-right': [
sharedPanelMotion,
{
'&-enter, &-appear': {
'&-start': {
transform: 'translateX(100%) !important',
},
'&-active': {
transform: 'translateX(0)',
},
},
'&-leave': {
transform: 'translateX(0)',
'&-active': {
transform: 'translateX(100%)',
},
},
},
],
// Top
'&-top': [
sharedPanelMotion,
{
'&-enter, &-appear': {
'&-start': {
transform: 'translateY(-100%) !important',
},
'&-active': {
transform: 'translateY(0)',
},
},
'&-leave': {
transform: 'translateY(0)',
'&-active': {
transform: 'translateY(-100%)',
},
},
},
],
// Bottom
'&-bottom': [
sharedPanelMotion,
{
'&-enter, &-appear': {
'&-start': {
transform: 'translateY(100%) !important',
},
'&-active': {
transform: 'translateY(0)',
},
},
'&-leave': {
transform: 'translateY(0)',
'&-active': {
transform: 'translateY(100%)',
},
},
},
],
},
},
};
};
export default genMotionStyle;

View File

@ -1,16 +0,0 @@
@import '../../style/themes/index';
@drawer-prefix-cls: ~'@{ant-prefix}-drawer';
.@{drawer-prefix-cls} {
&-rtl {
direction: rtl;
}
&-close {
.@{drawer-prefix-cls}-rtl & {
margin-right: 0;
margin-left: 12px;
}
}
}

View File

@ -48,7 +48,7 @@ import './radio/style';
// import './anchor/style';
// import './list/style';
// import './tree-select/style';
import './drawer/style';
// import './drawer/style';
// import './skeleton/style';
// import './comment/style';
// import './config-provider/style';

View File

@ -13,7 +13,7 @@ import type { ComponentToken as CollapseComponentToken } from '../../collapse/st
import type { ComponentToken as DatePickerComponentToken } from '../../date-picker/style';
import type { ComponentToken as DividerComponentToken } from '../../divider/style';
import type { ComponentToken as DropdownComponentToken } from '../../dropdown/style';
// import type { ComponentToken as DrawerComponentToken } from '../../drawer/style';
import type { ComponentToken as DrawerComponentToken } from '../../drawer/style';
import type { ComponentToken as EmptyComponentToken } from '../../empty/style';
import type { ComponentToken as ImageComponentToken } from '../../image/style';
import type { ComponentToken as InputNumberComponentToken } from '../../input-number/style';
@ -69,7 +69,7 @@ export interface ComponentTokenMap {
DatePicker?: DatePickerComponentToken;
Descriptions?: {};
Divider?: DividerComponentToken;
// Drawer?: DrawerComponentToken;
Drawer?: DrawerComponentToken;
Dropdown?: DropdownComponentToken;
Empty?: EmptyComponentToken;
// FloatButton?: FloatButtonComponentToken;

View File

@ -1,30 +1,9 @@
import {
defineComponent,
reactive,
onMounted,
computed,
onUnmounted,
nextTick,
watch,
ref,
} from 'vue';
import { Transition, defineComponent, onMounted, onUnmounted, nextTick, watch, ref } from 'vue';
import classnames from '../../_util/classNames';
import getScrollBarSize from '../../_util/getScrollBarSize';
import KeyCode from '../../_util/KeyCode';
import omit from '../../_util/omit';
import supportsPassive from '../../_util/supportsPassive';
import { drawerChildProps } from './IDrawerPropTypes';
import {
addEventListener,
dataToArray,
getTouchParentScroll,
isNumeric,
removeEventListener,
transformArguments,
transitionEndFun,
windowIsUndefined,
} from './utils';
import { dataToArray, windowIsUndefined } from './utils';
const currentDrawer: Record<string, boolean> = {};
@ -32,23 +11,12 @@ export interface scrollLockOptions {
container: HTMLElement;
}
interface Point {
x: number;
y: number;
}
const DrawerChild = defineComponent({
compatConfig: { MODE: 3 },
inheritAttrs: false,
props: drawerChildProps(),
emits: ['close', 'handleClick', 'change'],
setup(props, { emit, slots }) {
const state = reactive({
startPos: {
x: null,
y: null,
} as Point | null,
});
let timeout;
const contentWrapper = ref<HTMLElement>();
const dom = ref<HTMLElement>();
const maskDom = ref<HTMLElement>();
@ -61,8 +29,6 @@ const DrawerChild = defineComponent({
.replace('.', Math.round(Math.random() * 9).toString()),
).toString(16)}`;
const passive = !windowIsUndefined && supportsPassive ? { passive: false } : false;
onMounted(() => {
nextTick(() => {
const { open, getContainer, showMask, autofocus } = props;
@ -72,8 +38,6 @@ const DrawerChild = defineComponent({
if (container && container.parentNode === document.body) {
currentDrawer[drawerId] = open;
}
// level;
openLevelTransition();
nextTick(() => {
if (autofocus) {
domFocus();
@ -100,7 +64,6 @@ const DrawerChild = defineComponent({
if (container && container.parentNode === document.body) {
currentDrawer[drawerId] = !!open;
}
openLevelTransition();
if (open) {
if (autofocus) {
domFocus();
@ -119,7 +82,6 @@ const DrawerChild = defineComponent({
const { open } = props;
delete currentDrawer[drawerId];
if (open) {
setLevelTransform(false);
document.body.style.touchAction = '';
}
props.scrollLocker?.unLock();
@ -139,42 +101,6 @@ const DrawerChild = defineComponent({
dom.value?.focus?.();
};
const removeStartHandler = (e: TouchEvent) => {
if (e.touches.length > 1) {
// need clear the startPos when another touch event happens
state.startPos = null;
return;
}
state.startPos = {
x: e.touches[0].clientX,
y: e.touches[0].clientY,
};
};
const removeMoveHandler = (e: TouchEvent) => {
// the startPos may be null or undefined
if (e.changedTouches.length > 1 || !state.startPos) {
return;
}
const currentTarget = e.currentTarget as HTMLElement;
const differX = e.changedTouches[0].clientX - state.startPos.x;
const differY = e.changedTouches[0].clientY - state.startPos.y;
if (
(currentTarget === maskDom.value ||
currentTarget === handlerDom.value ||
(currentTarget === contentDom.value &&
getTouchParentScroll(currentTarget, e.target as HTMLElement, differX, differY))) &&
e.cancelable
) {
e.preventDefault();
}
};
const transitionEnd = (e: TransitionEvent) => {
const dom: HTMLElement = e.target as HTMLElement;
removeEventListener(dom, transitionEndFun, transitionEnd);
dom.style.transition = '';
};
const onClose = (e: Event) => {
emit('close', e);
};
@ -186,208 +112,13 @@ const DrawerChild = defineComponent({
}
};
const onWrapperTransitionEnd = (e: TransitionEvent) => {
const onAfterVisibleChange = () => {
const { open, afterVisibleChange } = props;
if (e.target === contentWrapper.value && e.propertyName.match(/transform$/)) {
dom.value.style.transition = '';
if (!open && getCurrentDrawerSome()) {
document.body.style.overflowX = '';
if (maskDom.value) {
maskDom.value.style.left = '';
maskDom.value.style.width = '';
}
}
if (afterVisibleChange) {
afterVisibleChange(!!open);
}
if (afterVisibleChange) {
afterVisibleChange(!!open);
}
};
const horizontalBoolAndPlacementName = computed(() => {
const { placement } = props;
const isHorizontal = placement === 'left' || placement === 'right';
const placementName = `translate${isHorizontal ? 'X' : 'Y'}`;
return {
isHorizontal,
placementName,
};
});
const openLevelTransition = () => {
const { open, width, height } = props;
const { isHorizontal, placementName } = horizontalBoolAndPlacementName.value;
const contentValue = contentDom.value
? contentDom.value.getBoundingClientRect()[isHorizontal ? 'width' : 'height']
: 0;
const value = (isHorizontal ? width : height) || contentValue;
setLevelAndScrolling(open, placementName, value);
};
const setLevelTransform = (
open?: boolean,
placementName?: string,
value?: string | number,
right?: number,
) => {
const { placement, levelMove, duration, ease, showMask } = props;
// router
levelDom.forEach(dom => {
dom.style.transition = `transform ${duration} ${ease}`;
addEventListener(dom, transitionEndFun, transitionEnd);
let levelValue = open ? value : 0;
if (levelMove) {
const $levelMove = transformArguments(levelMove, { target: dom, open });
levelValue = open ? $levelMove[0] : $levelMove[1] || 0;
}
const $value = typeof levelValue === 'number' ? `${levelValue}px` : levelValue;
let placementPos = placement === 'left' || placement === 'top' ? $value : `-${$value}`;
placementPos =
showMask && placement === 'right' && right
? `calc(${placementPos} + ${right}px)`
: placementPos;
dom.style.transform = levelValue ? `${placementName}(${placementPos})` : '';
});
};
const setLevelAndScrolling = (
open?: boolean,
placementName?: string,
value?: string | number,
) => {
if (!windowIsUndefined) {
const right =
document.body.scrollHeight >
(window.innerHeight || document.documentElement.clientHeight) &&
window.innerWidth > document.body.offsetWidth
? getScrollBarSize(true)
: 0;
setLevelTransform(open, placementName, value, right);
toggleScrollingToDrawerAndBody(right);
}
emit('change', open);
};
const toggleScrollingToDrawerAndBody = (right: number) => {
const { getContainer, showMask, open } = props;
const container = getContainer?.();
// body
if (container && container.parentNode === document.body && showMask) {
const eventArray = ['touchstart'];
const domArray = [document.body, maskDom.value, handlerDom.value, contentDom.value];
if (open && document.body.style.overflow !== 'hidden') {
if (right) {
addScrollingEffect(right);
}
document.body.style.touchAction = 'none';
//
domArray.forEach((item, i) => {
if (!item) {
return;
}
addEventListener(
item,
eventArray[i] || 'touchmove',
i ? removeMoveHandler : removeStartHandler,
passive,
);
});
} else if (getCurrentDrawerSome()) {
document.body.style.touchAction = '';
if (right) {
remScrollingEffect(right);
}
//
domArray.forEach((item, i) => {
if (!item) {
return;
}
removeEventListener(
item,
eventArray[i] || 'touchmove',
i ? removeMoveHandler : removeStartHandler,
passive,
);
});
}
}
};
const addScrollingEffect = (right: number) => {
const { placement, duration, ease } = props;
const widthTransition = `width ${duration} ${ease}`;
const transformTransition = `transform ${duration} ${ease}`;
dom.value.style.transition = 'none';
switch (placement) {
case 'right':
dom.value.style.transform = `translateX(-${right}px)`;
break;
case 'top':
case 'bottom':
dom.value.style.width = `calc(100% - ${right}px)`;
dom.value.style.transform = 'translateZ(0)';
break;
default:
break;
}
clearTimeout(timeout);
timeout = setTimeout(() => {
if (dom.value) {
dom.value.style.transition = `${transformTransition},${widthTransition}`;
dom.value.style.width = '';
dom.value.style.transform = '';
}
});
};
const remScrollingEffect = (right: number) => {
const { placement, duration, ease } = props;
dom.value.style.transition = 'none';
let heightTransition: string;
let widthTransition = `width ${duration} ${ease}`;
const transformTransition = `transform ${duration} ${ease}`;
switch (placement) {
case 'left': {
dom.value.style.width = '100%';
widthTransition = `width 0s ${ease} ${duration}`;
break;
}
case 'right': {
dom.value.style.transform = `translateX(${right}px)`;
dom.value.style.width = '100%';
widthTransition = `width 0s ${ease} ${duration}`;
if (maskDom.value) {
maskDom.value.style.left = `-${right}px`;
maskDom.value.style.width = `calc(100% + ${right}px)`;
}
break;
}
case 'top':
case 'bottom': {
dom.value.style.width = `calc(100% + ${right}px)`;
dom.value.style.height = '100%';
dom.value.style.transform = 'translateZ(0)';
heightTransition = `height 0s ${ease} ${duration}`;
break;
}
default:
break;
}
clearTimeout(timeout);
timeout = setTimeout(() => {
if (dom.value) {
dom.value.style.transition = `${transformTransition},${
heightTransition ? `${heightTransition},` : ''
}${widthTransition}`;
dom.value.style.transform = '';
dom.value.style.width = '';
dom.value.style.height = '';
}
});
};
const getCurrentDrawerSome = () => !Object.keys(currentDrawer).some(key => currentDrawer[key]);
const getLevelDom = ({ level, getContainer }) => {
if (windowIsUndefined) {
return;
@ -426,6 +157,7 @@ const DrawerChild = defineComponent({
canOpen.value = true;
});
});
return () => {
const {
width,
@ -449,6 +181,11 @@ const DrawerChild = defineComponent({
contentWrapperStyle,
style,
class: className,
rootClassName,
rootStyle,
maskMotion,
motion,
inline,
...otherProps
} = props;
//
@ -456,54 +193,53 @@ const DrawerChild = defineComponent({
const wrapperClassName = classnames(prefixCls, {
[`${prefixCls}-${placement}`]: true,
[`${prefixCls}-open`]: open,
[className]: !!className,
[`${prefixCls}-inline`]: inline,
'no-mask': !showMask,
[rootClassName]: true,
});
const { placementName } = horizontalBoolAndPlacementName.value;
//
// const defaultValue = !this.contentDom || !level ? '100%' : `${value}px`;
const placementPos = placement === 'left' || placement === 'top' ? '-100%' : '100%';
const transform = open ? '' : `${placementName}(${placementPos})`;
const motionProps = typeof motion === 'function' ? motion(placement) : motion;
return (
<div
{...omit(otherProps, ['switchScrollingEffect', 'autofocus'])}
tabindex={-1}
class={wrapperClassName}
style={style}
style={rootStyle}
ref={dom}
onKeydown={open && keyboard ? onKeyDown : undefined}
onTransitionend={onWrapperTransitionEnd}
>
{showMask && (
<div
class={`${prefixCls}-mask`}
onClick={maskClosable ? onClose : undefined}
style={maskStyle}
ref={maskDom}
/>
)}
<div
class={`${prefixCls}-content-wrapper`}
style={{
transform,
msTransform: transform,
width: isNumeric(width) ? `${width}px` : width,
height: isNumeric(height) ? `${height}px` : height,
...contentWrapperStyle,
}}
ref={contentWrapper}
<Transition {...maskMotion}>
{showMask && (
<div
v-show={open}
class={`${prefixCls}-mask`}
onClick={maskClosable ? onClose : undefined}
style={maskStyle}
ref={maskDom}
/>
)}
</Transition>
<Transition
{...motionProps}
onAfterEnter={onAfterVisibleChange}
onAfterLeave={onAfterVisibleChange}
>
<div class={`${prefixCls}-content`} ref={contentDom}>
{slots.default?.()}
</div>
{slots.handler ? (
<div onClick={onHandleClick} ref={handlerDom}>
{slots.handler?.()}
<div
v-show={open}
class={`${prefixCls}-content-wrapper`}
style={[contentWrapperStyle]}
ref={contentWrapper}
>
<div class={[`${prefixCls}-content`, className]} style={style} ref={contentDom}>
{slots.default?.()}
</div>
) : null}
</div>
{slots.handler ? (
<div onClick={onHandleClick} ref={handlerDom}>
{slots.handler?.()}
</div>
) : null}
</div>
</Transition>
</div>
);
};

View File

@ -37,21 +37,28 @@ const DrawerWrapper = defineComponent({
};
return () => {
const { getContainer, wrapperClassName, forceRender, ...otherProps } = props;
const {
getContainer,
wrapperClassName,
rootClassName,
rootStyle,
forceRender,
...otherProps
} = props;
let portal = null;
if (!getContainer) {
return (
<div class={wrapperClassName} ref={dom}>
<Child
v-slots={slots}
{...otherProps}
open={props.open}
getContainer={() => dom.value}
onClose={onClose}
onHandleClick={onHandleClick}
></Child>
</div>
<Child
v-slots={slots}
{...otherProps}
rootClassName={rootClassName}
rootStyle={rootStyle}
open={props.open}
onClose={onClose}
onHandleClick={onHandleClick}
inline={true}
></Child>
);
}
@ -71,6 +78,8 @@ const DrawerWrapper = defineComponent({
v-slots={slots}
{...otherProps}
{...rest}
rootClassName={rootClassName}
rootStyle={rootStyle}
open={visible !== undefined ? visible : props.open}
afterVisibleChange={
afterClose !== undefined ? afterClose : props.afterVisibleChange

View File

@ -1,5 +1,6 @@
import PropTypes from '../../_util/vue-types';
import type { CSSProperties, PropType } from 'vue';
import type { CSSProperties, PropType, TransitionProps } from 'vue';
import { arrayType, objectType, functionType } from '../../_util/type';
export type IPlacement = 'left' | 'top' | 'right' | 'bottom';
type ILevelMove = number | [number, number];
@ -9,6 +10,8 @@ const props = () => ({
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
style: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
class: String,
rootClassName: String,
rootStyle: objectType<CSSProperties>(),
placement: {
type: String as PropType<IPlacement>,
},
@ -26,12 +29,13 @@ const props = () => ({
maskStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
afterVisibleChange: Function,
keyboard: { type: Boolean, default: undefined },
contentWrapperStyle: {
type: Object as PropType<CSSProperties>,
default: undefined as CSSProperties,
},
contentWrapperStyle: arrayType<CSSProperties[]>(),
autofocus: { type: Boolean, default: undefined },
open: { type: Boolean, default: undefined },
// Motion
motion: functionType<(placement: IPlacement) => TransitionProps>(),
maskMotion: objectType<TransitionProps>(),
});
const drawerProps = () => ({
@ -51,6 +55,6 @@ const drawerChildProps = () => ({
getOpenCount: Function as PropType<() => number>,
scrollLocker: PropTypes.any,
switchScrollingEffect: Function,
inline: Boolean,
});
export { drawerProps, drawerChildProps };