element-ui element-plus dialog - 分析
源代码地址 - dialogversion:element-plus 1.0.1-beta.0index.vue<template><!-- dialog是v-if控制渲染的 --><template v-if="destroyOnClose && !visible"></template><template v-else>
·
version:element-plus 1.0.1-beta.0
index.vue
<template>
<!-- dialog是v-if控制渲染的 -->
<template v-if="destroyOnClose && !visible"></template>
<template v-else>
<!-- vue3 新增组件 teleport to表示元素出现在他的子元素中 -->
<!-- 个人理解就是 to的目标就是 调用appendChild -->
<teleport to="body" :disabled="!appendToBody">
<transition
name="dialog-fade"
@after-enter="afterEnter"
@after-leave="afterLeave"
>
<el-overlay
v-if="visible"
:z-index="zIndex"
:mask="modal"
@click="onModalClick"
>
<!-- $event.stopPropagation() 是防止冒泡给父标签mask 他点击就会触发onModalClick:关闭dialog -->
<div
ref="dialogRef"
v-trap-focus
:class="[
'el-dialog',
{
'is-fullscreen': fullscreen,
'el-dialog--center': center,
},
customClass,
]"
aria-modal="true"
role="dialog"
:aria-label="title || 'dialog'"
:style="style"
@click="$event.stopPropagation()"
>
<div class="el-dialog__header">
<!-- dialog 头部插槽 -->
<slot name="header">
<span class="el-dialog__title">
{{ title }}
</span>
</slot>
<button
aria-label="close"
class="el-dialog__headerbtn"
type="button"
@click="handleClose"
>
<i class="el-dialog__close el-icon el-icon-close"></i>
</button>
</div>
<div class="el-dialog__body">
<!-- body就是默认插槽 -->
<slot></slot>
</div>
<!-- footer插槽 -->
<div v-if="$slots.footer" class="el-dialog__footer">
<slot name="footer"></slot>
</div>
</div>
</el-overlay>
</transition>
</teleport>
</template>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
// https://blog.csdn.net/qq_33221861/article/details/111477433
import { TrapFocus } from '@element-plus/directives'
// 判断是不是 ['px', 'rem', 'em', 'vw', '%', 'vmin', 'vmax']这些结尾的
import { isValidWidthUnit } from '@element-plus/utils/validators'
// https://blog.csdn.net/qq_33221861/article/details/111475961
import { Overlay } from '@element-plus/overlay'
import {
default as useDialog,
CLOSE_EVENT,
CLOSED_EVENT,
OPEN_EVENT,
OPENED_EVENT,
UPDATE_MODEL_EVENT,
} from './useDialog'
import type { PropType, SetupContext } from 'vue'
export default defineComponent({
name: 'ElDialog',
components: {
'el-overlay': Overlay,
},
directives: {
TrapFocus,
},
props: {
appendToBody: {
type: Boolean,
default: false,
},
// 关闭前的回调,会暂停 Dialog 的关闭 function(done),done 用于关闭 Dialog
beforeClose: {
type: Function as PropType<(...args: any[]) => unknown>,
},
// 关闭时销毁 Dialog 中的元素
destroyOnClose: {
type: Boolean,
default: false,
},
// 是否对头部和底部采用居中布局
center: {
type: Boolean,
default: false,
},
customClass: {
type: String,
default: '',
},
// 是否可以通过点击 modal 关闭 Dialog
closeOnClickModal: {
type: Boolean,
default: true,
},
// 是否可以通过按下 ESC 关闭 Dialog
closeOnPressEscape: {
type: Boolean,
default: true,
},
// 是否为全屏 Dialog
// 就是dialog width:100%;height:100%; 填充完整个mask | body
fullscreen: {
type: Boolean,
default: false,
},
// 是否在 Dialog 出现时将 body 滚动锁定
lockScroll: {
type: Boolean,
default: true,
},
// 是否需要遮罩层 传给了overlay中的mask
modal: {
type: Boolean,
default: true,
},
// 是否显示关闭按钮
showClose: {
type: Boolean,
default: true,
},
title: {
type: String,
default: '',
},
// Dialog 打开的延时时间,单位毫秒
openDelay: {
type: Number,
default: 0,
},
closeDelay: {
type: Number,
default: 0,
},
// Dialog CSS 中的 margin-top 值
top: {
type: String,
default: '15vh',
},
// 是否显示 Dialog
modelValue: {
type: Boolean,
required: true,
},
// 宽度默认一半
width: {
type: String,
default: '50%',
validator: isValidWidthUnit,
},
zIndex: {
type: Number,
},
},
emits: [
OPEN_EVENT,
OPENED_EVENT,
CLOSE_EVENT,
CLOSED_EVENT,
UPDATE_MODEL_EVENT,
],
setup(props, ctx) {
return useDialog(props, ctx as SetupContext)
},
})
</script>
useDialog.ts
import { computed, ref, watch, nextTick, onMounted } from 'vue'
import isServer from '@element-plus/utils/isServer'
import { UPDATE_MODEL_EVENT } from '@element-plus/utils/constants'
import PopupManager from '@element-plus/utils/popup-manager'
import { clearTimer } from '@element-plus/utils/util'
import { useLockScreen, useRestoreActive, useModal } from '@element-plus/hooks'
import type { UseDialogProps } from './dialog'
import type { SetupContext } from '@vue/runtime-core'
export const CLOSE_EVENT = 'close'
export const OPEN_EVENT = 'open'
export const CLOSED_EVENT = 'closed'
export const OPENED_EVENT = 'opened'
export { UPDATE_MODEL_EVENT }
export default function(props: UseDialogProps, ctx: SetupContext) {
const visible = ref(false)
const closed = ref(false)
const dialogRef = ref(null)
const openTimer = ref<TimeoutHandle>(null)
const closeTimer = ref<TimeoutHandle>(null)
const zIndex = ref(props.zIndex || PopupManager.nextZIndex())
// 暴露了没有使用
const modalRef = ref<HTMLElement>(null)
const style = computed(() => {
const style = {} as CSSStyleDeclaration
// 如果fullscreen === false
if (!props.fullscreen) {
style.marginTop = props.top
// 采用props.width
if (props.width) {
style.width = props.width
}
}
return style
})
function afterEnter() {
ctx.emit(OPENED_EVENT)
}
function afterLeave() {
ctx.emit(CLOSED_EVENT)
// .sync
ctx.emit(UPDATE_MODEL_EVENT, false)
}
function open() {
clearTimer(closeTimer)
clearTimer(openTimer)
// 如果设置了打开的延迟并且 > 0
if (props.openDelay && props.openDelay > 0) {
// 设置定时器
openTimer.value = window.setTimeout(() => {
openTimer.value = null
doOpen()
}, props.openDelay)
} else {
doOpen()
}
}
function close() {
// if (this.willClose && !this.willClose()) return;
clearTimer(openTimer)
clearTimer(closeTimer)
// 关闭延迟
if (props.closeDelay && props.closeDelay > 0) {
closeTimer.value = window.setTimeout(() => {
closeTimer.value = null
doClose()
}, props.closeDelay)
} else {
doClose()
}
}
/**
* @description: handleClose 的回调
* @param {boolean} shouldCancel 文档中的done 用于关闭 Dialog
* @return {*}
*/
function hide(shouldCancel: boolean) {
if (shouldCancel) return
closed.value = true
visible.value = false
}
function handleClose() {
// 如果 beforeClose
if (props.beforeClose) {
// 调用
props.beforeClose(hide)
} else {
close()
}
}
function onModalClick() {
// 判断closeOnClickModal === true
if (props.closeOnClickModal) {
handleClose()
}
}
function doOpen() {
// ? 服务端不管?
if (isServer) {
return
}
// if (props.willOpen?.()) {
// return
// }
visible.value = true
}
function doClose() {
visible.value = false
}
// 如果 lockScroll为true
if (props.lockScroll) {
// 监听visible
// true -> 计算scrollBar + body padding-right 并赋值给body padding-right
// false -> 恢复原生的
useLockScreen(visible)
}
// 如果closeOnPressEscape为true
if (props.closeOnPressEscape) {
// 监听visible
// true -> 将 handleClose push 进一个数组(这个数组中全是handleClose方法,键盘按下 esc 触发数组中的最后一个元素:方法)
// false -> 找到这个 handleClose 在数组中的 index 并删除他
useModal({
handleClose,
}, visible)
}
// watch visible
// true -> 定义了一个变量 previousActive: HTMLElement = document.activeElement
// false -> previousActive.focus()
// 没怎么看懂这个
useRestoreActive(visible)
// 监听绑定的model值
watch(() => props.modelValue, val => {
// true
if (val) {
closed.value = false
open()
ctx.emit(OPEN_EVENT)
// this.$el.addEventListener('scroll', this.updatePopper)
nextTick(() => {
if (dialogRef.value) {
// dialogRef 存在 则滚动到最上面
dialogRef.value.scrollTop = 0
}
})
} else {
// this.$el.removeEventListener('scroll', this.updatePopper
close()
if (!closed.value) {
ctx.emit(CLOSE_EVENT)
}
}
})
onMounted(() => {
if (props.modelValue) {
visible.value = true
open()
}
})
return {
afterEnter,
afterLeave,
handleClose,
onModalClick,
closed,
dialogRef,
style,
modalRef,
visible,
zIndex,
}
}
更多推荐
已为社区贡献2条内容
所有评论(0)