跳到主要内容

dialog

dialog-content.ts

  • dialogContentProps
  • dialogContentEmits: close

dialogContentProps

  • center
  • alignCenter
  • closeIcon
  • customClass
  • draggable
  • fullscreen
  • showClose
  • title

dialog-content.vue

draggable

const { dialogRef, headerRef, bodyId, ns, style } = inject(dialogInjectionKey)!

const draggable = computed(() => props.draggable)
useDraggable(dialogRef, headerRef, draggable)

设置div attribute

这个div就是整个dialog overlay上的root,在这里同时设置到inject进来的ref上 外侧的overlay设置了zindexposition: fixed,然后这个div通过设置position: relative来layout

const composedDialogRef = composeRefs(focusTrapRef, dialogRef)

  <div
:ref="composedDialogRef"
:class="[
ns.b(),
ns.is('fullscreen', fullscreen),
ns.is('draggable', draggable),
ns.is('align-center', alignCenter),
{ [ns.m('center')]: center },
customClass,
]"
:style="style"
tabindex="-1"
>

设置header

显示title,如果配置了showClose则显示close button

    <header ref="headerRef" :class="ns.e('header')">
<slot name="header">
<span role="heading" :class="ns.e('title')">
{{ title }}
</span>
</slot>
<button
v-if="showClose"
:aria-label="t('el.dialog.close')"
:class="ns.e('headerbtn')"
type="button"
@click="$emit('close')"
>
<el-icon :class="ns.e('close')">
<component :is="closeIcon || Close" />
</el-icon>
</button>
</header>

设置dialog body

    <div :id="bodyId" :class="ns.e('body')">
<slot />
</div>
    <footer v-if="$slots.footer" :class="ns.e('footer')">
<slot name="footer" />
</footer>

packages\theme-chalk\src\dialog.scss

@include b(dialog) {
@include set-component-css-var('dialog', $dialog);

position: relative;
margin: var(#{getCssVarName('dialog-margin-top')}, 15vh) auto 50px;
background: getCssVar('dialog', 'bg-color');
border-radius: getCssVar('dialog', 'border-radius');
box-shadow: getCssVar('dialog', 'box-shadow');
box-sizing: border-box;
width: var(#{getCssVarName('dialog-width')}, 50%);

&:focus {
outline: none !important;
}

@include when(align-center) {
margin: auto;
}

dialog.ts

dialogProps

  • appendToBody
  • beforeClose
  • destroyOnClose
  • closeOnClickModal
  • closeOnPressEscape
  • lockScroll
  • modal
  • openDelay
  • closeDelay
  • top
  • modelValue
  • modalClass
  • width
  • zIndex
  • trapFocus
export const dialogProps = buildProps({
...dialogContentProps,
appendToBody: {
type: Boolean,
default: false,
},

dialogEmits

export const dialogEmits = {
open: () => true,
opened: () => true,
close: () => true,
closed: () => true,
[UPDATE_MODEL_EVENT]: (value: boolean) => isBoolean(value),
openAutoFocus: () => true,
closeAutoFocus: () => true,
}

use-dialog.ts

style

    const varPrefix = `--${namespace.value}-dialog` as const
if (!props.fullscreen) {
if (props.top) {
style[`${varPrefix}-margin-top`] = props.top
}
if (props.width) {
style[`${varPrefix}-width`] = addUnit(props.width)
}
}

functions

  function afterEnter() {
emit('opened')
}

function afterLeave() {
emit('closed')
emit(UPDATE_MODEL_EVENT, false)
if (props.destroyOnClose) {
rendered.value = false
}
}

function beforeLeave() {
emit('close')
}
  return {
afterEnter,
afterLeave,
beforeLeave,
handleClose,
onModalClick,
close,
doClose,
onOpenAutoFocus,
onCloseAutoFocus,
onCloseRequested,
onFocusoutPrevented,
titleId,
bodyId,
closed,
style,
overlayDialogStyle,
rendered,
visible,
zIndex,
}

dialog.vue

transition

https://vuejs.org/guide/built-ins/transition.html

The enter or leave can be triggered by one of the following:

  • Conditional rendering via v-if
  • Conditional display via v-show
  • Dynamic components toggling via the <component> special element
    <transition
name="dialog-fade"
@after-enter="afterEnter"
@after-leave="afterLeave"
@before-leave="beforeLeave"
>
<el-overlay
v-show="visible"
custom-mask-event
:mask="modal"
:overlay-class="modalClass"
:z-index="zIndex"
>

overlay

overlay的child div被css设置为同样全屏,然后事件都绑定在child div上 overlay控制visible, z-index,添加背景色0.5 alpha的白色遮罩

     <el-overlay
v-show="visible"
custom-mask-event
:mask="modal"
:overlay-class="modalClass"
:z-index="zIndex"
>
<div
role="dialog"
aria-modal="true"
:aria-label="title || undefined"
:aria-labelledby="!title ? titleId : undefined"
:aria-describedby="bodyId"
:class="`${ns.namespace.value}-overlay-dialog`"
:style="overlayDialogStyle"
@click="overlayEvent.onClick"
@mousedown="overlayEvent.onMousedown"
@mouseup="overlayEvent.onMouseup"
>

packages\theme-chalk\src\dialog.scss

.#{$namespace}-overlay-dialog {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
}

判断是否mousedown和mouseup都是点击在overlay本体上

const overlayEvent = useSameTarget(onModalClick)

function onModalClick() {
if (props.closeOnClickModal) {
handleClose()
}
}

focus-trap

  • 控制focus
  • 按esc键时会触发release-requested,进而触发dialog close
          <el-focus-trap
loop
:trapped="visible"
focus-start-el="container"
@focus-after-trapped="onOpenAutoFocus"
@focus-after-released="onCloseAutoFocus"
@focusout-prevented="onFocusoutPrevented"
@release-requested="onCloseRequested"
>
  function onCloseRequested() {
if (props.closeOnPressEscape) {
handleClose()
}
}

dialog-content

基本就是透传slot到el-dialog-content

            <el-dialog-content
v-if="rendered"
ref="dialogContentRef"
v-bind="$attrs"
:custom-class="customClass"
:center="center"
:align-center="alignCenter"
:close-icon="closeIcon"
:draggable="draggable"
:fullscreen="fullscreen"
:show-close="showClose"
:title="title"
@close="handleClose"
>
<template #header>
<slot
v-if="!$slots.title"
name="header"
:close="handleClose"
:title-id="titleId"
:title-class="ns.e('title')"
/>
<slot v-else name="title" />
</template>
<slot />
<template v-if="$slots.footer" #footer>
<slot name="footer" />
</template>
</el-dialog-content>