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