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`, // appearFromClass: `${transitionName}-appear ${transitionName}-appear-prepare`,
// appearActiveClass: `antdv-base-transtion`, // appearActiveClass: `antdv-base-transtion`,
// appearToClass: `${transitionName}-appear ${transitionName}-appear-active`, // 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`, enterActiveClass: `${transitionName}-enter ${transitionName}-enter-prepare`,
enterToClass: `${transitionName}-enter ${transitionName}-enter-active`, enterToClass: `${transitionName}-enter ${transitionName}-enter-active`,
leaveFromClass: ` ${transitionName}-leave`, leaveFromClass: ` ${transitionName}-leave`,

View File

@ -19,12 +19,14 @@ Basic drawer.
<template> <template>
<a-button type="primary" @click="showDrawer">Open</a-button> <a-button type="primary" @click="showDrawer">Open</a-button>
<a-drawer <a-drawer
v-model:visible="visible" v-model:open="open"
class="custom-class" class="custom-class"
root-class-name="root-class-name"
:root-style="{ color: 'blue' }"
style="color: red" style="color: red"
title="Basic Drawer" title="Basic Drawer"
placement="right" placement="right"
@after-visible-change="afterVisibleChange" @after-open-change="afterOpenChange"
> >
<p>Some contents...</p> <p>Some contents...</p>
<p>Some contents...</p> <p>Some contents...</p>
@ -35,19 +37,19 @@ Basic drawer.
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const visible = ref<boolean>(false); const open = ref<boolean>(false);
const afterVisibleChange = (bool: boolean) => { const afterOpenChange = (bool: boolean) => {
console.log('visible', bool); console.log('open', bool);
}; };
const showDrawer = () => { const showDrawer = () => {
visible.value = true; open.value = true;
}; };
return { return {
visible, open,
afterVisibleChange, afterOpenChange,
showDrawer, 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 value="left">left</a-radio>
</a-radio-group> </a-radio-group>
<a-button type="primary" @click="showDrawer">Open</a-button> <a-button type="primary" @click="showDrawer">Open</a-button>
<a-drawer <a-drawer :width="500" title="Basic Drawer" :placement="placement" :open="open" @close="onClose">
:width="500"
title="Basic Drawer"
:placement="placement"
:visible="visible"
@close="onClose"
>
<template #extra> <template #extra>
<a-button style="margin-right: 8px" @click="onClose">Cancel</a-button> <a-button style="margin-right: 8px" @click="onClose">Cancel</a-button>
<a-button type="primary" @click="onClose">Submit</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({ export default defineComponent({
setup() { setup() {
const placement = ref<DrawerProps['placement']>('left'); const placement = ref<DrawerProps['placement']>('left');
const visible = ref<boolean>(false); const open = ref<boolean>(false);
const showDrawer = () => { const showDrawer = () => {
visible.value = true; open.value = true;
}; };
const onClose = () => { const onClose = () => {
visible.value = false; open.value = false;
}; };
return { return {
placement, placement,
visible, open,
showDrawer, showDrawer,
onClose, onClose,
}; };

View File

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

View File

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

View File

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

View File

@ -38,7 +38,7 @@ Render in current dom. custom container, check `getContainer`.
title="Basic Drawer" title="Basic Drawer"
placement="right" placement="right"
:closable="false" :closable="false"
:visible="visible" :open="open"
:get-container="false" :get-container="false"
:style="{ position: 'absolute' }" :style="{ position: 'absolute' }"
@close="onClose" @close="onClose"
@ -51,23 +51,23 @@ Render in current dom. custom container, check `getContainer`.
import { defineComponent, ref } from 'vue'; import { defineComponent, ref } from 'vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const visible = ref(false); const open = ref(false);
const afterVisibleChange = (bool: boolean) => { const afterOpenChange = (bool: boolean) => {
console.log('visible', bool); console.log('open', bool);
}; };
const showDrawer = () => { const showDrawer = () => {
visible.value = true; open.value = true;
}; };
const onClose = () => { const onClose = () => {
visible.value = false; open.value = false;
}; };
return { return {
visible, open,
afterVisibleChange, afterOpenChange,
showDrawer, showDrawer,
onClose, 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) Open Default Size (378px)
</a-button> </a-button>
<a-button type="primary" @click="showDrawer('large')">Open Large Size (736px)</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> <template #extra>
<a-button style="margin-right: 8px" @click="onClose">Cancel</a-button> <a-button style="margin-right: 8px" @click="onClose">Cancel</a-button>
<a-button type="primary" @click="onClose">Submit</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'; import type { DrawerProps } from 'ant-design-vue';
export default defineComponent({ export default defineComponent({
setup() { setup() {
const visible = ref<boolean>(false); const open = ref<boolean>(false);
const size = ref<DrawerProps['size']>('default'); const size = ref<DrawerProps['size']>('default');
const showDrawer = (val: DrawerProps['size']) => { const showDrawer = (val: DrawerProps['size']) => {
size.value = val; size.value = val;
visible.value = true; open.value = true;
}; };
const onClose = () => { const onClose = () => {
visible.value = false; open.value = false;
}; };
return { return {
visible, open,
size, size,
showDrawer, showDrawer,
onClose, 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> </a-list-item>
</template> </template>
</a-list> </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, pStyle2]">User Profile</p>
<p :style="pStyle">Personal</p> <p :style="pStyle">Personal</p>
<a-row> <a-row>
@ -136,7 +136,7 @@ export default defineComponent({
descriptionItem, descriptionItem,
}, },
setup() { setup() {
const visible = ref<boolean>(false); const open = ref<boolean>(false);
const pStyle = { const pStyle = {
fontSize: '16px', fontSize: '16px',
color: 'rgba(0,0,0,0.85)', color: 'rgba(0,0,0,0.85)',
@ -149,13 +149,13 @@ export default defineComponent({
}; };
const showDrawer = () => { const showDrawer = () => {
visible.value = true; open.value = true;
}; };
const onClose = () => { const onClose = () => {
visible.value = false; open.value = false;
}; };
return { return {
visible, open,
pStyle, pStyle,
pStyle2, pStyle2,
showDrawer, showDrawer,

View File

@ -2,7 +2,7 @@
category: Components category: Components
type: Feedback type: Feedback
title: Drawer 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. 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 ## 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 | | Props | Description | Type | Default | Version |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| autofocus | Whether Drawer should get focused after open | boolean | true | 3.0.0 | | autofocus | Whether Drawer should get focused after open | boolean | true | 3.0.0 |
| bodyStyle | Style of the drawer content part | CSSProperties | - | | | 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 | | | 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 | | closeIcon | Custom close icon | VNode \| slot | `<CloseOutlined />` | 3.0.0 |
| contentWrapperStyle | Style of the drawer wrapper of content part | CSSProperties | - | 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 | | | 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 | | extra | Extra actions area at corner | VNode \| slot | - | 3.0.0 |
| footer | The footer for Drawer | 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 | | 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 | {} | | | maskStyle | Style for Drawer's mask element | CSSProperties | {} | |
| placement | The placement of the Drawer | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | | | 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 | | 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 | | 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 | - | | | style | Style of wrapper element which contains mask compare to drawerStyle | CSSProperties | - | |
| title | The title for Drawer | string \| slot | - | | | 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 | | | width | Width of the Drawer dialog | string \| number | 378 | |
| zIndex | The `z-index` of the Drawer | Number | 1000 | | | 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 | | 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) | - | | | 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 PropTypes from '../_util/vue-types';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import useConfigInject from '../config-provider/hooks/useConfigInject'; 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 omit from '../_util/omit';
import devWarning from '../vc-util/devWarning'; import devWarning from '../vc-util/devWarning';
import type { KeyboardEventHandler, MouseEventHandler } from '../_util/EventInterface'; 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]; 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]; export type placementType = (typeof PlacementTypes)[number];
const SizeTypes = tuple('default', 'large'); const SizeTypes = ['default', 'large'] as const;
export type sizeType = (typeof SizeTypes)[number]; export type sizeType = (typeof SizeTypes)[number];
export interface PushState { export interface PushState {
@ -49,25 +54,23 @@ export const drawerProps = () => ({
}, },
maskClosable: { type: Boolean, default: undefined }, maskClosable: { type: Boolean, default: undefined },
mask: { type: Boolean, default: undefined }, mask: { type: Boolean, default: undefined },
maskStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties }, maskStyle: objectType<CSSProperties>(),
/** @deprecated Use `style` instead */ rootClassName: String,
wrapStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties }, rootStyle: objectType<CSSProperties>(),
style: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
class: PropTypes.any,
/** @deprecated Use `class` instead */
wrapClassName: String,
size: { size: {
type: String as PropType<sizeType>, type: String as PropType<sizeType>,
}, },
drawerStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties }, drawerStyle: objectType<CSSProperties>(),
headerStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties }, headerStyle: objectType<CSSProperties>(),
bodyStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties }, bodyStyle: objectType<CSSProperties>(),
contentWrapperStyle: { contentWrapperStyle: {
type: Object as PropType<CSSProperties>, type: Object as PropType<CSSProperties>,
default: undefined as CSSProperties, default: undefined as CSSProperties,
}, },
title: PropTypes.any, title: PropTypes.any,
/** @deprecated Please use `open` instead */
visible: { type: Boolean, default: undefined }, visible: { type: Boolean, default: undefined },
open: { type: Boolean, default: undefined },
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
zIndex: Number, zIndex: Number,
@ -77,7 +80,7 @@ export const drawerProps = () => ({
keyboard: { type: Boolean, default: undefined }, keyboard: { type: Boolean, default: undefined },
extra: PropTypes.any, extra: PropTypes.any,
footer: PropTypes.any, footer: PropTypes.any,
footerStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties }, footerStyle: objectType<CSSProperties>(),
level: PropTypes.any, level: PropTypes.any,
levelMove: { levelMove: {
type: [Number, Array, Function] as PropType< type: [Number, Array, Function] as PropType<
@ -87,8 +90,12 @@ export const drawerProps = () => ({
handle: PropTypes.any, handle: PropTypes.any,
/** @deprecated Use `@afterVisibleChange` instead */ /** @deprecated Use `@afterVisibleChange` instead */
afterVisibleChange: Function as PropType<(visible: boolean) => void>, afterVisibleChange: Function as PropType<(visible: boolean) => void>,
/** @deprecated Please use `@afterOpenChange` instead */
onAfterVisibleChange: Function as PropType<(visible: boolean) => void>, 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:visible': Function as PropType<(visible: boolean) => void>,
'onUpdate:open': Function as PropType<(open: boolean) => void>,
onClose: Function as PropType<MouseEventHandler | KeyboardEventHandler>, onClose: Function as PropType<MouseEventHandler | KeyboardEventHandler>,
}); });
@ -100,7 +107,7 @@ const Drawer = defineComponent({
inheritAttrs: false, inheritAttrs: false,
props: initDefaultProps(drawerProps(), { props: initDefaultProps(drawerProps(), {
closable: true, closable: true,
placement: 'right' as placementType, placement: 'right',
maskClosable: true, maskClosable: true,
mask: true, mask: true,
level: null, level: null,
@ -115,11 +122,11 @@ const Drawer = defineComponent({
const vcDrawer = ref(null); const vcDrawer = ref(null);
const load = ref(false); const load = ref(false);
const visible = ref(false); const visible = ref(false);
const mergedOpen = computed(() => props.open ?? props.visible);
watch( watch(
() => props.visible, mergedOpen,
propsVisible => { () => {
if (propsVisible) { if (mergedOpen.value) {
load.value = true; load.value = true;
} else { } else {
visible.value = false; visible.value = false;
@ -128,9 +135,9 @@ const Drawer = defineComponent({
{ immediate: true }, { immediate: true },
); );
watch( watch(
[() => props.visible, load], [mergedOpen, load],
([propsVisible]) => { () => {
if (propsVisible && load.value) { if (mergedOpen.value && load.value) {
visible.value = true; visible.value = true;
} }
}, },
@ -138,6 +145,7 @@ const Drawer = defineComponent({
); );
const parentDrawerOpts = inject('parentDrawerOpts', null); const parentDrawerOpts = inject('parentDrawerOpts', null);
const { prefixCls, getPopupContainer, direction } = useConfigInject('drawer', props); const { prefixCls, getPopupContainer, direction } = useConfigInject('drawer', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const getContainer = computed(() => const getContainer = computed(() =>
// false // false
props.getContainer === undefined && getPopupContainer.value props.getContainer === undefined && getPopupContainer.value
@ -150,16 +158,21 @@ const Drawer = defineComponent({
'Drawer', 'Drawer',
'`afterVisibleChange` prop is deprecated, please use `@afterVisibleChange` event instead', '`afterVisibleChange` prop is deprecated, please use `@afterVisibleChange` event instead',
); );
devWarning( // ========================== Warning ===========================
props.wrapStyle === undefined, if (process.env.NODE_ENV !== 'production') {
'Drawer', [
'`wrapStyle` prop is deprecated, please use `style` instead', ['visible', 'open'],
); ['onUpdate:visible', 'onUpdate:open'],
devWarning( ['onAfterVisibleChange', 'onAfterOpenChange'],
props.wrapClassName === undefined, ].forEach(([deprecatedName, newName]) => {
'Drawer', devWarning(
'`wrapClassName` prop is deprecated, please use `class` instead', !props[deprecatedName],
); 'Drawer',
`\`${deprecatedName}\` is deprecated, please use \`${newName}\` instead.`,
);
});
}
const setPush = () => { const setPush = () => {
sPush.value = true; sPush.value = true;
}; };
@ -176,7 +189,7 @@ const Drawer = defineComponent({
}); });
onMounted(() => { onMounted(() => {
if (props.visible && parentDrawerOpts) { if (mergedOpen.value && parentDrawerOpts) {
parentDrawerOpts.setPush(); parentDrawerOpts.setPush();
} }
}); });
@ -207,6 +220,7 @@ const Drawer = defineComponent({
const close = (e: Event) => { const close = (e: Event) => {
emit('update:visible', false); emit('update:visible', false);
emit('update:open', false);
emit('close', e); emit('close', e);
}; };
@ -222,6 +236,7 @@ const Drawer = defineComponent({
} }
props.afterVisibleChange?.(open); props.afterVisibleChange?.(open);
emit('afterVisibleChange', open); emit('afterVisibleChange', open);
emit('afterOpenChange', open);
}; };
const pushTransform = computed(() => { const pushTransform = computed(() => {
@ -243,36 +258,28 @@ const Drawer = defineComponent({
} }
return null; 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(() => { const offsetStyle = computed(() => {
// https://github.com/ant-design/ant-design/issues/24287 // 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) { if (!visible.value && !mask) {
return {}; return {};
} }
const val: CSSProperties = {}; const val: CSSProperties = {};
if (placement === 'left' || placement === 'right') { if (placement === 'left' || placement === 'right') {
const defaultWidth = size === 'large' ? 736 : 378; val.width = isNumeric(mergedWidth.value) ? `${mergedWidth.value}px` : mergedWidth.value;
val.width = typeof width === 'undefined' ? defaultWidth : width;
val.width = typeof val.width === 'string' ? val.width : `${val.width}px`;
} else { } else {
const defaultHeight = size === 'large' ? 736 : 378; val.height = isNumeric(mergedHeight.value) ? `${mergedHeight.value}px` : mergedHeight.value;
val.height = typeof height === 'undefined' ? defaultHeight : height;
val.height = typeof val.height === 'string' ? val.height : `${val.height}px`;
} }
return val; return val;
}); });
const drawerStyle = computed(() => { const wrapperStyle = computed(() => {
const { zIndex, wrapStyle, mask, style } = props; const { zIndex } = props;
const val = mask ? {} : offsetStyle.value; const val = offsetStyle.value;
return { return [{ zIndex, transform: sPush.value ? pushTransform.value : undefined }, val];
zIndex,
transform: sPush.value ? pushTransform.value : undefined,
...val,
...wrapStyle,
...style,
};
}); });
const renderHeader = (prefixCls: string) => { const renderHeader = (prefixCls: string) => {
@ -342,21 +349,29 @@ const Drawer = defineComponent({
</div> </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 () => { return () => {
const { const { width, height, placement, mask, forceRender, ...rest } = props;
width,
height,
placement,
mask,
wrapClassName,
class: className,
forceRender,
...rest
} = props;
const val = mask ? offsetStyle.value : {};
const haveMask = mask ? '' : 'no-mask';
const vcDrawerProps: any = { const vcDrawerProps: any = {
...attrs, ...attrs,
...omit(rest, [ ...omit(rest, [
@ -369,13 +384,12 @@ const Drawer = defineComponent({
'bodyStyle', 'bodyStyle',
'title', 'title',
'push', 'push',
'wrapStyle',
'onAfterVisibleChange', 'onAfterVisibleChange',
'onClose', 'onClose',
'onUpdate:visible', 'onUpdate:visible',
'onUpdate:open',
'visible', 'visible',
]), ]),
...val,
forceRender, forceRender,
onClose: close, onClose: close,
afterVisibleChange, afterVisibleChange,
@ -384,24 +398,26 @@ const Drawer = defineComponent({
open: visible.value, open: visible.value,
showMask: mask, showMask: mask,
placement, placement,
class: classnames({
[className]: className,
[wrapClassName]: !!wrapClassName,
[haveMask]: !!haveMask,
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
}),
style: drawerStyle.value,
ref: vcDrawer, ref: vcDrawer,
}; };
return ( return wrapSSR(
<VcDrawer <NoCompactStyle>
{...vcDrawerProps} <VcDrawer
getContainer={getContainer.value} {...vcDrawerProps}
v-slots={{ maskMotion={maskMotion.value}
handler: props.handle ? () => props.handle : slots.handle, motion={panelMotion}
default: () => renderBody(prefixCls.value), width={mergedWidth.value}
}} height={mergedHeight.value}
></VcDrawer> 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: 反馈 type: 反馈
title: Drawer title: Drawer
subtitle: 抽屉 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 ## API
**🚨 注意:** v4 使用 `rootClassName``rootStyle` 来配置最外层元素样式。原 v4 `class``style` 改至配置 Drawer 窗体样式以和 Modal 对齐。
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| autofocus | 抽屉展开后是否将焦点切换至其 Dom 节点 | boolean | true | 3.0.0 | | autofocus | 抽屉展开后是否将焦点切换至其 Dom 节点 | boolean | true | 3.0.0 |
| bodyStyle | 可用于设置 Drawer 内容部分的样式 | CSSProperties | - | | | bodyStyle | 可用于设置 Drawer 内容部分的样式 | CSSProperties | - | |
| class | 对话框外层容器的类名 | string | - | | | class | Drawer 容器外层 className 设置,如果需要设置最外层,请使用 rootClassName | string | - | |
| closable | 是否显示左上角的关闭按钮 | boolean | true | | | closable | 是否显示左上角的关闭按钮 | boolean | true | |
| closeIcon | 自定义关闭图标 | VNode \| slot | `<CloseOutlined />` | 3.0.0 | | closeIcon | 自定义关闭图标 | VNode \| slot | `<CloseOutlined />` | 3.0.0 |
| contentWrapperStyle | 可用于设置 Drawer 包裹内容部分的样式 | CSSProperties | - | 3.0.0 | | contentWrapperStyle | 可用于设置 Drawer 包裹内容部分的样式 | CSSProperties | - | 3.0.0 |
| destroyOnClose | 关闭时销毁 Drawer 里的子元素 | boolean | false | | | destroyOnClose | 关闭时销毁 Drawer 里的子元素 | boolean | false | |
| drawerStyle | 用于设置 Drawer 弹出层的样式 | object | - | |
| extra | 抽屉右上角的操作区域 | VNode \| slot | - | 3.0.0 | | extra | 抽屉右上角的操作区域 | VNode \| slot | - | 3.0.0 |
| footer | 抽屉的页脚 | VNode \| slot | - | 3.0.0 | | footer | 抽屉的页脚 | VNode \| slot | - | 3.0.0 |
| footerStyle | 抽屉页脚部件的样式 | CSSProperties | - | 3.0.0 | | footerStyle | 抽屉页脚部件的样式 | CSSProperties | - | 3.0.0 |
@ -40,16 +41,18 @@ cover: https://img.alicdn.com/imgextra/i4/O1CN019djdZP1OHwXSRGCOW_!!600000000168
| maskStyle | 遮罩样式 | CSSProperties | {} | | | maskStyle | 遮罩样式 | CSSProperties | {} | |
| placement | 抽屉的方向 | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | | | placement | 抽屉的方向 | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | |
| push | 用于设置多层 Drawer 的推动行为 | boolean \| {distance: string \| number} | { distance: 180 } | 3.0.0 | | 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 | | size | 预设抽屉宽度或高度default `378px` 和 large `736px` | `default` \| `large` | `default` | 3.0.0 |
| style | 可用于设置 Drawer 最外层容器的样式,和 `drawerStyle` 的区别是作用节点包括 `mask` | CSSProperties | - | | | style | 设计 Drawer 容器样式,如果你只需要设置内容部分请使用 `bodyStyle` | CSSProperties | - | |
| title | 标题 | string \| slot | - | | | title | 标题 | string \| slot | - | |
| visible(v-model) | Drawer 是否可见 | boolean | - | | | open(v-model) | Drawer 是否可见 | boolean | - | 4.0 |
| width | 宽度 | string \| number | 378 | | | width | 宽度 | string \| number | 378 | |
| zIndex | 设置 Drawer 的 `z-index` | Number | 1000 | | | zIndex | 设置 Drawer 的 `z-index` | Number | 1000 | |
## 事件 ## 事件
| 名称 | 描述 | 类型 | 默认值 | 版本 | | 名称 | 描述 | 类型 | 默认值 | 版本 |
| ------------------ | ------------------------------------ | ----------------- | ------ | ---- | | --------------- | ------------------------------------ | -------------- | ------ | ---- |
| afterVisibleChange | 切换抽屉时动画结束后的回调 | function(visible) | 无 | | | afterOpenChange | 切换抽屉时动画结束后的回调 | function(open) | 无 | 4.0 |
| close | 点击遮罩层或左上角叉或取消按钮的回调 | function(e) | 无 | | | 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 './anchor/style';
// import './list/style'; // import './list/style';
// import './tree-select/style'; // import './tree-select/style';
import './drawer/style'; // import './drawer/style';
// import './skeleton/style'; // import './skeleton/style';
// import './comment/style'; // import './comment/style';
// import './config-provider/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 DatePickerComponentToken } from '../../date-picker/style';
import type { ComponentToken as DividerComponentToken } from '../../divider/style'; import type { ComponentToken as DividerComponentToken } from '../../divider/style';
import type { ComponentToken as DropdownComponentToken } from '../../dropdown/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 EmptyComponentToken } from '../../empty/style';
import type { ComponentToken as ImageComponentToken } from '../../image/style'; import type { ComponentToken as ImageComponentToken } from '../../image/style';
import type { ComponentToken as InputNumberComponentToken } from '../../input-number/style'; import type { ComponentToken as InputNumberComponentToken } from '../../input-number/style';
@ -69,7 +69,7 @@ export interface ComponentTokenMap {
DatePicker?: DatePickerComponentToken; DatePicker?: DatePickerComponentToken;
Descriptions?: {}; Descriptions?: {};
Divider?: DividerComponentToken; Divider?: DividerComponentToken;
// Drawer?: DrawerComponentToken; Drawer?: DrawerComponentToken;
Dropdown?: DropdownComponentToken; Dropdown?: DropdownComponentToken;
Empty?: EmptyComponentToken; Empty?: EmptyComponentToken;
// FloatButton?: FloatButtonComponentToken; // FloatButton?: FloatButtonComponentToken;

View File

@ -1,30 +1,9 @@
import { import { Transition, defineComponent, onMounted, onUnmounted, nextTick, watch, ref } from 'vue';
defineComponent,
reactive,
onMounted,
computed,
onUnmounted,
nextTick,
watch,
ref,
} from 'vue';
import classnames from '../../_util/classNames'; import classnames from '../../_util/classNames';
import getScrollBarSize from '../../_util/getScrollBarSize';
import KeyCode from '../../_util/KeyCode'; import KeyCode from '../../_util/KeyCode';
import omit from '../../_util/omit'; import omit from '../../_util/omit';
import supportsPassive from '../../_util/supportsPassive';
import { drawerChildProps } from './IDrawerPropTypes'; import { drawerChildProps } from './IDrawerPropTypes';
import { dataToArray, windowIsUndefined } from './utils';
import {
addEventListener,
dataToArray,
getTouchParentScroll,
isNumeric,
removeEventListener,
transformArguments,
transitionEndFun,
windowIsUndefined,
} from './utils';
const currentDrawer: Record<string, boolean> = {}; const currentDrawer: Record<string, boolean> = {};
@ -32,23 +11,12 @@ export interface scrollLockOptions {
container: HTMLElement; container: HTMLElement;
} }
interface Point {
x: number;
y: number;
}
const DrawerChild = defineComponent({ const DrawerChild = defineComponent({
compatConfig: { MODE: 3 }, compatConfig: { MODE: 3 },
inheritAttrs: false, inheritAttrs: false,
props: drawerChildProps(), props: drawerChildProps(),
emits: ['close', 'handleClick', 'change'], emits: ['close', 'handleClick', 'change'],
setup(props, { emit, slots }) { setup(props, { emit, slots }) {
const state = reactive({
startPos: {
x: null,
y: null,
} as Point | null,
});
let timeout;
const contentWrapper = ref<HTMLElement>(); const contentWrapper = ref<HTMLElement>();
const dom = ref<HTMLElement>(); const dom = ref<HTMLElement>();
const maskDom = ref<HTMLElement>(); const maskDom = ref<HTMLElement>();
@ -61,8 +29,6 @@ const DrawerChild = defineComponent({
.replace('.', Math.round(Math.random() * 9).toString()), .replace('.', Math.round(Math.random() * 9).toString()),
).toString(16)}`; ).toString(16)}`;
const passive = !windowIsUndefined && supportsPassive ? { passive: false } : false;
onMounted(() => { onMounted(() => {
nextTick(() => { nextTick(() => {
const { open, getContainer, showMask, autofocus } = props; const { open, getContainer, showMask, autofocus } = props;
@ -72,8 +38,6 @@ const DrawerChild = defineComponent({
if (container && container.parentNode === document.body) { if (container && container.parentNode === document.body) {
currentDrawer[drawerId] = open; currentDrawer[drawerId] = open;
} }
// level;
openLevelTransition();
nextTick(() => { nextTick(() => {
if (autofocus) { if (autofocus) {
domFocus(); domFocus();
@ -100,7 +64,6 @@ const DrawerChild = defineComponent({
if (container && container.parentNode === document.body) { if (container && container.parentNode === document.body) {
currentDrawer[drawerId] = !!open; currentDrawer[drawerId] = !!open;
} }
openLevelTransition();
if (open) { if (open) {
if (autofocus) { if (autofocus) {
domFocus(); domFocus();
@ -119,7 +82,6 @@ const DrawerChild = defineComponent({
const { open } = props; const { open } = props;
delete currentDrawer[drawerId]; delete currentDrawer[drawerId];
if (open) { if (open) {
setLevelTransform(false);
document.body.style.touchAction = ''; document.body.style.touchAction = '';
} }
props.scrollLocker?.unLock(); props.scrollLocker?.unLock();
@ -139,42 +101,6 @@ const DrawerChild = defineComponent({
dom.value?.focus?.(); 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) => { const onClose = (e: Event) => {
emit('close', e); emit('close', e);
}; };
@ -186,208 +112,13 @@ const DrawerChild = defineComponent({
} }
}; };
const onWrapperTransitionEnd = (e: TransitionEvent) => { const onAfterVisibleChange = () => {
const { open, afterVisibleChange } = props; const { open, afterVisibleChange } = props;
if (e.target === contentWrapper.value && e.propertyName.match(/transform$/)) { if (afterVisibleChange) {
dom.value.style.transition = ''; afterVisibleChange(!!open);
if (!open && getCurrentDrawerSome()) {
document.body.style.overflowX = '';
if (maskDom.value) {
maskDom.value.style.left = '';
maskDom.value.style.width = '';
}
}
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 }) => { const getLevelDom = ({ level, getContainer }) => {
if (windowIsUndefined) { if (windowIsUndefined) {
return; return;
@ -426,6 +157,7 @@ const DrawerChild = defineComponent({
canOpen.value = true; canOpen.value = true;
}); });
}); });
return () => { return () => {
const { const {
width, width,
@ -449,6 +181,11 @@ const DrawerChild = defineComponent({
contentWrapperStyle, contentWrapperStyle,
style, style,
class: className, class: className,
rootClassName,
rootStyle,
maskMotion,
motion,
inline,
...otherProps ...otherProps
} = props; } = props;
// //
@ -456,54 +193,53 @@ const DrawerChild = defineComponent({
const wrapperClassName = classnames(prefixCls, { const wrapperClassName = classnames(prefixCls, {
[`${prefixCls}-${placement}`]: true, [`${prefixCls}-${placement}`]: true,
[`${prefixCls}-open`]: open, [`${prefixCls}-open`]: open,
[className]: !!className, [`${prefixCls}-inline`]: inline,
'no-mask': !showMask, 'no-mask': !showMask,
[rootClassName]: true,
}); });
const { placementName } = horizontalBoolAndPlacementName.value; const motionProps = typeof motion === 'function' ? motion(placement) : motion;
//
// const defaultValue = !this.contentDom || !level ? '100%' : `${value}px`;
const placementPos = placement === 'left' || placement === 'top' ? '-100%' : '100%';
const transform = open ? '' : `${placementName}(${placementPos})`;
return ( return (
<div <div
{...omit(otherProps, ['switchScrollingEffect', 'autofocus'])} {...omit(otherProps, ['switchScrollingEffect', 'autofocus'])}
tabindex={-1} tabindex={-1}
class={wrapperClassName} class={wrapperClassName}
style={style} style={rootStyle}
ref={dom} ref={dom}
onKeydown={open && keyboard ? onKeyDown : undefined} onKeydown={open && keyboard ? onKeyDown : undefined}
onTransitionend={onWrapperTransitionEnd}
> >
{showMask && ( <Transition {...maskMotion}>
<div {showMask && (
class={`${prefixCls}-mask`} <div
onClick={maskClosable ? onClose : undefined} v-show={open}
style={maskStyle} class={`${prefixCls}-mask`}
ref={maskDom} onClick={maskClosable ? onClose : undefined}
/> style={maskStyle}
)} ref={maskDom}
<div />
class={`${prefixCls}-content-wrapper`} )}
style={{ </Transition>
transform, <Transition
msTransform: transform, {...motionProps}
width: isNumeric(width) ? `${width}px` : width, onAfterEnter={onAfterVisibleChange}
height: isNumeric(height) ? `${height}px` : height, onAfterLeave={onAfterVisibleChange}
...contentWrapperStyle,
}}
ref={contentWrapper}
> >
<div class={`${prefixCls}-content`} ref={contentDom}> <div
{slots.default?.()} v-show={open}
</div> class={`${prefixCls}-content-wrapper`}
{slots.handler ? ( style={[contentWrapperStyle]}
<div onClick={onHandleClick} ref={handlerDom}> ref={contentWrapper}
{slots.handler?.()} >
<div class={[`${prefixCls}-content`, className]} style={style} ref={contentDom}>
{slots.default?.()}
</div> </div>
) : null} {slots.handler ? (
</div> <div onClick={onHandleClick} ref={handlerDom}>
{slots.handler?.()}
</div>
) : null}
</div>
</Transition>
</div> </div>
); );
}; };

View File

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

View File

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