@coreui/vue-pro
Version:
UI Components Library for Vue.js
131 lines (108 loc) • 3.1 kB
text/typescript
import { ref, computed, watch, onUnmounted } from 'vue'
import type { Options, Placement } from '@popperjs/core'
import { usePopper } from './usePopper'
import { isRTL } from '../utils'
export const useDropdownWithPopper = <T extends HTMLElement = never>(
popperConfig?: Partial<Options>,
) => {
// References to the dropdown elements
const dropdownRefElement = ref<T | null>(null)
const dropdownMenuElement = ref<HTMLDivElement | null>(null)
// Reactive state to control dropdown visibility
const isOpen = ref<boolean>(false)
// Popper.js integration
const { popper, initPopper, destroyPopper } = usePopper()
// Computed Popper configuration based on RTL and user-provided settings
const _popperConfig = computed(() => ({
placement: (isRTL(dropdownRefElement.value) ? 'bottom-end' : 'bottom-start') as Placement,
modifiers: [
{
name: 'preventOverflow',
options: {
boundary: 'clippingParents',
},
},
{
name: 'offset',
options: {
offset: [0, 2],
},
},
],
...popperConfig,
}))
// Methods to control dropdown state
const closeDropdown = () => {
isOpen.value = false
}
const openDropdown = () => {
isOpen.value = true
}
const toggleDropdown = () => {
isOpen.value = !isOpen.value
}
const updatePopper = () => {
if (popper) {
popper.update()
}
}
// Event handlers
const handleKeyUp = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
isOpen.value = false
return
}
if (event.key === 'Tab') {
const activeElement = document.activeElement as HTMLElement
if (
dropdownRefElement.value?.contains(activeElement) ||
dropdownMenuElement.value?.contains(activeElement)
) {
return
}
isOpen.value = false
}
}
const handleMouseUp = (event: Event) => {
const target = event.target as HTMLElement
if (dropdownMenuElement.value?.contains(target) || dropdownRefElement.value?.contains(target)) {
return
}
isOpen.value = false
}
// Utility functions to manage event listeners
const addEventListeners = () => {
window.addEventListener('mouseup', handleMouseUp)
window.addEventListener('keyup', handleKeyUp)
}
const removeEventListeners = () => {
window.removeEventListener('mouseup', handleMouseUp)
window.removeEventListener('keyup', handleKeyUp)
}
// Watcher to handle side effects when `isOpen` changes
watch(isOpen, (open) => {
if (open) {
addEventListeners()
if (dropdownRefElement.value && dropdownMenuElement.value) {
initPopper(dropdownRefElement.value, dropdownMenuElement.value, _popperConfig.value)
}
} else {
removeEventListeners()
destroyPopper()
}
})
// Cleanup on component unmount
onUnmounted(() => {
removeEventListeners()
destroyPopper()
})
return {
dropdownRefElement,
dropdownMenuElement,
isOpen,
closeDropdown,
openDropdown,
toggleDropdown,
updatePopper,
}
}