diff --git a/packages/components/autocomplete/src/autocomplete.vue b/packages/components/autocomplete/src/autocomplete.vue index 07cc647556..fcc8b92aee 100644 --- a/packages/components/autocomplete/src/autocomplete.vue +++ b/packages/components/autocomplete/src/autocomplete.vue @@ -37,6 +37,7 @@ @keydown.down.prevent="highlight(highlightedIndex + 1)" @keydown.enter="handleKeyEnter" @keydown.tab="close" + @keydown.esc="handleKeyEscape" > @@ -18,13 +30,14 @@ import { computed, inject, onMounted, provide, ref, unref, watch } from 'vue' import { NOOP } from '@vue/shared' import { createPopper } from '@popperjs/core' +import ElFocusTrap from '@element-plus/components/focus-trap' import { useNamespace, useZIndex } from '@element-plus/hooks' import { POPPER_CONTENT_INJECTION_KEY, POPPER_INJECTION_KEY, formItemContextKey, } from '@element-plus/tokens' -import { usePopperContentProps } from './content' +import { usePopperContentEmits, usePopperContentProps } from './content' import { buildPopperOptions, unwrapMeasurableEl } from './utils' import type { WatchStopHandle } from 'vue' @@ -33,7 +46,7 @@ defineOptions({ name: 'ElPopperContent', }) -defineEmits(['mouseenter', 'mouseleave']) +const emit = defineEmits(usePopperContentEmits) const props = defineProps(usePopperContentProps) @@ -45,6 +58,7 @@ const formItemContext = inject(formItemContextKey, undefined) const { nextZIndex } = useZIndex() const ns = useNamespace('popper') const popperContentRef = ref() +const focusStartRef = ref('first') const arrowRef = ref() const arrowOffset = ref() provide(POPPER_CONTENT_INJECTION_KEY, { @@ -64,7 +78,8 @@ if ( }) } -const contentZIndex = ref(props.zIndex || nextZIndex()) +const contentZIndex = ref(props.zIndex || nextZIndex()) +const trapped = ref(false) const computedReference = computed( () => unwrapMeasurableEl(props.referenceEl) || unref(triggerRef) @@ -106,6 +121,43 @@ const togglePopperAlive = () => { modifiers: [...(options.modifiers || []), monitorable], })) updatePopper(false) + if (props.visible && props.focusOnShow) { + trapped.value = true + } else if (props.visible === false) { + trapped.value = false + } +} + +const onFocusAfterTrapped = () => { + emit('focus') +} + +const onFocusAfterReleased = () => { + focusStartRef.value = 'first' + emit('blur') +} + +const onFocusInTrap = (event: FocusEvent) => { + if (props.visible && !trapped.value) { + if (event.relatedTarget) { + ;(event.relatedTarget as HTMLElement)?.focus() + } + if (event.target) { + focusStartRef.value = event.target as typeof focusStartRef.value + } + trapped.value = true + } +} + +const onFocusoutPrevented = () => { + if (!props.trapping) { + trapped.value = false + } +} + +const onReleaseRequested = () => { + trapped.value = false + emit('close') } onMounted(() => { diff --git a/packages/components/select/src/select.vue b/packages/components/select/src/select.vue index 487f40990b..4b52c83a7b 100644 --- a/packages/components/select/src/select.vue +++ b/packages/components/select/src/select.vue @@ -155,7 +155,7 @@ @keydown="resetInputState" @keydown.down.prevent="navigateOptions('next')" @keydown.up.prevent="navigateOptions('prev')" - @keydown.esc.stop.prevent="visible = false" + @keydown.esc="handleKeydownEscape" @keydown.enter.stop.prevent="selectOption" @keydown.delete="deletePrevTag" @keydown.tab="visible = false" @@ -189,7 +189,7 @@ @keydown.down.stop.prevent="navigateOptions('next')" @keydown.up.stop.prevent="navigateOptions('prev')" @keydown.enter.stop.prevent="selectOption" - @keydown.esc.stop.prevent="visible = false" + @keydown.esc="handleKeydownEscape" @keydown.tab="visible = false" @mouseenter="inputHovering = true" @mouseleave="inputHovering = false" @@ -435,6 +435,7 @@ export default defineComponent({ handleBlur, handleClearClick, handleClose, + handleKeydownEscape, toggleMenu, selectOption, getValueKey, @@ -608,6 +609,7 @@ export default defineComponent({ handleBlur, handleClearClick, handleClose, + handleKeydownEscape, toggleMenu, selectOption, getValueKey, diff --git a/packages/components/select/src/useSelect.ts b/packages/components/select/src/useSelect.ts index 4628bd8a52..dacc73c96a 100644 --- a/packages/components/select/src/useSelect.ts +++ b/packages/components/select/src/useSelect.ts @@ -761,6 +761,14 @@ export const useSelect = (props, states: States, ctx) => { states.visible = false } + const handleKeydownEscape = (event: KeyboardEvent) => { + if (states.visible) { + event.preventDefault() + event.stopPropagation() + states.visible = false + } + } + const toggleMenu = () => { if (props.automaticDropdown) return if (!selectDisabled.value) { @@ -860,6 +868,7 @@ export const useSelect = (props, states: States, ctx) => { handleBlur, handleClearClick, handleClose, + handleKeydownEscape, toggleMenu, selectOption, getValueKey, diff --git a/packages/components/tooltip/src/content.vue b/packages/components/tooltip/src/content.vue index a1461d8d45..029b65d29f 100644 --- a/packages/components/tooltip/src/content.vue +++ b/packages/components/tooltip/src/content.vue @@ -32,6 +32,8 @@ :z-index="zIndex" @mouseenter="onContentEnter" @mouseleave="onContentLeave" + @blur="onBlur" + @close="onClose" >