mirror of
https://github.com/element-plus/element-plus.git
synced 2025-04-06 16:30:35 +08:00
fix(components): [focus-trap] tryFocus is invalid for document.body (#19272)
* fix(components): [focus-trap] optimize tryFocus * test: add test * chore: remove redundant code * test: optimize test
This commit is contained in:
parent
3bcad6d83d
commit
456cccdace
@ -3,6 +3,7 @@ import {
|
||||
focusFirstDescendant,
|
||||
getEdges,
|
||||
obtainAllFocusableElements,
|
||||
tryFocus,
|
||||
} from '../src/utils'
|
||||
|
||||
describe('focus-trap utils', () => {
|
||||
@ -53,4 +54,38 @@ describe('focus-trap utils', () => {
|
||||
focusFirstDescendant(focusable)
|
||||
expect(document.activeElement).toBe(focusable[0])
|
||||
})
|
||||
|
||||
describe('tryFocus', () => {
|
||||
it('should be focus the input element', () => {
|
||||
const input = document.querySelector('.focusable-input') as HTMLElement
|
||||
tryFocus(input)
|
||||
expect(document.activeElement).toBe(input)
|
||||
})
|
||||
|
||||
it('should be focus the span element', () => {
|
||||
const span = document.querySelector('.focusable-span') as HTMLElement
|
||||
tryFocus(span)
|
||||
expect(document.activeElement).toBe(span)
|
||||
})
|
||||
|
||||
it('should be focus the disabled input element', () => {
|
||||
const input = document.querySelector('[disabled]') as HTMLElement
|
||||
tryFocus(input)
|
||||
expect(document.activeElement).toBe(input)
|
||||
})
|
||||
|
||||
it('should be focus the document body', () => {
|
||||
const input = document.querySelector('.focusable-input') as HTMLElement
|
||||
tryFocus(input)
|
||||
expect(document.activeElement).not.toBe(document.body)
|
||||
tryFocus(document.body)
|
||||
expect(document.activeElement).toBe(document.body)
|
||||
})
|
||||
|
||||
it('should be focus the null element', () => {
|
||||
const activeElement = document.activeElement
|
||||
tryFocus(null)
|
||||
expect(document.activeElement).toBe(activeElement)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { isElement, isFocusable } from '@element-plus/utils'
|
||||
import { FOCUSOUT_PREVENTED, FOCUSOUT_PREVENTED_OPTS } from './tokens'
|
||||
|
||||
const focusReason = ref<'pointer' | 'keyboard'>()
|
||||
@ -81,8 +82,20 @@ export const tryFocus = (
|
||||
) => {
|
||||
if (element && element.focus) {
|
||||
const prevFocusedElement = document.activeElement
|
||||
let cleanup: boolean = false
|
||||
|
||||
if (
|
||||
isElement(element) &&
|
||||
!isFocusable(element) &&
|
||||
!element.getAttribute('tabindex')
|
||||
) {
|
||||
element.setAttribute('tabindex', '-1')
|
||||
cleanup = true
|
||||
}
|
||||
|
||||
element.focus({ preventScroll: true })
|
||||
lastAutomatedFocusTimestamp.value = window.performance.now()
|
||||
|
||||
if (
|
||||
element !== prevFocusedElement &&
|
||||
isSelectable(element) &&
|
||||
@ -90,6 +103,9 @@ export const tryFocus = (
|
||||
) {
|
||||
element.select()
|
||||
}
|
||||
if (isElement(element) && cleanup) {
|
||||
element.removeAttribute('tabindex')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,6 +49,7 @@ import { useNamespace, usePopperContainerId } from '@element-plus/hooks'
|
||||
import { composeEventHandlers } from '@element-plus/utils'
|
||||
import { ElPopperContent } from '@element-plus/components/popper'
|
||||
import ElTeleport from '@element-plus/components/teleport'
|
||||
import { tryFocus } from '@element-plus/components/focus-trap'
|
||||
import { TOOLTIP_INJECTION_KEY } from './constants'
|
||||
import { useTooltipContentProps } from './content'
|
||||
import type { PopperContentInstance } from '@element-plus/components/popper'
|
||||
@ -111,6 +112,7 @@ const ariaHidden = ref(true)
|
||||
|
||||
const onTransitionLeave = () => {
|
||||
onHide()
|
||||
isFocusInsideContent() && tryFocus(document.body)
|
||||
ariaHidden.value = true
|
||||
}
|
||||
|
||||
@ -161,6 +163,14 @@ const onBlur = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const isFocusInsideContent = (event?: FocusEvent) => {
|
||||
const popperContent: HTMLElement | undefined =
|
||||
contentRef.value?.popperContentRef
|
||||
const activeElement = (event?.relatedTarget as Node) || document.activeElement
|
||||
|
||||
return popperContent?.contains(activeElement)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => unref(open),
|
||||
(val) => {
|
||||
@ -187,5 +197,9 @@ defineExpose({
|
||||
* @description el-popper-content component instance
|
||||
*/
|
||||
contentRef,
|
||||
/**
|
||||
* @description validate current focus event is trigger inside el-popper-content
|
||||
*/
|
||||
isFocusInsideContent,
|
||||
})
|
||||
</script>
|
||||
|
@ -155,11 +155,7 @@ watch(
|
||||
)
|
||||
|
||||
const isFocusInsideContent = (event?: FocusEvent) => {
|
||||
const popperContent: HTMLElement | undefined =
|
||||
contentRef.value?.contentRef?.popperContentRef
|
||||
const activeElement = (event?.relatedTarget as Node) || document.activeElement
|
||||
|
||||
return popperContent && popperContent.contains(activeElement)
|
||||
return contentRef.value?.isFocusInsideContent(event)
|
||||
}
|
||||
|
||||
onDeactivated(() => open.value && hide())
|
||||
|
Loading…
x
Reference in New Issue
Block a user