element-ui element-plus drawer - 分析

源代码地址 - drawer

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>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值