version:element-plus 1.0.1-beta.0
<template>
<teleport to="body" :disabled="!appendToBody">
<transition
name="el-drawer-fade"
@after-enter="afterEnter"
@after-leave="afterLeave"
>
<!-- v-show 控制 -->
<!-- .el-drawer__wrapper 我用最新的发现没有这个div包裹,用的overlay -->
<div
v-show="modelValue"
ref="root"
class="el-drawer__wrapper"
tabindex="-1"
>
<!-- modelValue为true 才会加上 类名 'el-drawer__open' -->
<div
class="el-drawer__container"
:class="modelValue && 'el-drawer__open'"
tabindex="-1"
role="document"
@click.self="handleWrapperClick"
>
<!-- style 根据不同的方向 绑定不同的样式 -->
<div
ref="drawer"
aria-modal="true"
aria-labelledby="el-drawer__title"
:aria-label="title"
class="el-drawer"
:class="[direction, customClass]"
:style="isHorizontal ? 'width: ' + size : 'height: ' + size"
role="dialog"
tabindex="-1"
>
<!-- 如果设置了 withHeader 为 false ,则不渲染 header -->
<header
v-if="withHeader"
id="el-drawer__title"
class="el-drawer__header"
>
<slot name="title">
<span role="heading" tabindex="-1" :title="title">
{{ title }}
</span>
</slot>
<button
v-if="showClose"
:aria-label="'close ' + (title || 'drawer')"
class="el-drawer__close-btn"
type="button"
@click="closeDrawer"
>
<i class="el-drawer__close el-icon el-icon-close"></i>
</button>
</header>
<section v-if="state.rendered" class="el-drawer__body">
<slot></slot>
</section>
</div>
</div>
</div>
</transition>
</teleport>
</template>
<script lang="ts">
import {
defineComponent, ref, computed,
watch, nextTick,
onMounted,
} from 'vue'
import usePopup from '@element-plus/utils/popup/usePopup'
import Utils from '@element-plus/utils/aria'
import type { PropType } from 'vue'
type Hide = (cancel: boolean) => void
type DrawerDirection = 'ltr' | 'rtl' | 'ttb' | 'btt'
export default defineComponent({
name: 'ElDrawer',
// 官方文档说明也是说属性和dialog基本一致
props: {
modelValue: Boolean,
// Drawer 自身是否插入至 body 元素上。嵌套的 Drawer 必须指定该属性并赋值为 true
appendToBody: {
type: Boolean,
default: false,
},
// 关闭前的回调,会暂停 Drawer 的关闭,function(done),done 用于关闭 Drawer
beforeClose: Function as PropType<(hide: Hide) => void>,
customClass: {
type: String,
default: '',
},
direction: {
type: String as PropType<DrawerDirection>,
default: 'rtl',
validator: (val: DrawerDirection) => {
return ['ltr', 'rtl', 'ttb', 'btt'].indexOf(val) !== -1
},
},
// 是否显示关闭按钮
showClose: {
type: Boolean,
default: true,
},
// Drawer 窗体的大小, 当使用 number 类型时, 以像素为单位, 当使用 string 类型时, 请传入 'x%', 否则便会以 number 类型解释
size: {
type: String,
default: '30%',
},
title: {
type: String,
default: '',
},
// 文档没有说明
wrapperClosable: {
type: Boolean,
default: true,
},
// 控制是否显示 header 栏, 默认为 true, 当此项为 false 时, title attribute 和 title slot 均不生效
withHeader: {
type: Boolean,
default: true,
},
// 文档没有说明
openDelay: {
type: Number,
default: 0,
},
// 文档没有说明
closeDelay: {
type: Number,
default: 0,
},
// 文档没有说明
zIndex: Number,
// 是否需要遮罩层
modal: {
type: Boolean,
default: true,
},
// 文档没有说明
modalFade: {
type: Boolean,
default: true,
},
// 文档没有说明
modalClass: String,
// 文档没有说明
modalAppendToBody: {
type: Boolean,
default: true,
},
// 文档没有说明
lockScroll: {
type: Boolean,
default: true,
},
// 是否可以通过按下 ESC 关闭 Drawer
closeOnPressEscape: {
type: Boolean,
default: true,
},
// 文档没有说明
closeOnClickModal: {
type: Boolean,
default: false,
},
// 控制是否在关闭 Drawer 之后将子元素全部销毁
destroyOnClose: {
type: Boolean,
default: false,
},
},
emits: ['open', 'opened', 'close', 'closed', 'update:modelValue'],
setup(props, ctx) {
const {
state,
doAfterClose,
updateClosingFlag,
restoreBodyStyle,
} = usePopup(props, doClose)
const drawer = ref<HTMLElement>(null)
const root = ref<HTMLElement>(null)
const prevActiveElement = ref<HTMLElement>(null)
const closed = ref(false)
const isHorizontal = computed(() => props.direction === 'rtl' || props.direction === 'ltr')
function afterEnter() {
ctx.emit('opened')
}
function doClose() {
updateClosingFlag(true)
props.lockScroll && setTimeout(restoreBodyStyle, 200)
state.opened = false
doAfterClose()
}
function afterLeave() {
ctx.emit('closed')
}
function hide(cancel = true) {
if (cancel !== false) {
ctx.emit('update:modelValue', false)
ctx.emit('close')
if (props.destroyOnClose === true) {
state.rendered = false
}
closed.value = true
}
}
// modal 点击事件
function handleWrapperClick() {
if (props.wrapperClosable) {
closeDrawer()
}
}
// 点击关闭 drawer
function closeDrawer() {
if (typeof props.beforeClose === 'function') {
props.beforeClose(hide)
} else {
hide()
}
}
function handleClose() {
// This method here will be called by PopupManger, when the `closeOnPressEscape` was set to true
// pressing `ESC` will call this method, and also close the drawer.
// This method also calls `beforeClose` if there was one.
closeDrawer()
}
// 监听 modelValue
watch(
() => props.modelValue,
val => {
state.visible = val
// true
if (val) {
closed.value = false
ctx.emit('open')
// prevActiveElement = focus element
prevActiveElement.value = document.activeElement as HTMLElement
nextTick(() => {
// 找到第一个支持focus的子元素(递归)并且focus
Utils.focusFirstDescendant(drawer.value)
})
} else {
if (!closed.value) ctx.emit('close')
nextTick(() => {
prevActiveElement.value?.focus()
})
}
},
)
onMounted(() => {
if (props.modelValue) {
state.rendered = true
state.visible = true
}
})
return {
state,
root,
drawer,
closed,
afterEnter,
afterLeave,
handleWrapperClick,
isHorizontal,
closeDrawer,
handleClose,
}
},
})
</script>