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'; import PropTypes from '../_util/vue-types'; import CloseOutlined from '@ant-design/icons-vue/CloseOutlined'; import { defaultConfigProvider } from '../config-provider'; import { tuple, withInstall } from '../_util/type'; import omit from '../_util/omit'; const PlacementTypes = tuple('top', 'right', 'bottom', 'left'); 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(), 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, }, 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 }]).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>; const Drawer = defineComponent({ name: 'ADrawer', inheritAttrs: false, 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); const parentDrawerOpts = inject('parentDrawerOpts', null); onBeforeMount(() => { provide('parentDrawerOpts', { setPush, setPull, }); }); 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 (!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)); if (placement === 'left' || placement === 'right') { return `translateX(${placement === 'left' ? distance : -distance}px)`; } if (placement === 'top' || placement === 'bottom') { return `translateY(${placement === 'top' ? distance : -distance}px)`; } }; const getRcDrawerStyle = () => { const { zIndex, placement, style, mask } = props; const offsetStyle = mask ? {} : getOffsetStyle(); return { zIndex, transform: sPush.value ? getPushTransform(placement) : undefined, ...offsetStyle, ...style, }; }; 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; } return (
{renderCloseIcon(prefixCls)} {title &&
{title}
}
{extra &&
{extra}
}
); }; const renderCloseIcon = (prefixCls: string) => { const { closable } = props; const $closeIcon = getPropsSlot(slots, props, 'closeIcon'); return ( closable && ( ) ); }; const renderBody = (prefixCls: string) => { if (destroyClose.value && !props.visible) { return null; } destroyClose.value = false; const { bodyStyle, drawerStyle } = props; const containerStyle: CSSProperties = {}; const isDestroyOnClose = getDestroyOnClose(); if (isDestroyOnClose) { // Increase the opacity transition, delete children after closing. containerStyle.opacity = 0; containerStyle.transition = 'opacity .3s'; } return (
{renderHeader(prefixCls)}
{slots.default?.()}
{renderFooter(prefixCls)}
); }; const renderFooter = (prefixCls: string) => { const footer = getPropsSlot(slots, props, 'footer'); if (!footer) { return null; } const footerClassName = `${prefixCls}-footer`; return (
{footer}
); }; 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 {renderBody(prefixCls)}; }; }, }); export default withInstall(Drawer);