@fesjs/fes-design
Version:
fes-design for PC
134 lines (131 loc) • 4.04 kB
JavaScript
import { ref, reactive, computed, watch, onMounted, onActivated, nextTick } from 'vue';
import { computePosition, offset, flip, shift, arrow } from '@floating-ui/dom';
import { isBoolean, isFunction } from 'lodash-es';
import { useVModel } from '@vueuse/core';
import popupManager from '../_util/popupManager';
import getElementFromVueInstance from '../_util/getElementFromVueInstance';
const MAP = {
bottom: 'up',
top: 'down',
left: 'right',
right: 'left'
};
var usePopper = (props, emit) => {
const visible = useVModel(props, 'modelValue', emit, {
passive: props.passive
});
const updateVisible = val => {
visible.value = val;
};
const virtualRect = ref(null);
const triggerRef = ref();
const popperRef = ref();
const arrowRef = ref();
const popperStyle = reactive({
zIndex: popupManager.nextZIndex()
});
const placement = ref(props.placement);
const cacheVisible = ref(true);
const transitionName = computed(() => {
const placementValue = placement.value;
return `fes-slide-${MAP[placementValue.split('-')[0]]}`;
});
const computePopper = () => {
if (isBoolean(props.disabled) && props.disabled) {
return;
}
if (isFunction(props.disabled) && props.disabled()) {
return;
}
if (!visible.value) {
return;
}
popperStyle.zIndex = popupManager.nextZIndex();
nextTick(() => {
const rawTriggerEl = getElementFromVueInstance(triggerRef.value);
const triggerRect = rawTriggerEl.getBoundingClientRect();
// trigger 不可见的时候立即隐藏,允许误差
if (triggerRect.width <= 1 && triggerRect.height <= 1) {
updateVisible(false);
return;
}
const triggerEl = props.trigger === 'contextmenu' // 仅在右键时,使用鼠标具体触发位置
? {
getBoundingClientRect: () => virtualRect.value && {
width: 0,
height: 0,
top: virtualRect.value.y,
right: virtualRect.value.x,
bottom: virtualRect.value.y,
left: virtualRect.value.x
},
contextElement: rawTriggerEl
} : rawTriggerEl;
const popperEl = popperRef.value;
computePosition(triggerEl, popperEl, {
placement: props.placement,
middleware: [offset(props.offset),
// 当位置不够时切换到对面方向
flip(),
// 当无法完全显示时,自动调整主轴位置
shift(), props.arrow && arrow({
element: arrowRef.value
})].filter(Boolean)
}).then(state => {
// 当方向改变时,动画需要重新执行
if (placement.value !== state.placement) {
cacheVisible.value = false;
nextTick(() => {
cacheVisible.value = true;
});
placement.value = state.placement;
return;
}
placement.value = state.placement;
Object.assign(popperEl.style, {
left: `${state.x}px`,
top: `${state.y}px`
});
if (props.arrow) {
// Accessing the data
const {
x: arrowX,
y: arrowY
} = state.middlewareData.arrow;
const staticSide = {
top: 'bottom',
right: 'left',
bottom: 'top',
left: 'right'
}[state.placement.split('-')[0]];
Object.assign(arrowRef.value.style, {
left: arrowX != null ? `${arrowX}px` : '',
top: arrowY != null ? `${arrowY}px` : '',
right: '',
bottom: '',
[staticSide]: '-3px'
});
}
});
});
};
const updateVirtualRect = value => {
virtualRect.value = value;
};
watch(virtualRect, computePopper);
onMounted(computePopper);
onActivated(computePopper);
return {
visible,
updateVisible,
triggerRef,
popperRef,
arrowRef,
popperStyle,
computePopper,
updateVirtualRect,
cacheVisible,
transitionName
};
};
export { usePopper as default };