refactor(drawer): use compositionAPI
parent
4fe3c7a919
commit
0c31ada67a
|
@ -22,8 +22,7 @@ Basic drawer.
|
|||
v-model:visible="visible"
|
||||
title="Basic Drawer"
|
||||
placement="right"
|
||||
:closable="false"
|
||||
:after-visible-change="afterVisibleChange"
|
||||
@after-visible-change="afterVisibleChange"
|
||||
>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<docs>
|
||||
---
|
||||
order: 2
|
||||
title:
|
||||
zh-CN: 额外操作
|
||||
en-US: Extra Actions
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
在 Ant Design 规范中,操作按钮建议放在抽屉的右上角,可以使用 extra 属性来实现。
|
||||
|
||||
## en-US
|
||||
|
||||
Extra actions should be placed at corner of drawer in Ant Design, you can using `extra` prop for that.
|
||||
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-radio-group v-model:value="placement" style="margin-right: 8px">
|
||||
<a-radio value="top">top</a-radio>
|
||||
<a-radio value="right">right</a-radio>
|
||||
<a-radio value="bottom">bottom</a-radio>
|
||||
<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"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button style="margin-right: 8px" @click="onClose">Cancel</a-button>
|
||||
<a-button type="primary" @click="onClose">Submit</a-button>
|
||||
</template>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const placement = ref<string>('left');
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
const showDrawer = () => {
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
return {
|
||||
placement,
|
||||
visible,
|
||||
showDrawer,
|
||||
onClose,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
<docs>
|
||||
---
|
||||
order: 3
|
||||
order: 4
|
||||
title:
|
||||
zh-CN: 抽屉表单
|
||||
en-US: Submit form in drawer
|
||||
|
@ -26,6 +26,7 @@ Use form in drawer with submit button.
|
|||
:width="720"
|
||||
:visible="visible"
|
||||
:body-style="{ paddingBottom: '80px' }"
|
||||
:footer-style="{ textAlign: 'right' }"
|
||||
@close="onClose"
|
||||
>
|
||||
<a-form :model="form" :rules="rules" layout="vertical">
|
||||
|
@ -96,22 +97,10 @@ Use form in drawer with submit button.
|
|||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
<div
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
borderTop: '1px solid #e9e9e9',
|
||||
padding: '10px 16px',
|
||||
background: '#fff',
|
||||
textAlign: 'right',
|
||||
zIndex: 1,
|
||||
}"
|
||||
>
|
||||
<template #footer>
|
||||
<a-button style="margin-right: 8px" @click="onClose">Cancel</a-button>
|
||||
<a-button type="primary" @click="onClose">Submit</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
|
|
|
@ -1,20 +1,24 @@
|
|||
<template>
|
||||
<demo-sort>
|
||||
<basic />
|
||||
<extra />
|
||||
<placement />
|
||||
<render-in-current />
|
||||
<form-in-drawer />
|
||||
<user-profile />
|
||||
<multi-level-drawer />
|
||||
<size />
|
||||
</demo-sort>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import Basic from './basic.vue';
|
||||
import Extra from './extra.vue';
|
||||
import Placement from './placement.vue';
|
||||
import UserProfile from './user-profile.vue';
|
||||
import MultiLevelDrawer from './multi-level-drawer.vue';
|
||||
import FormInDrawer from './form-in-drawer.vue';
|
||||
import RenderInCurrent from './render-in-current.vue';
|
||||
import Size from './size.vue';
|
||||
|
||||
import CN from '../index.zh-CN.md';
|
||||
import US from '../index.en-US.md';
|
||||
|
@ -25,11 +29,13 @@ export default defineComponent({
|
|||
US,
|
||||
components: {
|
||||
Basic,
|
||||
Extra,
|
||||
Placement,
|
||||
UserProfile,
|
||||
MultiLevelDrawer,
|
||||
FormInDrawer,
|
||||
RenderInCurrent,
|
||||
Size,
|
||||
},
|
||||
setup() {
|
||||
return {};
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
<docs>
|
||||
---
|
||||
order: 5
|
||||
order: 6
|
||||
title:
|
||||
zh-CN: 多层抽屉
|
||||
en-US: Multi-level drawer
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
在抽屉内打开新的抽屉,用以解决多分支任务的复杂状况。
|
||||
|
||||
## en-US
|
||||
|
||||
Open a new drawer on top of an existing drawer to handle multi branch tasks.
|
||||
|
||||
</docs>
|
||||
|
||||
<docs>
|
||||
---
|
||||
order: 6
|
||||
title:
|
||||
zh-CN: 多层抽屉
|
||||
en-US: Multi-level drawer
|
||||
|
@ -22,7 +40,8 @@ Open a new drawer on top of an existing drawer to handle multi branch tasks.
|
|||
title="Multi-level drawer"
|
||||
width="520"
|
||||
:closable="false"
|
||||
:visible="visible"
|
||||
v-model:visible="visible"
|
||||
:footer-style="{ textAlign: 'right' }"
|
||||
@close="onClose"
|
||||
>
|
||||
<a-button type="primary" @click="showChildrenDrawer">Two-level drawer</a-button>
|
||||
|
@ -30,27 +49,15 @@ Open a new drawer on top of an existing drawer to handle multi branch tasks.
|
|||
title="Two-level Drawer"
|
||||
width="320"
|
||||
:closable="false"
|
||||
:visible="childrenDrawer"
|
||||
@close="onChildrenDrawerClose"
|
||||
v-model:visible="childrenDrawer"
|
||||
>
|
||||
<a-button type="primary" @click="showChildrenDrawer">This is two-level drawer</a-button>
|
||||
</a-drawer>
|
||||
<div
|
||||
:style="{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
width: '100%',
|
||||
borderTop: '1px solid #e8e8e8',
|
||||
padding: '10px 16px',
|
||||
textAlign: 'right',
|
||||
left: 0,
|
||||
background: '#fff',
|
||||
borderRadius: '0 0 4px 4px',
|
||||
}"
|
||||
>
|
||||
|
||||
<template #footer>
|
||||
<a-button style="margin-right: 8px" @click="onClose">Cancel</a-button>
|
||||
<a-button type="primary" @click="onClose">Submit</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
|
@ -71,16 +78,12 @@ export default defineComponent({
|
|||
const showChildrenDrawer = () => {
|
||||
childrenDrawer.value = true;
|
||||
};
|
||||
const onChildrenDrawerClose = () => {
|
||||
childrenDrawer.value = false;
|
||||
};
|
||||
return {
|
||||
visible,
|
||||
childrenDrawer,
|
||||
showDrawer,
|
||||
onClose,
|
||||
showChildrenDrawer,
|
||||
onChildrenDrawerClose,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<docs>
|
||||
---
|
||||
order: 2
|
||||
order: 3
|
||||
title:
|
||||
zh-CN: 渲染在当前 DOM
|
||||
en-US: Render in current dom
|
||||
|
@ -40,7 +40,7 @@ Render in current dom. custom container, check getContainer.
|
|||
:closable="false"
|
||||
:visible="visible"
|
||||
:get-container="false"
|
||||
:wrap-style="{ position: 'absolute' }"
|
||||
:style="{ position: 'absolute' }"
|
||||
@close="onClose"
|
||||
>
|
||||
<p>Some contents...</p>
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
<docs>
|
||||
---
|
||||
order: 7
|
||||
title:
|
||||
zh-CN: 预设宽度
|
||||
en-US: Presetted size
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
抽屉的默认宽度为 `378px`,另外还提供一个大号抽屉 `736px`,可以用 size 属性来设置。
|
||||
|
||||
## en-US
|
||||
|
||||
The default width (or height) of Drawer is `378px`, and there is a presetted large size `736px`.
|
||||
|
||||
</docs>
|
||||
|
||||
<template>
|
||||
<a-button type="primary" @click="showDrawer('default')">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"
|
||||
:placement="placement"
|
||||
:visible="visible"
|
||||
@close="onClose"
|
||||
>
|
||||
<template #extra>
|
||||
<a-button style="margin-right: 8px" @click="onClose">Cancel</a-button>
|
||||
<a-button type="primary" @click="onClose">Submit</a-button>
|
||||
</template>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
</a-drawer>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const visible = ref<boolean>(false);
|
||||
const size = ref<string>('default');
|
||||
|
||||
const showDrawer = (val: string) => {
|
||||
size.value = val;
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
const onClose = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
return {
|
||||
visible,
|
||||
size,
|
||||
showDrawer,
|
||||
onClose,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
<docs>
|
||||
---
|
||||
order: 4
|
||||
order: 5
|
||||
title:
|
||||
zh-CN: 信息预览抽屉
|
||||
en-US: Preview drawer
|
||||
|
|
|
@ -15,34 +15,41 @@ A Drawer is a panel that is typically overlaid on top of a page and slides in fr
|
|||
- Processing subtasks. When subtasks are too heavy for a Popover and we still want to keep the subtasks in the context of the main task, Drawer comes very handy.
|
||||
- When the same Form is needed in multiple places.
|
||||
|
||||
|
||||
## API
|
||||
|
||||
| Property | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| closable | Whether a close (x) button is visible on top right of the Drawer dialog or not. | boolean | true | |
|
||||
| destroyOnClose | Whether to unmount child components on closing drawer or not. | boolean | false | |
|
||||
| getContainer | Return the mounted node for Drawer. | HTMLElement \| `() => HTMLElement` \| Selectors | 'body' | |
|
||||
| mask | Whether to show mask or not. | Boolean | true | |
|
||||
| maskClosable | Clicking on the mask (area outside the Drawer) to close the Drawer or not. | boolean | true | |
|
||||
| maskStyle | Style for Drawer's mask element. | object | {} | |
|
||||
| title | The title for Drawer. | string\|slot | - | |
|
||||
| visible(v-model) | Whether the Drawer dialog is visible or not. | boolean | false | |
|
||||
| wrapClassName | The class name of the container of the Drawer dialog. | string | - | |
|
||||
| wrapStyle | Style of wrapper element which **contains mask** compare to `drawerStyle` | object | - | |
|
||||
| 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 | - | |
|
||||
| className(old: wrapClassName) | The class name of the container of the Drawer dialog | string | - | 3.0.0 |
|
||||
| closable | Whether a close (x) button is visible on top right 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 | object | - | |
|
||||
| headerStyle | Style of the drawer header part | object | - | |
|
||||
| bodyStyle | Style of the drawer content part | object | - | |
|
||||
| width | Width of the Drawer dialog. | string\|number | 256 | |
|
||||
| height | placement is `top` or `bottom`, height of the Drawer dialog. | string\|number | - | |
|
||||
| zIndex | The `z-index` of the Drawer. | Number | 1000 | |
|
||||
| placement | The placement of the Drawer. | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | |
|
||||
| handle | After setting, the drawer is directly mounted on the DOM, and you can control the drawer to open or close through this `handle`. | VNode \| slot | - | |
|
||||
| afterVisibleChange | Callback after the animation ends when switching drawers. | function(visible) | - | |
|
||||
| keyboard | Whether support press esc to close | Boolean | true | |
|
||||
| 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 |
|
||||
| forceRender | Prerender Drawer component forcely | boolean | - | false | 3.0.0 |
|
||||
| getContainer | Return the mounted node for Drawer | HTMLElement \| `() => HTMLElement` \| Selectors | 'body' | |
|
||||
| headerStyle | Style of the drawer header part | CSSProperties | - | 3.0.0 |
|
||||
| height | Placement is `top` or `bottom`, height of the Drawer dialog | string \| number | 378 | |
|
||||
| keyboard | Whether support press esc to close | boolean | true | |
|
||||
| mask | Whether to show mask or not | Boolean | true | |
|
||||
| maskClosable | Clicking on the mask (area outside the Drawer) to close the Drawer or not | boolean | true | |
|
||||
| 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 |
|
||||
| size | presetted size of drawer, default `378px` and large `736px` | `default` \| `large` | `default` | 3.0.0 |
|
||||
| style(old: wrapStyle) | Style of wrapper element which contains mask compare to drawerStyle | CSSProperties | - | 3.0.0 |
|
||||
| title | The title for Drawer | string \| slot | - | |
|
||||
| visible(v-model) | Whether the Drawer dialog is visible or not | boolean | - | |
|
||||
| width | Width of the Drawer dialog | string \| number | 378 | |
|
||||
| zIndex | The `z-index` of the Drawer | Number | 1000 | |
|
||||
|
||||
## Methods
|
||||
|
||||
| Name | Description | Type | Default | Version |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| afterVisibleChange | Callback after the animation ends when switching drawers. | function(visible) | - | |
|
||||
| close | Specify a callback that will be called when a user clicks mask, close button or Cancel button. | function(e) | - | |
|
||||
|
|
|
@ -1,189 +1,237 @@
|
|||
import type { CSSProperties } from 'vue';
|
||||
import { inject, provide, nextTick, defineComponent } from 'vue';
|
||||
import type { CSSProperties, ExtractPropTypes, PropType } from 'vue';
|
||||
import {
|
||||
inject,
|
||||
nextTick,
|
||||
defineComponent,
|
||||
ref,
|
||||
onMounted,
|
||||
provide,
|
||||
onBeforeMount,
|
||||
onUpdated,
|
||||
onUnmounted,
|
||||
} from 'vue';
|
||||
import { getPropsSlot } from '../_util/props-util';
|
||||
import classnames from '../_util/classNames';
|
||||
import VcDrawer from '../vc-drawer/src';
|
||||
import VcDrawer from '../vc-drawer';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import BaseMixin from '../_util/BaseMixin';
|
||||
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
|
||||
import { getComponent, getOptionProps } from '../_util/props-util';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import { tuple, withInstall } from '../_util/type';
|
||||
import omit from '../_util/omit';
|
||||
|
||||
const PlacementTypes = tuple('top', 'right', 'bottom', 'left');
|
||||
type placementType = typeof PlacementTypes[number];
|
||||
export type placementType = typeof PlacementTypes[number];
|
||||
|
||||
const SizeTypes = tuple('default', 'large');
|
||||
export type sizeType = typeof SizeTypes[number];
|
||||
|
||||
export interface PushState {
|
||||
distance: string | number;
|
||||
}
|
||||
|
||||
const defaultPushState: PushState = { distance: 180 };
|
||||
|
||||
const drawerProps = {
|
||||
autoFocus: PropTypes.looseBool,
|
||||
closable: PropTypes.looseBool.def(true),
|
||||
closeIcon: PropTypes.VNodeChild.def(<CloseOutlined />),
|
||||
destroyOnClose: PropTypes.looseBool,
|
||||
forceRender: PropTypes.looseBool,
|
||||
getContainer: PropTypes.any,
|
||||
maskClosable: PropTypes.looseBool.def(true),
|
||||
mask: PropTypes.looseBool.def(true),
|
||||
maskStyle: PropTypes.object,
|
||||
style: PropTypes.object,
|
||||
size: {
|
||||
type: String as PropType<sizeType>,
|
||||
},
|
||||
drawerStyle: PropTypes.object,
|
||||
headerStyle: PropTypes.object,
|
||||
bodyStyle: PropTypes.object,
|
||||
contentWrapperStyle: PropTypes.object,
|
||||
title: PropTypes.VNodeChild,
|
||||
visible: PropTypes.looseBool,
|
||||
className: PropTypes.string,
|
||||
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
zIndex: PropTypes.number,
|
||||
prefixCls: PropTypes.string,
|
||||
push: PropTypes.oneOfType([PropTypes.looseBool, { type: Object as PropType<PushState> }]).def(
|
||||
defaultPushState,
|
||||
),
|
||||
placement: PropTypes.oneOf(PlacementTypes).def('right'),
|
||||
keyboard: PropTypes.looseBool.def(true),
|
||||
extra: PropTypes.VNodeChild,
|
||||
footer: PropTypes.VNodeChild,
|
||||
footerStyle: PropTypes.object,
|
||||
level: PropTypes.any.def(null),
|
||||
levelMove: PropTypes.any,
|
||||
};
|
||||
|
||||
export type DrawerProps = Partial<ExtractPropTypes<typeof drawerProps>>;
|
||||
|
||||
const Drawer = defineComponent({
|
||||
name: 'ADrawer',
|
||||
mixins: [BaseMixin],
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
closable: PropTypes.looseBool.def(true),
|
||||
destroyOnClose: PropTypes.looseBool,
|
||||
getContainer: PropTypes.any,
|
||||
maskClosable: PropTypes.looseBool.def(true),
|
||||
mask: PropTypes.looseBool.def(true),
|
||||
maskStyle: PropTypes.object,
|
||||
wrapStyle: PropTypes.object,
|
||||
bodyStyle: PropTypes.object,
|
||||
headerStyle: PropTypes.object,
|
||||
drawerStyle: PropTypes.object,
|
||||
title: PropTypes.VNodeChild,
|
||||
visible: PropTypes.looseBool,
|
||||
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).def(256),
|
||||
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).def(256),
|
||||
zIndex: PropTypes.number,
|
||||
prefixCls: PropTypes.string,
|
||||
placement: PropTypes.oneOf(PlacementTypes).def('right'),
|
||||
level: PropTypes.any.def(null),
|
||||
wrapClassName: PropTypes.string, // not use class like react, vue will add class to root dom
|
||||
handle: PropTypes.VNodeChild,
|
||||
afterVisibleChange: PropTypes.func,
|
||||
keyboard: PropTypes.looseBool.def(true),
|
||||
onClose: PropTypes.func,
|
||||
'onUpdate:visible': PropTypes.func,
|
||||
},
|
||||
setup(props) {
|
||||
props: drawerProps,
|
||||
emits: ['update:visible', 'close', 'afterVisibleChange'],
|
||||
setup(props, { emit, slots, attrs }) {
|
||||
const sPush = ref(false);
|
||||
const preVisible = ref(props.visible);
|
||||
const destroyClose = ref(false);
|
||||
const vcDrawer = ref(null);
|
||||
const configProvider = inject('configProvider', defaultConfigProvider);
|
||||
return {
|
||||
configProvider,
|
||||
destroyClose: false,
|
||||
preVisible: props.visible,
|
||||
parentDrawer: inject('parentDrawer', null),
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
sPush: false,
|
||||
};
|
||||
},
|
||||
beforeCreate() {
|
||||
provide('parentDrawer', this);
|
||||
},
|
||||
mounted() {
|
||||
// fix: delete drawer in child and re-render, no push started.
|
||||
// <Drawer>{show && <Drawer />}</Drawer>
|
||||
const { visible } = this;
|
||||
if (visible && this.parentDrawer) {
|
||||
this.parentDrawer.push();
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
nextTick(() => {
|
||||
if (this.preVisible !== this.visible && this.parentDrawer) {
|
||||
if (this.visible) {
|
||||
this.parentDrawer.push();
|
||||
} else {
|
||||
this.parentDrawer.pull();
|
||||
}
|
||||
}
|
||||
this.preVisible = this.visible;
|
||||
});
|
||||
},
|
||||
beforeUnmount() {
|
||||
// unmount drawer in child, clear push.
|
||||
if (this.parentDrawer) {
|
||||
this.parentDrawer.pull();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
domFocus() {
|
||||
if (this.$refs.vcDrawer) {
|
||||
(this.$refs.vcDrawer as any).domFocus();
|
||||
}
|
||||
},
|
||||
close(e: Event) {
|
||||
this.$emit('update:visible', false);
|
||||
this.$emit('close', e);
|
||||
},
|
||||
// onMaskClick(e) {
|
||||
// if (!this.maskClosable) {
|
||||
// return;
|
||||
// }
|
||||
// this.close(e);
|
||||
// },
|
||||
push() {
|
||||
this.setState({
|
||||
sPush: true,
|
||||
const parentDrawerOpts = inject('parentDrawerOpts', null);
|
||||
|
||||
onBeforeMount(() => {
|
||||
provide('parentDrawerOpts', {
|
||||
setPush,
|
||||
setPull,
|
||||
});
|
||||
},
|
||||
pull() {
|
||||
this.setState(
|
||||
{
|
||||
sPush: false,
|
||||
},
|
||||
() => {
|
||||
this.domFocus();
|
||||
},
|
||||
);
|
||||
},
|
||||
onDestroyTransitionEnd() {
|
||||
const isDestroyOnClose = this.getDestroyOnClose();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const { visible } = props;
|
||||
if (visible && parentDrawerOpts) {
|
||||
parentDrawerOpts.setPush();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (parentDrawerOpts) {
|
||||
parentDrawerOpts.setPull();
|
||||
}
|
||||
});
|
||||
|
||||
onUpdated(() => {
|
||||
const { visible } = props;
|
||||
nextTick(() => {
|
||||
if (preVisible.value !== visible && parentDrawerOpts) {
|
||||
if (visible) {
|
||||
parentDrawerOpts.setPush();
|
||||
} else {
|
||||
parentDrawerOpts.setPull();
|
||||
}
|
||||
}
|
||||
preVisible.value = visible;
|
||||
});
|
||||
});
|
||||
|
||||
const domFocus = () => {
|
||||
vcDrawer.value?.domFocus?.();
|
||||
};
|
||||
|
||||
const close = (e: Event) => {
|
||||
emit('update:visible', false);
|
||||
emit('close', e);
|
||||
};
|
||||
|
||||
const afterVisibleChange = (visible: boolean) => {
|
||||
emit('afterVisibleChange', visible);
|
||||
};
|
||||
|
||||
const setPush = () => {
|
||||
sPush.value = true;
|
||||
};
|
||||
|
||||
const setPull = () => {
|
||||
sPush.value = false;
|
||||
nextTick(() => {
|
||||
domFocus();
|
||||
});
|
||||
};
|
||||
|
||||
const onDestroyTransitionEnd = () => {
|
||||
const isDestroyOnClose = getDestroyOnClose();
|
||||
if (!isDestroyOnClose) {
|
||||
return;
|
||||
}
|
||||
if (!this.visible) {
|
||||
this.destroyClose = true;
|
||||
(this as any).$forceUpdate();
|
||||
if (!props.visible) {
|
||||
destroyClose.value = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const getDestroyOnClose = () => {
|
||||
return props.destroyOnClose && !props.visible;
|
||||
};
|
||||
|
||||
const getPushTransform = (placement?: placementType) => {
|
||||
const { push } = props;
|
||||
let distance: number | string;
|
||||
if (typeof push === 'boolean') {
|
||||
distance = push ? defaultPushState.distance : 0;
|
||||
} else {
|
||||
distance = push!.distance;
|
||||
}
|
||||
|
||||
distance = parseFloat(String(distance || 0));
|
||||
|
||||
getDestroyOnClose() {
|
||||
return this.destroyOnClose && !this.visible;
|
||||
},
|
||||
// get drawar push width or height
|
||||
getPushTransform(placement?: placementType) {
|
||||
if (placement === 'left' || placement === 'right') {
|
||||
return `translateX(${placement === 'left' ? 180 : -180}px)`;
|
||||
return `translateX(${placement === 'left' ? distance : -distance}px)`;
|
||||
}
|
||||
if (placement === 'top' || placement === 'bottom') {
|
||||
return `translateY(${placement === 'top' ? 180 : -180}px)`;
|
||||
return `translateY(${placement === 'top' ? distance : -distance}px)`;
|
||||
}
|
||||
},
|
||||
getRcDrawerStyle() {
|
||||
const { zIndex, placement, wrapStyle } = this.$props;
|
||||
const { sPush: push } = this.$data;
|
||||
};
|
||||
|
||||
const getRcDrawerStyle = () => {
|
||||
const { zIndex, placement, style, mask } = props;
|
||||
const offsetStyle = mask ? {} : getOffsetStyle();
|
||||
return {
|
||||
zIndex,
|
||||
transform: push ? this.getPushTransform(placement) : undefined,
|
||||
...wrapStyle,
|
||||
transform: sPush.value ? getPushTransform(placement) : undefined,
|
||||
...offsetStyle,
|
||||
...style,
|
||||
};
|
||||
},
|
||||
renderHeader(prefixCls: string) {
|
||||
const { closable, headerStyle } = this.$props;
|
||||
const title = getComponent(this, 'title');
|
||||
};
|
||||
|
||||
const renderHeader = (prefixCls: string) => {
|
||||
const { closable, headerStyle } = props;
|
||||
const extra = getPropsSlot(slots, props, 'extra');
|
||||
const title = getPropsSlot(slots, props, 'title');
|
||||
if (!title && !closable) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const headerClassName = title ? `${prefixCls}-header` : `${prefixCls}-header-no-title`;
|
||||
return (
|
||||
<div class={headerClassName} style={headerStyle}>
|
||||
{title && <div class={`${prefixCls}-title`}>{title}</div>}
|
||||
{closable ? this.renderCloseIcon(prefixCls) : null}
|
||||
<div
|
||||
class={classnames(`${prefixCls}-header`, {
|
||||
[`${prefixCls}-header-close-only`]: closable && !title && !extra,
|
||||
})}
|
||||
style={headerStyle}
|
||||
>
|
||||
<div class={`${prefixCls}-header-title`}>
|
||||
{renderCloseIcon(prefixCls)}
|
||||
{title && <div class={`${prefixCls}-title`}>{title}</div>}
|
||||
</div>
|
||||
{extra && <div class={`${prefixCls}-extra`}>{extra}</div>}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
renderCloseIcon(prefixCls: string) {
|
||||
const { closable } = this;
|
||||
};
|
||||
|
||||
const renderCloseIcon = (prefixCls: string) => {
|
||||
const { closable } = props;
|
||||
const $closeIcon = getPropsSlot(slots, props, 'closeIcon');
|
||||
return (
|
||||
closable && (
|
||||
<button key="closer" onClick={this.close} aria-label="Close" class={`${prefixCls}-close`}>
|
||||
<CloseOutlined />
|
||||
<button key="closer" onClick={close} aria-label="Close" class={`${prefixCls}-close`}>
|
||||
{$closeIcon}
|
||||
</button>
|
||||
)
|
||||
);
|
||||
},
|
||||
// render drawer body dom
|
||||
renderBody(prefixCls: string) {
|
||||
if (this.destroyClose && !this.visible) {
|
||||
};
|
||||
|
||||
const renderBody = (prefixCls: string) => {
|
||||
if (destroyClose.value && !props.visible) {
|
||||
return null;
|
||||
}
|
||||
this.destroyClose = false;
|
||||
const { bodyStyle, drawerStyle } = this.$props;
|
||||
destroyClose.value = false;
|
||||
|
||||
const { bodyStyle, drawerStyle } = props;
|
||||
|
||||
const containerStyle: CSSProperties = {};
|
||||
|
||||
const isDestroyOnClose = this.getDestroyOnClose();
|
||||
const isDestroyOnClose = getDestroyOnClose();
|
||||
if (isDestroyOnClose) {
|
||||
// Increase the opacity transition, delete children after closing.
|
||||
containerStyle.opacity = 0;
|
||||
|
@ -194,74 +242,93 @@ const Drawer = defineComponent({
|
|||
<div
|
||||
class={`${prefixCls}-wrapper-body`}
|
||||
style={{ ...containerStyle, ...drawerStyle }}
|
||||
onTransitionend={this.onDestroyTransitionEnd}
|
||||
onTransitionend={onDestroyTransitionEnd}
|
||||
>
|
||||
{this.renderHeader(prefixCls)}
|
||||
{renderHeader(prefixCls)}
|
||||
<div key="body" class={`${prefixCls}-body`} style={bodyStyle}>
|
||||
{this.$slots.default?.()}
|
||||
{slots.default?.()}
|
||||
</div>
|
||||
{renderFooter(prefixCls)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const props: any = getOptionProps(this);
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
width,
|
||||
height,
|
||||
visible,
|
||||
placement,
|
||||
wrapClassName,
|
||||
mask,
|
||||
...rest
|
||||
} = props;
|
||||
const haveMask = mask ? '' : 'no-mask';
|
||||
const offsetStyle: CSSProperties = {};
|
||||
if (placement === 'left' || placement === 'right') {
|
||||
offsetStyle.width = typeof width === 'number' ? `${width}px` : width;
|
||||
} else {
|
||||
offsetStyle.height = typeof height === 'number' ? `${height}px` : height;
|
||||
}
|
||||
const handler = getComponent(this, 'handle') || false;
|
||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('drawer', customizePrefixCls);
|
||||
const { class: className } = this.$attrs;
|
||||
const vcDrawerProps: any = {
|
||||
...this.$attrs,
|
||||
...omit(rest, [
|
||||
'closable',
|
||||
'destroyOnClose',
|
||||
'drawerStyle',
|
||||
'headerStyle',
|
||||
'bodyStyle',
|
||||
'title',
|
||||
'push',
|
||||
'visible',
|
||||
'getPopupContainer',
|
||||
'rootPrefixCls',
|
||||
'getPrefixCls',
|
||||
'renderEmpty',
|
||||
'csp',
|
||||
'pageHeader',
|
||||
'autoInsertSpaceInButton',
|
||||
]),
|
||||
onClose: this.close,
|
||||
handler,
|
||||
...offsetStyle,
|
||||
prefixCls,
|
||||
open: visible,
|
||||
showMask: mask,
|
||||
placement,
|
||||
class: classnames({
|
||||
[className as string]: !!className,
|
||||
[wrapClassName]: !!wrapClassName,
|
||||
[haveMask]: !!haveMask,
|
||||
}),
|
||||
wrapStyle: this.getRcDrawerStyle(),
|
||||
ref: 'vcDrawer',
|
||||
};
|
||||
return <VcDrawer {...vcDrawerProps}>{this.renderBody(prefixCls)}</VcDrawer>;
|
||||
|
||||
const renderFooter = (prefixCls: string) => {
|
||||
const footer = getPropsSlot(slots, props, 'footer');
|
||||
if (!footer) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const footerClassName = `${prefixCls}-footer`;
|
||||
return (
|
||||
<div class={footerClassName} style={props.footerStyle}>
|
||||
{footer}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getOffsetStyle = () => {
|
||||
// https://github.com/ant-design/ant-design/issues/24287
|
||||
const { visible, mask, placement, size, width, height } = props;
|
||||
if (!visible && !mask) {
|
||||
return {};
|
||||
}
|
||||
const offsetStyle: CSSProperties = {};
|
||||
if (placement === 'left' || placement === 'right') {
|
||||
const defaultWidth = size === 'large' ? 736 : 378;
|
||||
offsetStyle.width = typeof width === 'undefined' ? defaultWidth : width;
|
||||
} else {
|
||||
const defaultHeight = size === 'large' ? 736 : 378;
|
||||
offsetStyle.height = typeof height === 'undefined' ? defaultHeight : height;
|
||||
}
|
||||
return offsetStyle;
|
||||
};
|
||||
|
||||
return () => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
width,
|
||||
height,
|
||||
visible,
|
||||
placement,
|
||||
mask,
|
||||
className,
|
||||
...rest
|
||||
} = props;
|
||||
const offsetStyle = mask ? getOffsetStyle() : {};
|
||||
const haveMask = mask ? '' : 'no-mask';
|
||||
const getPrefixCls = configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('drawer', customizePrefixCls);
|
||||
const vcDrawerProps: any = {
|
||||
...attrs,
|
||||
...omit(rest, [
|
||||
'size',
|
||||
'closeIcon',
|
||||
'closable',
|
||||
'destroyOnClose',
|
||||
'drawerStyle',
|
||||
'headerStyle',
|
||||
'bodyStyle',
|
||||
'title',
|
||||
'push',
|
||||
]),
|
||||
...offsetStyle,
|
||||
onClose: close,
|
||||
afterVisibleChange,
|
||||
handler: false,
|
||||
prefixCls,
|
||||
open: visible,
|
||||
showMask: mask,
|
||||
placement,
|
||||
wrapperClassName: classnames({
|
||||
[className as string]: className,
|
||||
[haveMask]: !!haveMask,
|
||||
}),
|
||||
style: getRcDrawerStyle(),
|
||||
ref: vcDrawer,
|
||||
};
|
||||
return <VcDrawer {...vcDrawerProps}>{renderBody(prefixCls)}</VcDrawer>;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -6,33 +6,50 @@ subtitle: 抽屉
|
|||
cover: https://gw.alipayobjects.com/zos/alicdn/7z8NJQhFb/Drawer.svg
|
||||
---
|
||||
|
||||
屏幕边缘滑出的浮层面板。
|
||||
|
||||
## 何时使用
|
||||
|
||||
抽屉从父窗体边缘滑入,覆盖住部分父窗体内容。用户在抽屉内操作时不必离开当前任务,操作完成后,可以平滑地回到原任务。
|
||||
|
||||
- 当需要一个附加的面板来控制父窗体内容,这个面板在需要时呼出。比如,控制界面展示样式,往界面中添加内容。
|
||||
- 当需要在当前任务流中插入临时任务,创建或预览附加内容。比如展示协议条款,创建子对象。
|
||||
|
||||
## API
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| autoFocus | 抽屉展开后是否将焦点切换至其 Dom 节点 | boolean | true | 3.0.0 |
|
||||
| bodyStyle | 可用于设置 Drawer 内容部分的样式 | CSSProperties | - | |
|
||||
| className(原 wrapClassName) | 对话框外层容器的类名 | string | - | 3.0.0 |
|
||||
| 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 |
|
||||
| forceRender | 预渲染 Drawer 内元素 | boolean | - | false | 3.0.0 |
|
||||
| getContainer | 指定 Drawer 挂载的 HTML 节点 | HTMLElement \| `() => HTMLElement` \| Selectors | 'body' | |
|
||||
| maskClosable | 点击蒙层是否允许关闭 | boolean | true | |
|
||||
| headerStyle | 用于设置 Drawer 头部的样式 | CSSProperties | - | 3.0.0 |
|
||||
| height | 高度, 在 `placement` 为 `top` 或 `bottom` 时使用 | string \| number | 378 | |
|
||||
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
|
||||
| mask | 是否展示遮罩 | Boolean | true | |
|
||||
| maskStyle | 遮罩样式 | object | {} | |
|
||||
| maskClosable | 点击蒙层是否允许关闭 | boolean | true | |
|
||||
| maskStyle | 遮罩样式 | CSSProperties | {} | |
|
||||
| placement | 抽屉的方向 | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | |
|
||||
| push | 用于设置多层 Drawer 的推动行为 | boolean \| {distance: string \| number} | { distance: 180} | 3.0.0 |
|
||||
| size | 预设抽屉宽度(或高度),default `378px` 和 large `736px` | `default` \| `large` | `default` | 3.0.0 |
|
||||
| style(原 wrapStyle) | 可用于设置 Drawer 最外层容器的样式,和 `drawerStyle` 的区别是作用节点包括 `mask` | CSSProperties | - | 3.0.0 |
|
||||
| title | 标题 | string \| slot | - | |
|
||||
| visible(v-model) | Drawer 是否可见 | boolean | - | |
|
||||
| wrapClassName | 对话框外层容器的类名 | string | - | |
|
||||
| wrapStyle | 可用于设置 Drawer 最外层容器的样式,和 `drawerStyle` 的区别是作用节点包括 `mask` | object | - | |
|
||||
| drawerStyle | 用于设置 Drawer 弹出层的样式 | object | - | |
|
||||
| headerStyle | 用于设置 Drawer 头部的样式 | object | - | |
|
||||
| bodyStyle | 可用于设置 Drawer 内容部分的样式 | object | - | |
|
||||
| width | 宽度 | string \| number | 256 | |
|
||||
| height | 高度, 在 `placement` 为 `top` 或 `bottom` 时使用 | string \| number | 256 | |
|
||||
| width | 宽度 | string \| number | 378 | |
|
||||
| zIndex | 设置 Drawer 的 `z-index` | Number | 1000 | |
|
||||
| placement | 抽屉的方向 | 'top' \| 'right' \| 'bottom' \| 'left' | 'right' | |
|
||||
| handle | 设置后抽屉直接挂载到 DOM 上,你可以通过该 handle 控制抽屉打开关闭 | VNode \| slot | - | |
|
||||
| afterVisibleChange | 切换抽屉时动画结束后的回调 | function(visible) | 无 | |
|
||||
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
|
||||
|
||||
## 方法
|
||||
|
||||
| 名称 | 描述 | 类型 | 默认值 | 版本 |
|
||||
| ----- | ------------------------------------ | ----------- | ------ | ---- |
|
||||
| close | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | 无 | |
|
||||
| 名称 | 描述 | 类型 | 默认值 | 版本 |
|
||||
| ------------------ | ------------------------------------ | ----------------- | ------ | ---- |
|
||||
| afterVisibleChange | 切换抽屉时动画结束后的回调 | function(visible) | 无 | |
|
||||
| close | 点击遮罩层或右上角叉或取消按钮的回调 | function(e) | 无 | |
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
@import '../../style/themes/index';
|
||||
@import '../../style/themes/index.less';
|
||||
|
||||
// Preserve the typo for compatibility
|
||||
// https://github.com/ant-design/ant-design/issues/14628
|
||||
@dawer-prefix-cls: ~'@{ant-prefix}-drawer';
|
||||
|
||||
@drawer-prefix-cls: @dawer-prefix-cls;
|
||||
@drawer-prefix-cls: ~'@{ant-prefix}-drawer';
|
||||
@picker-prefix-cls: ~'@{ant-prefix}-picker';
|
||||
|
||||
.@{drawer-prefix-cls} {
|
||||
@drawer-header-close-padding: ceil(((@drawer-header-close-size - @font-size-lg) / 2));
|
||||
|
||||
position: fixed;
|
||||
z-index: @zindex-modal;
|
||||
width: 0%;
|
||||
|
@ -20,7 +19,10 @@
|
|||
|
||||
&-content-wrapper {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.@{drawer-prefix-cls}-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -38,12 +40,17 @@
|
|||
width: 100%;
|
||||
transition: transform @animation-duration-slow @ease-base-out;
|
||||
}
|
||||
&.@{drawer-prefix-cls}-open.no-mask {
|
||||
width: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
&-left {
|
||||
left: 0;
|
||||
|
||||
.@{drawer-prefix-cls} {
|
||||
&-content-wrapper {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.@{drawer-prefix-cls}-open {
|
||||
.@{drawer-prefix-cls}-content-wrapper {
|
||||
box-shadow: @shadow-1-right;
|
||||
|
@ -84,9 +91,6 @@
|
|||
height: 100%;
|
||||
transition: transform @animation-duration-slow @ease-base-out;
|
||||
}
|
||||
&.@{drawer-prefix-cls}-open.no-mask {
|
||||
height: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
&-top {
|
||||
|
@ -118,15 +122,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.@{drawer-prefix-cls}-open {
|
||||
.@{drawer-prefix-cls} {
|
||||
&-mask {
|
||||
height: 100%;
|
||||
opacity: 1;
|
||||
transition: none;
|
||||
animation: antdDrawerFadeIn @animation-duration-slow @ease-base-out;
|
||||
}
|
||||
}
|
||||
&.@{drawer-prefix-cls}-open .@{drawer-prefix-cls}-mask {
|
||||
height: 100%;
|
||||
opacity: 1;
|
||||
transition: none;
|
||||
animation: antdDrawerFadeIn @animation-duration-slow @ease-base-out;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
&-title {
|
||||
|
@ -147,19 +148,13 @@
|
|||
}
|
||||
|
||||
&-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: @zindex-popup-close;
|
||||
display: block;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
padding: 0;
|
||||
color: @text-color-secondary;
|
||||
display: inline-block;
|
||||
margin-right: 12px;
|
||||
color: @modal-close-color;
|
||||
font-weight: 700;
|
||||
font-size: @font-size-lg;
|
||||
font-style: normal;
|
||||
line-height: 56px;
|
||||
line-height: 1;
|
||||
text-align: center;
|
||||
text-transform: none;
|
||||
text-decoration: none;
|
||||
|
@ -179,27 +174,48 @@
|
|||
|
||||
&-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;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-close-only {
|
||||
padding-bottom: 0;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
&-header-no-title {
|
||||
color: @text-color;
|
||||
background: @drawer-bg;
|
||||
&-wrapper-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: 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;
|
||||
}
|
||||
&-wrapper-body {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
&-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 {
|
||||
|
@ -212,12 +228,20 @@
|
|||
opacity: 0;
|
||||
filter: ~'alpha(opacity=45)';
|
||||
transition: opacity @animation-duration-slow linear, height 0s ease @animation-duration-slow;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&-open {
|
||||
&-content {
|
||||
box-shadow: @shadow-2;
|
||||
}
|
||||
}
|
||||
|
||||
.@{picker-prefix-cls} {
|
||||
&-clear {
|
||||
background: @popover-background;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes antdDrawerFadeIn {
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
@import '../../style/themes/index';
|
||||
@import '../../style/mixins/index';
|
||||
@import './drawer';
|
||||
@import './rtl';
|
||||
|
||||
// .popover-customize-bg(@drawer-prefix-cls, @popover-background);
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
@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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -494,6 +494,9 @@
|
|||
@modal-footer-bg: transparent;
|
||||
@modal-footer-border-color-split: @border-color-split;
|
||||
@modal-mask-bg: fade(@black, 45%);
|
||||
@modal-close-color: @text-color-secondary;
|
||||
@modal-footer-padding-vertical: 10px;
|
||||
@modal-footer-padding-horizontal: 16px;
|
||||
|
||||
// Progress
|
||||
// --
|
||||
|
@ -876,9 +879,12 @@
|
|||
|
||||
// Drawer
|
||||
// ---
|
||||
@drawer-header-padding: 16px 24px;
|
||||
@drawer-body-padding: 24px;
|
||||
@drawer-header-padding: @padding-md @padding-lg;
|
||||
@drawer-body-padding: @padding-lg;
|
||||
@drawer-bg: @component-background;
|
||||
@drawer-footer-padding-vertical: @modal-footer-padding-vertical;
|
||||
@drawer-footer-padding-horizontal: @modal-footer-padding-horizontal;
|
||||
@drawer-header-close-size: 56px;
|
||||
|
||||
// Timeline
|
||||
// ---
|
||||
|
|
|
@ -3,25 +3,27 @@
|
|||
@drawer: drawer;
|
||||
.@{drawer} {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: 9999;
|
||||
> * {
|
||||
transition: transform @duration @ease-in-out-circ, opacity @duration @ease-in-out-circ,
|
||||
box-shaow @duration @ease-in-out-circ;
|
||||
transition: width 0s ease @duration, height 0s ease @duration, transform @duration @ease-in-out-circ;
|
||||
>* {
|
||||
transition: transform @duration @ease-in-out-circ, opacity @duration @ease-in-out-circ, box-shadow @duration @ease-in-out-circ;
|
||||
}
|
||||
&.@{drawer}-open {
|
||||
transition: transform @duration @ease-in-out-circ;
|
||||
}
|
||||
& &-mask {
|
||||
background: #000;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transition: opacity @duration @ease-in-out-circ, width 0s ease @duration,
|
||||
left: 0;
|
||||
transition: opacity @duration @ease-in-out-circ,
|
||||
height 0s ease @duration;
|
||||
display: block !important;
|
||||
}
|
||||
&-content-wrapper {
|
||||
position: fixed;
|
||||
position: absolute;
|
||||
background: #fff;
|
||||
}
|
||||
&-content {
|
||||
|
@ -77,41 +79,53 @@
|
|||
}
|
||||
&.@{drawer}-open {
|
||||
width: 100%;
|
||||
&.no-mask {
|
||||
width: 0%;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-left {
|
||||
top: 0;
|
||||
left: 0;
|
||||
.@{drawer} {
|
||||
&-handle {
|
||||
right: -40px;
|
||||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 2px 0 8px rgba(0, 0, 0, .15);
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
}
|
||||
&.@{drawer}-open {
|
||||
.@{drawer} {
|
||||
&-content-wrapper {
|
||||
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 2px 0 8px rgba(0, 0, 0, .15);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&-right {
|
||||
top: 0;
|
||||
right: 0;
|
||||
.@{drawer} {
|
||||
&-content-wrapper {
|
||||
right: 0;
|
||||
}
|
||||
&-handle {
|
||||
left: -40px;
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, .15);
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
}
|
||||
&.@{drawer}-open {
|
||||
& .@{drawer} {
|
||||
&-content-wrapper {
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: -2px 0 8px rgba(0, 0, 0, .15);
|
||||
}
|
||||
}
|
||||
&.no-mask {
|
||||
// https://github.com/ant-design/ant-design/issues/18607
|
||||
right: 1px;
|
||||
transform: translateX(1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
&-top,
|
||||
|
@ -122,60 +136,74 @@
|
|||
.@{drawer}-content {
|
||||
width: 100%;
|
||||
}
|
||||
.@{drawer}-content {
|
||||
height: 100%;
|
||||
}
|
||||
&.@{drawer}-open {
|
||||
height: 100%;
|
||||
&.no-mask {
|
||||
height: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
.@{drawer} {
|
||||
&-handle {
|
||||
left: 50%;
|
||||
margin-left: -20px;
|
||||
}
|
||||
}
|
||||
&.@{drawer}-open {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
&-top {
|
||||
top: 0;
|
||||
left: 0;
|
||||
.@{drawer} {
|
||||
&-handle {
|
||||
top: auto;
|
||||
bottom: -40px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
}
|
||||
&.@{drawer}-open {
|
||||
.@{drawer} {
|
||||
&-wrapper {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
&-content-wrapper {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, .15);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&-bottom {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
.@{drawer} {
|
||||
&-content-wrapper {
|
||||
bottom: 0;
|
||||
}
|
||||
&-handle {
|
||||
top: -40px;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, .15);
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
}
|
||||
&.@{drawer}-open {
|
||||
.@{drawer} {
|
||||
&-content-wrapper {
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.15);
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, .15);
|
||||
}
|
||||
}
|
||||
&.no-mask {
|
||||
// https://github.com/ant-design/ant-design/issues/18607
|
||||
bottom: 1px;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
&.@{drawer}-open {
|
||||
.@{drawer} {
|
||||
&-mask {
|
||||
opacity: 0.3;
|
||||
width: 100%;
|
||||
opacity: .3;
|
||||
height: 100%;
|
||||
animation: fadeIn 0.3s @ease-in-out-circ;
|
||||
transition: none;
|
||||
transition: opacity 0.3s @ease-in-out-circ;
|
||||
}
|
||||
&-handle {
|
||||
&-icon {
|
||||
|
@ -190,13 +218,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import Drawer from './src/DrawerWrapper';
|
||||
|
||||
export default Drawer;
|
|
@ -0,0 +1,513 @@
|
|||
import { defineComponent, reactive, onMounted, onUpdated, onUnmounted, nextTick, watch } 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 type { IDrawerChildProps } from './IDrawerPropTypes';
|
||||
|
||||
import {
|
||||
addEventListener,
|
||||
dataToArray,
|
||||
getTouchParentScroll,
|
||||
isNumeric,
|
||||
removeEventListener,
|
||||
transformArguments,
|
||||
transitionEndFun,
|
||||
transitionStr,
|
||||
windowIsUndefined,
|
||||
} from './utils';
|
||||
|
||||
const currentDrawer: Record<string, boolean> = {};
|
||||
|
||||
const DrawerChild = defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: DrawerChildProps,
|
||||
emits: ['close', 'handleClick', 'change'],
|
||||
setup(props, { emit, slots, expose }) {
|
||||
const state = reactive({
|
||||
levelDom: [],
|
||||
dom: null,
|
||||
contentWrapper: null,
|
||||
contentDom: null,
|
||||
maskDom: null,
|
||||
handlerDom: null,
|
||||
drawerId: null,
|
||||
timeout: null,
|
||||
passive: null,
|
||||
startPos: {
|
||||
x: null,
|
||||
y: null,
|
||||
},
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (!windowIsUndefined) {
|
||||
state.passive = supportsPassive ? { passive: false } : false;
|
||||
}
|
||||
const { open, getContainer, showMask, autoFocus } = props;
|
||||
const container = getContainer?.();
|
||||
state.drawerId = `drawer_id_${Number(
|
||||
(Date.now() + Math.random())
|
||||
.toString()
|
||||
.replace('.', Math.round(Math.random() * 9).toString()),
|
||||
).toString(16)}`;
|
||||
getLevelDom(props);
|
||||
if (open) {
|
||||
if (container && container.parentNode === document.body) {
|
||||
currentDrawer[state.drawerId] = open;
|
||||
}
|
||||
// 默认打开状态时推出 level;
|
||||
openLevelTransition();
|
||||
nextTick(() => {
|
||||
if (autoFocus) {
|
||||
domFocus();
|
||||
}
|
||||
});
|
||||
if (showMask) {
|
||||
props.scrollLocker?.lock();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
onUpdated(() => {
|
||||
const { open, getContainer, scrollLocker, showMask, autoFocus } = props;
|
||||
const container = getContainer?.();
|
||||
if (container && container.parentNode === document.body) {
|
||||
currentDrawer[state.drawerId] = !!open;
|
||||
}
|
||||
openLevelTransition();
|
||||
if (open) {
|
||||
if (autoFocus) {
|
||||
domFocus();
|
||||
}
|
||||
if (showMask) {
|
||||
scrollLocker?.lock();
|
||||
}
|
||||
} else {
|
||||
scrollLocker?.unLock();
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
const { open, scrollLocker } = props;
|
||||
delete currentDrawer[state.drawerId];
|
||||
if (open) {
|
||||
setLevelTransform(false);
|
||||
document.body.style.touchAction = '';
|
||||
}
|
||||
scrollLocker?.unLock();
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.placement,
|
||||
val => {
|
||||
if (val) {
|
||||
// test 的 bug, 有动画过场,删除 dom
|
||||
state.contentDom = null;
|
||||
if (state.contentWrapper) {
|
||||
state.contentWrapper.style.transition = `none`;
|
||||
setTimeout(() => {
|
||||
state.contentWrapper.style.transition = ``;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const domFocus = () => {
|
||||
state.dom?.focus?.();
|
||||
};
|
||||
|
||||
const removeStartHandler = (e: TouchEvent) => {
|
||||
if (e.touches.length > 1) {
|
||||
return;
|
||||
}
|
||||
state.startPos = {
|
||||
x: e.touches[0].clientX,
|
||||
y: e.touches[0].clientY,
|
||||
};
|
||||
};
|
||||
|
||||
const removeMoveHandler = (e: TouchEvent) => {
|
||||
if (e.changedTouches.length > 1) {
|
||||
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 === state.maskDom ||
|
||||
currentTarget === state.handlerDom ||
|
||||
(currentTarget === state.contentDom &&
|
||||
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 onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.keyCode === KeyCode.ESC) {
|
||||
e.stopPropagation();
|
||||
onClose(e);
|
||||
}
|
||||
};
|
||||
|
||||
const onClose = (e: Event) => {
|
||||
emit('close', e);
|
||||
};
|
||||
|
||||
const onWrapperTransitionEnd = (e: TransitionEvent) => {
|
||||
const { open, afterVisibleChange } = props;
|
||||
if (e.target === state.contentWrapper && e.propertyName.match(/transform$/)) {
|
||||
state.dom.style.transition = '';
|
||||
if (!open && getCurrentDrawerSome()) {
|
||||
document.body.style.overflowX = '';
|
||||
if (state.maskDom) {
|
||||
state.maskDom.style.left = '';
|
||||
state.maskDom.style.width = '';
|
||||
}
|
||||
}
|
||||
if (afterVisibleChange) {
|
||||
afterVisibleChange(!!open);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const openLevelTransition = () => {
|
||||
const { open, width, height } = props;
|
||||
const { isHorizontal, placementName } = getHorizontalBoolAndPlacementName();
|
||||
const contentValue = state.contentDom
|
||||
? state.contentDom.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 切换时可能会导至页面失去滚动条,所以需要时时获取。
|
||||
state.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, state.maskDom, state.handlerDom, state.contentDom];
|
||||
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,
|
||||
state.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,
|
||||
state.passive,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const addScrollingEffect = (right: number) => {
|
||||
const { placement, duration, ease } = props;
|
||||
const widthTransition = `width ${duration} ${ease}`;
|
||||
const transformTransition = `transform ${duration} ${ease}`;
|
||||
state.dom.style.transition = 'none';
|
||||
switch (placement) {
|
||||
case 'right':
|
||||
state.dom.style.transform = `translateX(-${right}px)`;
|
||||
break;
|
||||
case 'top':
|
||||
case 'bottom':
|
||||
state.dom.style.width = `calc(100% - ${right}px)`;
|
||||
state.dom.style.transform = 'translateZ(0)';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
clearTimeout(state.timeout);
|
||||
state.timeout = setTimeout(() => {
|
||||
if (state.dom) {
|
||||
state.dom.style.transition = `${transformTransition},${widthTransition}`;
|
||||
state.dom.style.width = '';
|
||||
state.dom.style.transform = '';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const remScrollingEffect = (right: number) => {
|
||||
const { placement, duration, ease } = props;
|
||||
|
||||
if (transitionStr) {
|
||||
document.body.style.overflowX = 'hidden';
|
||||
}
|
||||
state.dom.style.transition = 'none';
|
||||
let heightTransition: string;
|
||||
let widthTransition = `width ${duration} ${ease}`;
|
||||
const transformTransition = `transform ${duration} ${ease}`;
|
||||
switch (placement) {
|
||||
case 'left': {
|
||||
state.dom.style.width = '100%';
|
||||
widthTransition = `width 0s ${ease} ${duration}`;
|
||||
break;
|
||||
}
|
||||
case 'right': {
|
||||
state.dom.style.transform = `translateX(${right}px)`;
|
||||
state.dom.style.width = '100%';
|
||||
widthTransition = `width 0s ${ease} ${duration}`;
|
||||
if (state.maskDom) {
|
||||
state.maskDom.style.left = `-${right}px`;
|
||||
state.maskDom.style.width = `calc(100% + ${right}px)`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'top':
|
||||
case 'bottom': {
|
||||
state.dom.style.width = `calc(100% + ${right}px)`;
|
||||
state.dom.style.height = '100%';
|
||||
state.dom.style.transform = 'translateZ(0)';
|
||||
heightTransition = `height 0s ${ease} ${duration}`;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
clearTimeout(state.timeout);
|
||||
state.timeout = setTimeout(() => {
|
||||
if (state.dom) {
|
||||
state.dom.style.transition = `${transformTransition},${
|
||||
heightTransition ? `${heightTransition},` : ''
|
||||
}${widthTransition}`;
|
||||
state.dom.style.transform = '';
|
||||
state.dom.style.width = '';
|
||||
state.dom.style.height = '';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const getCurrentDrawerSome = () => !Object.keys(currentDrawer).some(key => currentDrawer[key]);
|
||||
|
||||
const getLevelDom = ({ level, getContainer }: IDrawerChildProps) => {
|
||||
if (windowIsUndefined) {
|
||||
return;
|
||||
}
|
||||
const container = getContainer?.();
|
||||
const parent = container ? (container.parentNode as HTMLElement) : null;
|
||||
state.levelDom = [];
|
||||
if (level === 'all') {
|
||||
const children: HTMLElement[] = parent ? Array.prototype.slice.call(parent.children) : [];
|
||||
children.forEach((child: HTMLElement) => {
|
||||
if (
|
||||
child.nodeName !== 'SCRIPT' &&
|
||||
child.nodeName !== 'STYLE' &&
|
||||
child.nodeName !== 'LINK' &&
|
||||
child !== container
|
||||
) {
|
||||
state.levelDom.push(child);
|
||||
}
|
||||
});
|
||||
} else if (level) {
|
||||
dataToArray(level).forEach(key => {
|
||||
document.querySelectorAll(key).forEach(item => {
|
||||
state.levelDom.push(item);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getHorizontalBoolAndPlacementName = () => {
|
||||
const { placement } = props;
|
||||
const isHorizontal = placement === 'left' || placement === 'right';
|
||||
const placementName = `translate${isHorizontal ? 'X' : 'Y'}`;
|
||||
return {
|
||||
isHorizontal,
|
||||
placementName,
|
||||
};
|
||||
};
|
||||
|
||||
const getDerivedStateFromProps = (
|
||||
props: IDrawerChildProps,
|
||||
{ prevProps }: { prevProps: IDrawerChildProps },
|
||||
) => {
|
||||
const nextState = {
|
||||
prevProps: props,
|
||||
};
|
||||
if (prevProps !== undefined) {
|
||||
const { placement, level } = props;
|
||||
if (placement !== prevProps.placement) {
|
||||
// test 的 bug, 有动画过场,删除 dom
|
||||
state.contentDom = null;
|
||||
}
|
||||
if (level !== prevProps.level) {
|
||||
getLevelDom(props);
|
||||
}
|
||||
}
|
||||
return nextState;
|
||||
};
|
||||
|
||||
expose({ getDerivedStateFromProps });
|
||||
|
||||
return () => {
|
||||
const {
|
||||
width,
|
||||
height,
|
||||
open: $open,
|
||||
prefixCls,
|
||||
placement,
|
||||
level,
|
||||
levelMove,
|
||||
ease,
|
||||
duration,
|
||||
getContainer,
|
||||
onChange,
|
||||
afterVisibleChange,
|
||||
showMask,
|
||||
maskClosable,
|
||||
maskStyle,
|
||||
keyboard,
|
||||
getOpenCount,
|
||||
scrollLocker,
|
||||
contentWrapperStyle,
|
||||
style,
|
||||
...otherProps
|
||||
} = props;
|
||||
// 首次渲染都将是关闭状态。
|
||||
const open = state.dom ? $open : false;
|
||||
const wrapperClassName = classnames(prefixCls, {
|
||||
[`${prefixCls}-${placement}`]: true,
|
||||
[`${prefixCls}-open`]: open,
|
||||
'no-mask': !showMask,
|
||||
});
|
||||
|
||||
const { placementName } = getHorizontalBoolAndPlacementName();
|
||||
// 百分比与像素动画不同步,第一次打用后全用像素动画。
|
||||
// const defaultValue = !this.contentDom || !level ? '100%' : `${value}px`;
|
||||
const placementPos = placement === 'left' || placement === 'top' ? '-100%' : '100%';
|
||||
const transform = open ? '' : `${placementName}(${placementPos})`;
|
||||
|
||||
return (
|
||||
<div
|
||||
{...omit(otherProps, ['switchScrollingEffect', 'autoFocus'])}
|
||||
tabindex={-1}
|
||||
class={wrapperClassName}
|
||||
style={style}
|
||||
ref={(c: HTMLElement | null) => {
|
||||
state.dom = c as HTMLElement;
|
||||
}}
|
||||
onKeydown={open && keyboard ? onKeyDown : undefined}
|
||||
onTransitionend={onWrapperTransitionEnd}
|
||||
>
|
||||
{showMask && (
|
||||
<div
|
||||
class={`${prefixCls}-mask`}
|
||||
onClick={maskClosable ? onClose : undefined}
|
||||
style={maskStyle}
|
||||
ref={c => {
|
||||
state.maskDom = c as HTMLElement;
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
class={`${prefixCls}-content-wrapper`}
|
||||
style={{
|
||||
transform,
|
||||
msTransform: transform,
|
||||
width: isNumeric(width) ? `${width}px` : width,
|
||||
height: isNumeric(height) ? `${height}px` : height,
|
||||
...contentWrapperStyle,
|
||||
}}
|
||||
ref={c => {
|
||||
state.contentWrapper = c as HTMLElement;
|
||||
}}
|
||||
>
|
||||
<div
|
||||
class={`${prefixCls}-content`}
|
||||
ref={c => {
|
||||
state.contentDom = c as HTMLElement;
|
||||
}}
|
||||
>
|
||||
{slots.children?.()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default DrawerChild;
|
|
@ -0,0 +1,118 @@
|
|||
import Child from './DrawerChild';
|
||||
import { initDefaultProps } from '../../_util/props-util';
|
||||
import { Teleport, defineComponent, ref, watch } from 'vue';
|
||||
import { DrawerProps } from './IDrawerPropTypes';
|
||||
import type { IDrawerProps } from './IDrawerPropTypes';
|
||||
|
||||
const DrawerWrapper = defineComponent({
|
||||
inheritAttrs: false,
|
||||
props: initDefaultProps(DrawerProps, {
|
||||
prefixCls: 'drawer',
|
||||
placement: 'left',
|
||||
getContainer: 'body',
|
||||
level: 'all',
|
||||
duration: '.3s',
|
||||
ease: 'cubic-bezier(0.78, 0.14, 0.15, 0.86)',
|
||||
afterVisibleChange: () => {},
|
||||
showMask: true,
|
||||
maskClosable: true,
|
||||
maskStyle: {},
|
||||
wrapperClassName: null,
|
||||
keyboard: true,
|
||||
forceRender: false,
|
||||
autoFocus: true,
|
||||
}),
|
||||
emits: ['handleClick', 'close'],
|
||||
|
||||
setup(props, { emit, expose, slots }) {
|
||||
const dom = ref<HTMLElement>(null);
|
||||
|
||||
const container = ref(props.getContainer || null);
|
||||
|
||||
const open = ref<boolean>(props.open);
|
||||
|
||||
const $forceRender = ref<boolean>(props.forceRender);
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
val => {
|
||||
if (!dom.value) {
|
||||
$forceRender.value = true;
|
||||
open.value = false;
|
||||
setTimeout(() => {
|
||||
open.value = true;
|
||||
});
|
||||
} else {
|
||||
open.value = val;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const getDerivedStateFromProps = (
|
||||
props: IDrawerProps,
|
||||
{ prevProps }: { prevProps: IDrawerProps },
|
||||
) => {
|
||||
const newState: {
|
||||
open?: boolean;
|
||||
prevProps: IDrawerProps;
|
||||
} = {
|
||||
prevProps: props,
|
||||
};
|
||||
if (typeof prevProps !== 'undefined' && props.open !== prevProps.open) {
|
||||
newState.open = props.open;
|
||||
}
|
||||
return newState;
|
||||
};
|
||||
|
||||
expose({ getDerivedStateFromProps });
|
||||
|
||||
const onHandleClick = (e: MouseEvent | KeyboardEvent) => {
|
||||
emit('handleClick', e);
|
||||
};
|
||||
|
||||
const onClose = (e: MouseEvent | KeyboardEvent) => {
|
||||
emit('close', e);
|
||||
};
|
||||
|
||||
return () => {
|
||||
const { afterVisibleChange, getContainer, wrapperClassName, forceRender, ...otherProps } =
|
||||
props;
|
||||
|
||||
let portal = null;
|
||||
if (!getContainer) {
|
||||
return (
|
||||
<div class={wrapperClassName || null} ref={dom}>
|
||||
<Child
|
||||
v-slots={{ children: slots.default }}
|
||||
{...otherProps}
|
||||
open={open.value}
|
||||
getContainer={() => dom.value}
|
||||
onClose={onClose}
|
||||
onHandleClick={onHandleClick}
|
||||
></Child>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if ($forceRender.value || open.value || dom.value) {
|
||||
portal = (
|
||||
<Teleport to={container.value}>
|
||||
<div class={wrapperClassName || null} ref={dom}>
|
||||
<Child
|
||||
v-slots={{ children: slots.default }}
|
||||
{...props}
|
||||
open={open.value}
|
||||
getContainer={() => dom.value}
|
||||
afterVisibleChange={afterVisibleChange}
|
||||
onClose={onClose}
|
||||
onHandleClick={onHandleClick}
|
||||
/>
|
||||
</div>
|
||||
</Teleport>
|
||||
);
|
||||
}
|
||||
return portal;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export default DrawerWrapper;
|
|
@ -1,29 +1,33 @@
|
|||
import PropTypes from '../../_util/vue-types';
|
||||
import { PropType, ExtractPropTypes } from 'vue';
|
||||
|
||||
const IProps = {
|
||||
width: PropTypes.any,
|
||||
height: PropTypes.any,
|
||||
defaultOpen: PropTypes.looseBool,
|
||||
firstEnter: PropTypes.looseBool,
|
||||
open: PropTypes.looseBool,
|
||||
export type IPlacement = 'left' | 'top' | 'right' | 'bottom';
|
||||
|
||||
const Props = {
|
||||
prefixCls: PropTypes.string,
|
||||
placement: PropTypes.string,
|
||||
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
style: PropTypes.object,
|
||||
placement: {
|
||||
type: String as PropType<IPlacement>,
|
||||
},
|
||||
class: PropTypes.string,
|
||||
level: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
|
||||
levelMove: PropTypes.oneOfType([PropTypes.number, PropTypes.func, PropTypes.array]),
|
||||
ease: PropTypes.string,
|
||||
duration: PropTypes.string,
|
||||
handler: PropTypes.any,
|
||||
ease: PropTypes.string,
|
||||
showMask: PropTypes.looseBool,
|
||||
maskStyle: PropTypes.object,
|
||||
className: PropTypes.string,
|
||||
wrapStyle: PropTypes.object,
|
||||
maskClosable: PropTypes.looseBool,
|
||||
maskStyle: PropTypes.object,
|
||||
afterVisibleChange: PropTypes.func,
|
||||
keyboard: PropTypes.looseBool,
|
||||
contentWrapperStyle: PropTypes.object,
|
||||
autoFocus: PropTypes.looseBool,
|
||||
open: PropTypes.looseBool,
|
||||
};
|
||||
|
||||
const IDrawerProps = {
|
||||
...IProps,
|
||||
const DrawerProps = {
|
||||
...Props,
|
||||
wrapperClassName: PropTypes.string,
|
||||
forceRender: PropTypes.looseBool,
|
||||
getContainer: PropTypes.oneOfType([
|
||||
|
@ -34,11 +38,16 @@ const IDrawerProps = {
|
|||
]),
|
||||
};
|
||||
|
||||
const IDrawerChildProps = {
|
||||
...IProps,
|
||||
type IDrawerProps = Partial<ExtractPropTypes<typeof DrawerProps>>;
|
||||
|
||||
const DrawerChildProps = {
|
||||
...Props,
|
||||
getContainer: PropTypes.func,
|
||||
getOpenCount: PropTypes.func,
|
||||
scrollLocker: PropTypes.any,
|
||||
switchScrollingEffect: PropTypes.func,
|
||||
};
|
||||
|
||||
export { IDrawerProps, IDrawerChildProps };
|
||||
type IDrawerChildProps = Partial<ExtractPropTypes<typeof DrawerChildProps>>;
|
||||
|
||||
export { DrawerProps, DrawerChildProps, IDrawerProps, IDrawerChildProps };
|
|
@ -1,5 +0,0 @@
|
|||
// base in 1.7.7
|
||||
// export this package's api
|
||||
import Drawer from './Drawer';
|
||||
|
||||
export default Drawer;
|
|
@ -1,47 +1,54 @@
|
|||
export function dataToArray(vars) {
|
||||
export function dataToArray(vars: any) {
|
||||
if (Array.isArray(vars)) {
|
||||
return vars;
|
||||
}
|
||||
return [vars];
|
||||
}
|
||||
const transitionEndObject = {
|
||||
const transitionEndObject: Record<string, string> = {
|
||||
transition: 'transitionend',
|
||||
WebkitTransition: 'webkitTransitionEnd',
|
||||
MozTransition: 'transitionend',
|
||||
OTransition: 'oTransitionEnd otransitionend',
|
||||
};
|
||||
export const transitionStr = Object.keys(transitionEndObject).filter(key => {
|
||||
export const transitionStr: string = Object.keys(transitionEndObject).filter(key => {
|
||||
if (typeof document === 'undefined') {
|
||||
return false;
|
||||
}
|
||||
const html = document.getElementsByTagName('html')[0];
|
||||
return key in (html ? html.style : {});
|
||||
})[0];
|
||||
export const transitionEnd = transitionEndObject[transitionStr];
|
||||
export const transitionEndFun: string = transitionEndObject[transitionStr];
|
||||
|
||||
export function addEventListener(target, eventType, callback, options) {
|
||||
export function addEventListener(
|
||||
target: HTMLElement,
|
||||
eventType: string,
|
||||
callback: (e: TouchEvent | Event) => void,
|
||||
options?: any,
|
||||
) {
|
||||
if (target.addEventListener) {
|
||||
target.addEventListener(eventType, callback, options);
|
||||
} else if (target.attachEvent) {
|
||||
target.attachEvent(`on${eventType}`, callback);
|
||||
} else if ((target as any).attachEvent) {
|
||||
// tslint:disable-line
|
||||
(target as any).attachEvent(`on${eventType}`, callback); // tslint:disable-line
|
||||
}
|
||||
}
|
||||
|
||||
export function removeEventListener(target, eventType, callback, options) {
|
||||
export function removeEventListener(
|
||||
target: HTMLElement,
|
||||
eventType: string,
|
||||
callback: (e: TouchEvent | Event) => void,
|
||||
options?: any,
|
||||
) {
|
||||
if (target.removeEventListener) {
|
||||
target.removeEventListener(eventType, callback, options);
|
||||
} else if (target.attachEvent) {
|
||||
target.detachEvent(`on${eventType}`, callback);
|
||||
} else if ((target as any).attachEvent) {
|
||||
// tslint:disable-line
|
||||
(target as any).detachEvent(`on${eventType}`, callback); // tslint:disable-line
|
||||
}
|
||||
}
|
||||
|
||||
export function transformArguments(arg, cb) {
|
||||
let result;
|
||||
if (typeof arg === 'function') {
|
||||
result = arg(cb);
|
||||
} else {
|
||||
result = arg;
|
||||
}
|
||||
export function transformArguments(arg: any, cb: any) {
|
||||
const result = typeof arg === 'function' ? arg(cb) : arg;
|
||||
if (Array.isArray(result)) {
|
||||
if (result.length === 2) {
|
||||
return result;
|
||||
|
@ -51,9 +58,8 @@ export function transformArguments(arg, cb) {
|
|||
return [result];
|
||||
}
|
||||
|
||||
export const isNumeric = value => {
|
||||
return !isNaN(parseFloat(value)) && isFinite(value); // eslint-disable-line
|
||||
};
|
||||
export const isNumeric = (value: string | number | undefined) =>
|
||||
!isNaN(parseFloat(value as string)) && isFinite(value as number);
|
||||
|
||||
export const windowIsUndefined = !(
|
||||
typeof window !== 'undefined' &&
|
||||
|
@ -61,7 +67,12 @@ export const windowIsUndefined = !(
|
|||
window.document.createElement
|
||||
);
|
||||
|
||||
export const getTouchParentScroll = (root, currentTarget, differX, differY) => {
|
||||
export const getTouchParentScroll = (
|
||||
root: HTMLElement,
|
||||
currentTarget: HTMLElement | Document | null,
|
||||
differX: number,
|
||||
differY: number,
|
||||
): boolean => {
|
||||
if (!currentTarget || currentTarget === document || currentTarget instanceof Document) {
|
||||
return false;
|
||||
}
|
||||
|
@ -92,10 +103,10 @@ export const getTouchParentScroll = (root, currentTarget, differX, differY) => {
|
|||
(isX &&
|
||||
(!x ||
|
||||
(x &&
|
||||
((currentTarget.scrollLeft >= scrollX && scrollX < 0) ||
|
||||
(currentTarget.scrollLeft <= 0 && scrollX > 0)))))
|
||||
((currentTarget.scrollLeft >= scrollX && differX < 0) ||
|
||||
(currentTarget.scrollLeft <= 0 && differX > 0)))))
|
||||
) {
|
||||
return getTouchParentScroll(root, currentTarget.parentNode, differX, differY);
|
||||
return getTouchParentScroll(root, currentTarget.parentNode as HTMLElement, differX, differY);
|
||||
}
|
||||
return false;
|
||||
};
|
Loading…
Reference in New Issue