- 收敛 easyflow-ui-admin 的 lint、格式和类型问题 - 修正 demo 页面与管理端前端构建失败点 - 验证 pnpm lint 与 pnpm build 均已通过
233 lines
5.6 KiB
TypeScript
233 lines
5.6 KiB
TypeScript
import {
|
|
arrow,
|
|
computePosition,
|
|
type FlipOptions,
|
|
offset,
|
|
type OffsetOptions,
|
|
type Placement,
|
|
shift,
|
|
type ShiftOptions,
|
|
size,
|
|
} from '@floating-ui/dom';
|
|
|
|
export type FloatingOptions = {
|
|
trigger: string | HTMLElement;
|
|
triggerEvent?: string[];
|
|
floatContent: string | HTMLElement;
|
|
placement?: Placement;
|
|
offsetOptions?: OffsetOptions;
|
|
flipOptions?: FlipOptions;
|
|
shiftOptions?: ShiftOptions;
|
|
interactive?: boolean;
|
|
showArrow?: boolean;
|
|
onShow?: () => void;
|
|
onHide?: () => void;
|
|
syncWidth?: boolean;
|
|
syncWidthMode?: 'min' | 'equal';
|
|
};
|
|
|
|
export type FloatingInstance = {
|
|
destroy: () => void;
|
|
hide: () => void;
|
|
isVisible: () => boolean;
|
|
};
|
|
|
|
export const createFloating = ({
|
|
trigger,
|
|
triggerEvent,
|
|
floatContent,
|
|
placement = 'bottom',
|
|
offsetOptions,
|
|
flipOptions,
|
|
shiftOptions,
|
|
interactive,
|
|
showArrow,
|
|
onShow,
|
|
onHide,
|
|
syncWidth = false,
|
|
syncWidthMode = 'min',
|
|
}: FloatingOptions): FloatingInstance => {
|
|
if (typeof trigger === 'string') {
|
|
const triggerEl = document.querySelector(trigger);
|
|
if (!triggerEl) {
|
|
throw new Error(
|
|
"element not found by document.querySelector('" + trigger + "')",
|
|
);
|
|
} else {
|
|
trigger = triggerEl as HTMLElement;
|
|
}
|
|
}
|
|
|
|
let floating: HTMLElement;
|
|
if (typeof floatContent === 'string') {
|
|
const floatContentEl = document.querySelector(floatContent);
|
|
if (!floatContentEl) {
|
|
throw new Error(
|
|
"element not found by document.querySelector('" + floatContent + "')",
|
|
);
|
|
} else {
|
|
floating = floatContentEl as HTMLElement;
|
|
}
|
|
} else {
|
|
floating = floatContent as HTMLElement;
|
|
}
|
|
|
|
let arrowElement: HTMLElement;
|
|
if (showArrow) {
|
|
arrowElement = document.createElement('div');
|
|
arrowElement.style.position = 'absolute';
|
|
arrowElement.style.backgroundColor = '#222';
|
|
arrowElement.style.width = '8px';
|
|
arrowElement.style.height = '8px';
|
|
arrowElement.style.transform = 'rotate(45deg)';
|
|
arrowElement.style.display = 'none';
|
|
|
|
floating.firstElementChild!.before(arrowElement);
|
|
}
|
|
|
|
function updatePosition() {
|
|
computePosition(trigger as Element, floating, {
|
|
placement: placement,
|
|
middleware: [
|
|
offset(offsetOptions), // 手动偏移配置
|
|
// flip(flipOptions), // 注释掉自动翻转,强制向下弹出,避免遮挡顶部工具栏
|
|
shift(shiftOptions), //自动偏移(使得浮动元素能够进入视野)
|
|
...(showArrow ? [arrow({ element: arrowElement })] : []),
|
|
...(syncWidth
|
|
? [
|
|
size({
|
|
apply({ rects, elements }) {
|
|
if (syncWidthMode === 'equal') {
|
|
Object.assign(elements.floating.style, {
|
|
width: `${rects.reference.width}px`,
|
|
minWidth: `${rects.reference.width}px`,
|
|
});
|
|
} else {
|
|
Object.assign(elements.floating.style, {
|
|
width: '',
|
|
minWidth: `${rects.reference.width}px`,
|
|
});
|
|
}
|
|
},
|
|
}),
|
|
]
|
|
: []),
|
|
],
|
|
}).then(({ x, y, placement, middlewareData }) => {
|
|
Object.assign(floating.style, {
|
|
left: `${x}px`,
|
|
top: `${y}px`,
|
|
position: 'absolute',
|
|
});
|
|
|
|
if (showArrow) {
|
|
const { x: arrowX, y: arrowY } = middlewareData.arrow as {
|
|
x: number;
|
|
y: number;
|
|
};
|
|
const staticSide = {
|
|
top: 'bottom',
|
|
right: 'left',
|
|
bottom: 'top',
|
|
left: 'right',
|
|
}[placement.split('-')[0]] as string;
|
|
|
|
Object.assign(arrowElement.style, {
|
|
zIndex: -1,
|
|
left: arrowX != null ? `${arrowX}px` : '',
|
|
top: arrowY != null ? `${arrowY}px` : '',
|
|
right: '',
|
|
bottom: '',
|
|
[staticSide]: '2px',
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
let visible = false;
|
|
|
|
function showTooltip() {
|
|
floating.style.display = 'block';
|
|
floating.style.visibility = 'visible';
|
|
floating.style.position = 'absolute';
|
|
|
|
if (showArrow) {
|
|
arrowElement.style.display = 'block';
|
|
}
|
|
|
|
visible = true;
|
|
updatePosition();
|
|
onShow?.();
|
|
}
|
|
|
|
function hideTooltip() {
|
|
floating.style.display = 'none';
|
|
if (showArrow) {
|
|
arrowElement.style.display = 'none';
|
|
}
|
|
visible = false;
|
|
onHide?.();
|
|
}
|
|
|
|
function onTrigger(event: any) {
|
|
if (!visible) {
|
|
showTooltip();
|
|
} else {
|
|
hideTooltip();
|
|
}
|
|
}
|
|
|
|
function hideTooltipCompute(event: any) {
|
|
if (
|
|
floating.contains(event.target as Node) ||
|
|
(trigger as Node).contains(event.target as Node)
|
|
) {
|
|
return;
|
|
}
|
|
hideTooltip();
|
|
}
|
|
|
|
if (!triggerEvent || triggerEvent.length == 0) {
|
|
if (interactive) {
|
|
triggerEvent = ['click'];
|
|
} else {
|
|
triggerEvent = ['mouseenter', 'focus'];
|
|
}
|
|
}
|
|
|
|
triggerEvent.forEach((event) => {
|
|
(trigger as HTMLElement).addEventListener(event, onTrigger);
|
|
});
|
|
|
|
if (interactive) {
|
|
document.addEventListener('click', hideTooltipCompute);
|
|
} else {
|
|
['mouseleave', 'blur'].forEach((event) => {
|
|
trigger.addEventListener(event, hideTooltip);
|
|
});
|
|
}
|
|
|
|
return {
|
|
destroy() {
|
|
triggerEvent.forEach((event) => {
|
|
(trigger as HTMLElement).removeEventListener(event, onTrigger);
|
|
});
|
|
|
|
if (interactive) {
|
|
document.removeEventListener('click', hideTooltipCompute);
|
|
} else {
|
|
['mouseleave', 'blur'].forEach((event) => {
|
|
trigger.removeEventListener(event, hideTooltip);
|
|
});
|
|
}
|
|
},
|
|
hide() {
|
|
hideTooltip();
|
|
},
|
|
|
|
isVisible() {
|
|
return visible;
|
|
},
|
|
};
|
|
};
|