element-ui element-plus trap-focus - 分析

源代码地址 - trap-focus

version:element-plus 1.0.1-beta.0

这个指令只在dialog中使用了

import { nextTick } from 'vue'
import { on, off } from '@element-plus/utils/dom'
import { obtainAllFocusableElements, EVENT_CODE } from '@element-plus/utils/aria'

import type { ObjectDirective } from 'vue'

export const FOCUSABLE_CHILDREN = '_trap-focus-children'
export const TRAP_FOCUS_HANDLER = '_trap-focus-handler'

export interface ITrapFocusElement extends HTMLElement {
  [FOCUSABLE_CHILDREN]: HTMLElement[]
  [TRAP_FOCUS_HANDLER]: (e: KeyboardEvent) => void
}

// 中文文档示例 https://www.vue3js.cn/docs/zh/guide/custom-directive.html#%E9%92%A9%E5%AD%90%E5%87%BD%E6%95%B0
// api https://www.vue3js.cn/docs/zh/api/application-api.html#directive
// 现在可以直接调用生命周期函数
const TrapFocus: ObjectDirective = {
  beforeMount(el: ITrapFocusElement) {
    // obtainAllFocusableElements 是一个过滤el下FOCUSABLE_ELEMENT_SELECTORS所有可以focus和可见的函数
    // const FOCUSABLE_ELEMENT_SELECTORS =`a[href],button:not([disabled]),button:not([hidden]),:not([tabindex="-1"]),input:not([disabled]),input:not([type="hidden"]),select:not([disabled]),textarea:not([disabled])`
    el[FOCUSABLE_CHILDREN] = obtainAllFocusableElements(el)

    el[TRAP_FOCUS_HANDLER] = (e: KeyboardEvent) => {
      const focusableElement = el[FOCUSABLE_CHILDREN]
      // 如果el[FOCUSABLE_CHILDREN]中含有子元素 并且键盘按下的是`Tab`
      if (focusableElement.length > 0 && e.code === EVENT_CODE.tab) {
        // 如果只有一个元素
        if (focusableElement.length === 1) {
          // 阻止默认行为就是tab无效
          e.preventDefault()
          // 如果当前获得焦点的元素不是这唯一一个元素 那么让他获得焦点
          if (document.activeElement  !== focusableElement[0]) {
            focusableElement[0].focus()
          }
          return
        }

        const goingBackward = e.shiftKey
        const isFirst = e.target === focusableElement[0]
        const isLast = e.target === focusableElement[focusableElement.length - 1]
        // shift + tab 同事按下 并且是第一个元素的话
        // 个人理解就是 tab 反方向
        if (isFirst && goingBackward) {
          e.preventDefault()
          // 最后一个focus
          focusableElement[focusableElement.length - 1].focus()
        }
        // 没有shift 如果到了最后一个 则第一个focus
        if (isLast && !goingBackward) {
          e.preventDefault()
          focusableElement[0].focus()
        }

        // the is critical since jsdom did not implement user actions, you can only mock it
        // DELETE ME: when testing env switches to puppeteer
        if (process.env.NODE_ENV === 'test') {

          const index = focusableElement.findIndex(element => element === e.target)
          if (index !== -1) {
            focusableElement[goingBackward ? index - 1 : index + 1]?.focus()
          }
        }
      }
    }
    on(document, 'keydown', el[TRAP_FOCUS_HANDLER])
  },
  updated(el: ITrapFocusElement) {
    nextTick(() => {
      // 重新获取
      el[FOCUSABLE_CHILDREN] = obtainAllFocusableElements(el)
    })
  },
  unmounted(el: ITrapFocusElement) {
    off(document, 'keydown', el[TRAP_FOCUS_HANDLER])
  },
}

export default TrapFocus
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值