magiccube-vue3
Version:
vue3-js版组件库
210 lines (185 loc) • 7.84 kB
JavaScript
import { ref, Teleport, onUnmounted, nextTick, getCurrentInstance } from 'vue'
import * as utils from './common'
export default {
props: {
picker: [Function, Object, null],
autoClose: {
type: Boolean,
default: true
}
},
emits: ['close'],
setup(props, { emit, slots, expose }) {
/**
* 每个dropdown都有一个的id
*/
const uuid = utils._uuid()
/* 动态锚点 */
const instance = getCurrentInstance()
const globalOptions = instance.appContext?.config?.globalProperties?.$ELEMENT
const TELEPORT_NAME = globalOptions.teleportName? `.${globalOptions.teleportName}` : 'body'
const itemFontSize = globalOptions.dropdownFontSize? globalOptions.dropdownFontSize + 'px' : ''
/**
* 父级可滚动元素集
*/
let scrollEventNode = null
const dropdownEl = ref(null)
const top = ref(0)
const left = ref(0)
const width = ref(0)
const reverse = ref(false)
const slide = ref(false)
const arrowLeft = ref(0)
const isFixedWidth = ref(false)
/**
* dropdown打开时存储top、left、父级的scrollTop、父级的scrollLeft初始值
* 在页面滚动时进行监听 dropdown会跟随改变位置
*/
let originDropdownSite = {}
const addScrollListener = () => {
scrollEventNode = utils.getParentScrollElement(props.picker)
if(scrollEventNode && scrollEventNode.length){
scrollEventNode.forEach(item => {
if ((item.scrollTop || item.scrollTop === 0) && !originDropdownSite.scrollTop && originDropdownSite.scrollTop !== 0) originDropdownSite.scrollTop = item.scrollTop
if ((item.scrollLeft || item.scrollLeft === 0) && !originDropdownSite.scrollLeft && originDropdownSite.scrollLeft !== 0) originDropdownSite.scrollLeft = item.scrollLeft
item.addEventListener('scroll', handleScrollListener)
})
} else {
window.addEventListener('scroll', handleWindowScrollListener)
}
}
/* 有滚动元素包裹 */
const removeScrollListener = () => {
if (!scrollEventNode) return
if(scrollEventNode.length){
scrollEventNode.forEach(item => item.removeEventListener('scroll', handleScrollListener))
} else {
window.removeEventListener('scroll', handleWindowScrollListener)
}
scrollEventNode = null
}
/* 滚动设置在body级别(页面自带滚动属性) */
const handleScrollListener = (e) => {
const y = e.target.scrollTop - originDropdownSite.scrollTop
const x = e.target.scrollLeft - originDropdownSite.scrollLeft
top.value = originDropdownSite.top - y + 'px'
left.value = originDropdownSite.left - x + 'px'
}
const handleWindowScrollListener = (e) => {
const target = e.target.scrollingElement
const y = target.scrollTop
const x = target.scrollLeft
top.value = originDropdownSite.top - y + 10 + 'px'
left.value = originDropdownSite.left - x + 'px'
}
/**
* 点击dropdown以外地方自动收起
*/
const handleClickListener = (e) => {
try{
const item = props.picker
const _dropdownEl = document.getElementById(uuid)
if(!_dropdownEl) return
if (item && !item.contains?.(e.target) && !_dropdownEl?.contains?.(e.target)) emit('close')
} catch(err) {
throw new Error(err.message)
}
}
/**
* 展开 slide-down
*/
const visible = (options) => {
slide.value = true
const { event, pickerHeight, pickerWidth, dropdownWidth, dropdownHeight, fixedWidth } = options
const clientHeight = event.clientY
const pageWidth = document.body.clientWidth
const pageHeight = document.body.clientHeight
const pickerTop = event.clientY - event.offsetY
const pickerLeft = event.clientX - event.offsetX
let _y = 0
let _x = 0
width.value = dropdownWidth || pickerWidth || dropdownEl.value.offsetWidth
const _w = fixedWidth? (width.value) : Math.max(dropdownWidth, pickerWidth, dropdownEl.value.offsetWidth)
// 兼容日历的宽度限制
isFixedWidth.value = fixedWidth
/** 计算是否超出屏幕 向下或向上打开 */
if ((clientHeight + dropdownHeight) > pageHeight) {
_y = pickerTop - dropdownHeight - 10
// 如果反向打开超出屏幕 不进行反转 并顶部与屏幕对齐
if(_y < 0) {
_y = 0
} else {
reverse.value = true
}
} else {
_y = pickerTop + pickerHeight + 8
reverse.value = false
}
/** 计算是否超出屏幕 靠左或靠右 */
if(_w + pickerLeft > pageWidth){
_x = pickerLeft + (pageWidth - (_w + pickerLeft + 10))
} else {
_x = pickerLeft
}
arrowLeft.value = pickerLeft - _x + 16
top.value = _y + 'px'
left.value = _x + 'px'
originDropdownSite.top = _y
originDropdownSite.left = _x
addScrollListener()
}
/**
* 收起 slide-up
*/
const invisible = () => {
originDropdownSite = {}
slide.value = false
removeScrollListener()
}
const mouseleave = () => props.autoClose && emit('close', 'mouse-leave-dropdown')
nextTick(() => {
window.addEventListener('click', handleClickListener, true)
})
onUnmounted(() => {
window.removeEventListener('click', handleClickListener, true)
/**
* 注销并移除下拉dom
*/
removeScrollListener()
const target = document.getElementById(uuid)
target && target.parentNode.removeChild(target)
})
expose({
visible,
invisible
})
return () => (
<Teleport to={TELEPORT_NAME}>
<div ref={dropdownEl} id={uuid}
class={[
'mc-dropdown-panel',
{
'slide-down': slide.value,
'slide-up': !slide.value,
'reverse': reverse.value,
}
]}
onMouseleave={mouseleave}
style={{
width: isFixedWidth.value? width.value + 'px' : 'auto',
minWidth: width.value? width.value + 'px' : '',
top: top.value,
left: left.value,
// fontSize: itemFontSize //下拉控件影响到城市等控件 暂时不进行字号统一处理
}}>
{slots.default ? slots.default() : ''}
{/* 本项目目前UI规范中没有这个箭头,但是不要删除,以免以后会用到
<span class="mc-dropdown-panel__arrow"
style={{
left: arrowLeft.value + 'px'
}}></span> */}
</div>
</Teleport>
)
}
}