kui-vue
Version:
A lightweight desktop UI component library suitable for Vue.js 2.
178 lines (158 loc) • 5.67 kB
JavaScript
export function setPlacement({
refSelection,
refPopper,
currentPlacement,
position = null,
transOrigin,
top,
left,
offset = 3,
}) {
if (!refPopper.value) return;
// 模式检测 & 基准矩形
let rect = null;
// 是否是鼠标右键/坐标模式
const isMouseMode =
position &&
typeof position.x === "number" &&
typeof position.y === "number";
if (isMouseMode) {
// 鼠标模式:0x0 虚拟矩形
rect = {
width: 0,
height: 0,
top: position.y,
bottom: position.y,
left: position.x,
right: position.x,
};
} else if (refSelection && refSelection.value) {
// 元素模式:真实 DOM 矩形
const selection = refSelection.value.$el || refSelection.value;
rect = selection.getBoundingClientRect();
} else {
return;
}
const pickerH = refPopper.value.offsetHeight;
const pickerW = refPopper.value.offsetWidth;
const { clientHeight, clientWidth, scrollTop, scrollLeft } =
document.documentElement;
// 计算居中坐标 (仅用于检测)
const centerLeft = rect.left + rect.width / 2 - pickerW / 2;
const centerTop = rect.top + rect.height / 2 - pickerH / 2;
const check = {
// 主轴
top: rect.top > pickerH + offset,
bottom: clientHeight - rect.bottom > pickerH + offset,
left: rect.left > pickerW + offset,
right: clientWidth - rect.right > pickerW + offset,
// 交叉轴 - 对齐检测
alignLeft: clientWidth - rect.left > pickerW,
alignRight: rect.right > pickerW,
alignTop: clientHeight - rect.top > pickerH,
alignBottom: rect.bottom > pickerH,
// 交叉轴 - 居中检测 (只有非鼠标模式才需要关心这个)
centerH: centerLeft > 0 && centerLeft + pickerW < clientWidth,
centerV: centerTop > 0 && centerTop + pickerH < clientHeight,
};
// 智能决策
let [side, align] = currentPlacement.value.split("-");
// [关键修复] 仅在鼠标模式下,强制补全对齐方向
if (isMouseMode && !align) {
// 鼠标点在哪里,菜单就从哪里开始,不能居中悬浮在鼠标上
if (side === "top" || side === "bottom")
align = "left"; // 默认 bottom-left
else if (side === "left" || side === "right") align = "top"; // 默认 right-top
}
// 主轴翻转 (Main Axis Flip)
if (side === "top" && !check.top && check.bottom) side = "bottom";
else if (side === "bottom" && !check.bottom && check.top) side = "top";
else if (side === "left" && !check.left && check.right) side = "right";
else if (side === "right" && !check.right && check.left) side = "left";
// 交叉轴翻转 (Cross Axis Logic)
if (side === "top" || side === "bottom") {
// 如果有明确对齐要求 (比如鼠标模式强制了 left,或者 Select 指定了 bottom-left)
if (align === "left" && !check.alignLeft && check.alignRight)
align = "right";
else if (align === "right" && !check.alignRight && check.alignLeft)
align = "left";
// 如果没有对齐要求 (说明是 Select 的 bottom 居中模式)
// 且居中放不下,尝试贴边降级
else if (!align && !check.centerH) {
if (check.alignLeft)
align = "left"; // 居中不行,试左贴边
else if (check.alignRight) align = "right"; // 左也不行,试右贴边
}
} else if (side === "left" || side === "right") {
if (align === "top" && !check.alignTop && check.alignBottom)
align = "bottom";
else if (align === "bottom" && !check.alignBottom && check.alignTop)
align = "top";
else if (!align && !check.centerV) {
if (check.alignTop) align = "top";
else if (check.alignBottom) align = "bottom";
}
}
// 生成最终 placement (如果是居中,align 为 undefined,字符串就是 'top' 等)
const finalPlacement = align ? `${side}-${align}` : side;
// 坐标计算
let calcTop = 0;
let calcLeft = 0;
let originX = "center";
let originY = "center";
// Y 轴
if (side === "top") {
calcTop = rect.top - pickerH - offset;
originY = "bottom";
} else if (side === "bottom") {
calcTop = rect.bottom + offset;
originY = "top";
} else {
// left/right
if (align === "top") {
calcTop = rect.top;
originY = "top";
} else if (align === "bottom") {
calcTop = rect.bottom - pickerH;
originY = "bottom";
} else {
// 垂直居中 (只有 Select 会进这里,鼠标模式已被强制 align)
calcTop = rect.top + (rect.height - pickerH) / 2;
originY = "center";
}
}
// X 轴
if (side === "left") {
calcLeft = rect.left - pickerW - offset;
originX = "right";
} else if (side === "right") {
calcLeft = rect.right + offset;
originX = "left";
} else {
// top/bottom
if (align === "left") {
calcLeft = rect.left;
originX = "left";
} else if (align === "right") {
calcLeft = rect.right - pickerW;
originX = "right";
} else {
// 水平居中 (只有 Select 会进这里)
calcLeft = rect.left + (rect.width - pickerW) / 2;
originX = "center";
}
}
// 边界修正 (Safe Clamp) ---
// 无论哪种模式,最后都要保证不飞出屏幕
if (calcLeft < 0) calcLeft = 0;
else if (calcLeft + pickerW > clientWidth) calcLeft = clientWidth - pickerW;
if (calcTop < 0) calcTop = 0;
else if (calcTop + pickerH > clientHeight) calcTop = clientHeight - pickerH;
// 赋值
top.value = calcTop + scrollTop;
left.value = calcLeft + scrollLeft;
transOrigin.value = `${originX} ${originY}`;
if (currentPlacement.value !== finalPlacement) {
currentPlacement.value = finalPlacement;
}
}