element-plus/packages/components/dropdown/src/dropdown.vue
jeremywu dd19cae2bc
refactor(components): popper composables (#5035)
* refactor(components): popper composables

- Refactor popper composables

* updates

* updates for tooltip

* Updates for popper. TODO: fix controlled tooltip animation

* Fix controlled mode popper animation issue

* Add new feature for customizing tooltip theme

* Fix popover and popconfirm error

* - Add Collection component for wrapping a collection of component
- Add FocusTrap component for trap focus for popups
- Add RovingFocus component for roving focus component type
- Adjust dropdown component based on these newly added components
- Add popper-trigger component for placing the trigger
- TODO: Finish current dropdown component, and all component's tests plus documents

* Refactor popper

* Complete organizing popper

* Almost finish dropdown

* Update popper tests

* update only-child test

* Finish focus trap component test

* Finish tooltip content test

* Finish tooltip trigger tests

* Finish tooltip tests

* finish tests for Collection and RovingFocusGroup

* Fix test cases for timeselect & select & popover

* Fix popover, popconfirm, menu bug and test cases

* Fix select-v2 test error caused by updating popper

* Fix date-picker test issue for updating popper

* fix test cases

* Fix eslint

* Rebase dev & fix tests

* Remove unused code
2022-01-04 09:15:15 +08:00

212 lines
5.5 KiB
Vue

<template>
<div class="el-dropdown">
<el-tooltip
ref="popperRef"
:effect="effect"
:fallback-placements="['bottom', 'top', 'right', 'left']"
:gpu-acceleration="false"
:hide-after="hideTimeout"
:manual-mode="true"
:placement="placement"
:popper-class="`el-dropdown__popper ${popperClass}`"
:reference-element="referenceElementRef?.$el"
:trigger="trigger"
:show-after="showTimeout"
:stop-popper-mouse-event="false"
:virtual-ref="triggeringElementRef"
:virtual-triggering="splitButton"
append-to-body
pure
transition="el-zoom-in-top"
@show="$emit('visible-change', true)"
@hide="$emit('visible-change', false)"
>
<template #content>
<el-scrollbar
ref="scrollbar"
:wrap-style="wrapStyle"
tag="ul"
view-class="el-dropdown__list"
>
<el-focus-trap trapped @mount-on-focus="onMountOnFocus">
<el-roving-focus-group
:loop="loop"
:current-tab-id="currentTabId"
orientation="horizontal"
@current-tab-id-change="handleCurrentTabIdChange"
@entry-focus="handleEntryFocus"
>
<el-dropdown-collection>
<slot name="dropdown"></slot>
</el-dropdown-collection>
</el-roving-focus-group>
</el-focus-trap>
</el-scrollbar>
</template>
<template v-if="!splitButton" #default>
<div :class="dropdownTriggerKls">
<slot name="default" />
</div>
</template>
</el-tooltip>
<template v-if="splitButton">
<el-button-group>
<el-button
ref="referenceElementRef"
:size="dropdownSize"
:type="type"
@click="handlerMainButtonClick"
>
<slot name="default" />
</el-button>
<el-button
ref="triggeringElementRef"
:size="dropdownSize"
:type="type"
class="el-dropdown__caret-button"
>
<el-icon class="el-dropdown__icon"><arrow-down /></el-icon>
</el-button>
</el-button-group>
</template>
</div>
</template>
<script lang="ts">
import {
computed,
defineComponent,
getCurrentInstance,
provide,
ref,
toRef,
unref,
} from 'vue'
import ElButton from '@element-plus/components/button'
import ElTooltip from '@element-plus/components/tooltip'
import ElScrollbar from '@element-plus/components/scrollbar'
import ElIcon from '@element-plus/components/icon'
import ElFocusTrap from '@element-plus/components/focus-trap'
import ElRovingFocusGroup from '@element-plus/components/roving-focus-group'
import { addUnit } from '@element-plus/utils/util'
import { ArrowDown } from '@element-plus/icons-vue'
import { useSize } from '@element-plus/hooks'
import { ElCollection as ElDropdownCollection, dropdownProps } from './dropdown'
import { DROPDOWN_INJECTION_KEY } from './tokens'
import type { CSSProperties } from 'vue'
const { ButtonGroup: ElButtonGroup } = ElButton
export default defineComponent({
name: 'ElDropdown',
components: {
ElButton,
ElFocusTrap,
ElButtonGroup,
ElScrollbar,
ElDropdownCollection,
ElTooltip,
ElRovingFocusGroup,
ElIcon,
ArrowDown,
},
props: dropdownProps,
emits: ['visible-change', 'click', 'command'],
setup(props, { emit }) {
const _instance = getCurrentInstance()
const triggeringElementRef = ref()
const referenceElementRef = ref()
const popperRef = ref<InstanceType<typeof ElTooltip> | null>(null)
const contentRef = ref<HTMLElement | null>(null)
const visible = ref(false)
const scrollbar = ref(null)
const currentTabId = ref<string | null>(null)
const isUsingKeyboard = ref(false)
const wrapStyle = computed<CSSProperties>(() => ({
maxHeight: addUnit(props.maxHeight),
}))
const dropdownTriggerKls = computed(() => [
[dropdownSize.value ? `el-dropdown--${dropdownSize.value}` : ''],
])
function handleClick() {
popperRef.value?.onClose()
}
const dropdownSize = useSize()
function commandHandler(...args: any[]) {
emit('command', ...args)
}
function onItemEnter() {
// NOOP for now
}
function onItemLeave() {
const contentEl = unref(contentRef)
contentEl?.focus()
currentTabId.value = null
}
function handleCurrentTabIdChange(id: string) {
currentTabId.value = id
}
function handleEntryFocus(e: Event) {
if (!isUsingKeyboard.value) {
e.preventDefault()
e.stopImmediatePropagation()
}
}
provide(DROPDOWN_INJECTION_KEY, {
contentRef,
isUsingKeyboard,
onItemEnter,
onItemLeave,
})
provide('elDropdown', {
instance: _instance,
dropdownSize,
visible,
handleClick,
commandHandler,
trigger: toRef(props, 'trigger'),
hideOnClick: toRef(props, 'hideOnClick'),
})
const onMountOnFocus = (e: Event) => {
e.preventDefault()
contentRef.value?.focus?.({
preventScroll: true,
})
}
const handlerMainButtonClick = (event: MouseEvent) => {
emit('click', event)
}
return {
visible,
scrollbar,
wrapStyle,
dropdownTriggerKls,
dropdownSize,
currentTabId,
handleCurrentTabIdChange,
handlerMainButtonClick,
handleEntryFocus,
onMountOnFocus,
popperRef,
triggeringElementRef,
referenceElementRef,
}
},
})
</script>