@whitesev/pops
Version:
弹窗库
590 lines (571 loc) • 16 kB
text/typescript
import { GlobalConfig } from "../../GlobalConfig";
import { PopsHandler } from "../../handler/PopsHandler";
import { pops } from "../../Pops";
import { popsDOMUtils } from "../../utils/PopsDOMUtils";
import { PopsSafeUtils } from "../../utils/PopsSafeUtils";
import { popsUtils } from "../../utils/PopsUtils";
import { PopsTooltipConfig } from "./config";
import type { PopsToolTipDetails } from "./indexType";
type ToolTipEventTypeName = "MouseEvent" | "TouchEvent";
export class ToolTip {
$el = {
$shadowContainer: null as unknown as HTMLDivElement,
$shadowRoot: null as unknown as ShadowRoot | HTMLElement,
$toolTip: null as unknown as HTMLElement,
$content: null as unknown as HTMLElement,
$arrow: null as unknown as HTMLElement,
};
$data = {
config: null as any as Required<PopsToolTipDetails>,
guid: null as any as string,
timeId_close_TouchEvent: <number[]>[],
timeId_close_MouseEvent: <number[]>[],
};
constructor(
config: Required<PopsToolTipDetails>,
guid: string,
ShadowInfo: {
$shadowContainer: HTMLDivElement;
$shadowRoot: ShadowRoot | HTMLElement;
}
) {
this.$data.config = config;
this.$data.guid = guid;
this.$el.$shadowContainer = ShadowInfo.$shadowContainer;
this.$el.$shadowRoot = ShadowInfo.$shadowRoot;
this.show = this.show.bind(this);
this.close = this.close.bind(this);
this.toolTipAnimationFinishEvent =
this.toolTipAnimationFinishEvent.bind(this);
this.toolTipMouseEnterEvent = this.toolTipMouseEnterEvent.bind(this);
this.toolTipMouseLeaveEvent = this.toolTipMouseLeaveEvent.bind(this);
this.init();
}
init() {
let toolTipInfo = this.createToolTip();
this.$el.$toolTip = toolTipInfo.$toolTipContainer;
this.$el.$content = toolTipInfo.$toolTipContent;
this.$el.$arrow = toolTipInfo.$toolTipArrow;
this.changeContent();
this.changeZIndex();
this.changePosition();
if (!this.$data.config.alwaysShow) {
this.offEvent();
this.onEvent();
}
}
/**
* 创建提示元素
*/
createToolTip() {
let $toolTipContainer = popsDOMUtils.createElement(
"div",
{
className: "pops-tip",
innerHTML: /*html*/ `
<div class="pops-tip-content" style="text-align: center;"></div>
<div class="pops-tip-arrow"></div>
`,
},
{
"data-position": this.$data.config.isFixed ? "fixed" : "absolute",
"data-guid": this.$data.guid,
}
);
/** 内容 */
let $toolTipContent =
$toolTipContainer.querySelector<HTMLElement>(".pops-tip-content")!;
/** 箭头 */
let $toolTipArrow =
$toolTipContainer.querySelector<HTMLElement>(".pops-tip-arrow")!;
// 处理className
if (
typeof this.$data.config.className === "string" &&
this.$data.config.className.trim() !== ""
) {
popsDOMUtils.addClassName($toolTipContainer, this.$data.config.className);
}
// 添加z-index
$toolTipContainer.style.zIndex = PopsHandler.handleZIndex(
this.$data.config.zIndex
).toString();
if (this.$data.config.style != null) {
/* 添加自定义style */
let cssNode = popsDOMUtils.createElement("style", {
type: "text/css",
innerHTML: this.$data.config.style,
});
$toolTipContainer.appendChild(cssNode);
}
// 处理是否显示箭头元素
if (!this.$data.config.showArrow) {
$toolTipArrow.remove();
}
return {
$toolTipContainer: $toolTipContainer,
$toolTipArrow: $toolTipArrow,
$toolTipContent: $toolTipContent,
};
}
/**
* 获取提示的内容
*/
getContent() {
return typeof this.$data.config.content === "function"
? this.$data.config.content()
: this.$data.config.content;
}
/**
* 修改提示的内容
* @param text
*/
changeContent(text?: string) {
if (text == null) {
text = this.getContent();
}
PopsSafeUtils.setSafeHTML(this.$el.$content, text);
}
/**
* 获取z-index
*/
getZIndex() {
const zIndex = PopsHandler.handleZIndex(this.$data.config.zIndex);
return zIndex;
}
/**
* 动态修改z-index
*/
changeZIndex() {
const zIndex = this.getZIndex();
this.$el.$toolTip.style.setProperty("z-index", zIndex.toString());
}
/**
* 计算 提示框的位置
* @param targetElement 目标元素
* @param arrowDistance 箭头和目标元素的距离
* @param otherDistance 其它位置的偏移
*/
calcToolTipPosition(
targetElement: HTMLElement,
arrowDistance: number,
otherDistance: number
) {
let offsetInfo = popsDOMUtils.offset(
targetElement,
!this.$data.config.isFixed
);
// 目标 宽
let targetElement_width = offsetInfo.width;
// 目标 高
let targetElement_height = offsetInfo.height;
// 目标 顶部距离
let targetElement_top = offsetInfo.top;
// let targetElement_bottom = offsetInfo.bottom;
// 目标 左边距离
let targetElement_left = offsetInfo.left;
// let targetElement_right = offsetInfo.right;
let toolTipElement_width = popsDOMUtils.outerWidth(this.$el.$toolTip);
let toolTipElement_height = popsDOMUtils.outerHeight(this.$el.$toolTip);
/* 目标元素的x轴的中间位置 */
let targetElement_X_center_pos =
targetElement_left + targetElement_width / 2 - toolTipElement_width / 2;
/* 目标元素的Y轴的中间位置 */
let targetElement_Y_center_pos =
targetElement_top + targetElement_height / 2 - toolTipElement_height / 2;
return {
TOP: {
left: targetElement_X_center_pos - otherDistance,
top: targetElement_top - toolTipElement_height - arrowDistance,
arrow: "bottom",
motion: "fadeInTop",
},
RIGHT: {
left: targetElement_left + targetElement_width + arrowDistance,
top: targetElement_Y_center_pos + otherDistance,
arrow: "left",
motion: "fadeInRight",
},
BOTTOM: {
left: targetElement_X_center_pos - otherDistance,
top: targetElement_top + targetElement_height + arrowDistance,
arrow: "top",
motion: "fadeInBottom",
},
LEFT: {
left: targetElement_left - toolTipElement_width - arrowDistance,
top: targetElement_Y_center_pos + otherDistance,
arrow: "right",
motion: "fadeInLeft",
},
};
}
/**
* 动态修改tooltip的位置
*/
changePosition() {
let positionInfo = this.calcToolTipPosition(
this.$data.config.target,
this.$data.config.arrowDistance,
this.$data.config.otherDistance
);
let positionKey =
this.$data.config.position.toUpperCase() as any as keyof typeof positionInfo;
let positionDetail = positionInfo[positionKey];
if (positionDetail) {
this.$el.$toolTip.style.left = positionDetail.left + "px";
this.$el.$toolTip.style.top = positionDetail.top + "px";
this.$el.$toolTip.setAttribute("data-motion", positionDetail.motion);
this.$el.$arrow.setAttribute("data-position", positionDetail.arrow);
} else {
console.error("不存在该位置", this.$data.config.position);
}
}
/**
* 事件绑定
*/
onEvent() {
// 监听动画结束事件
this.onToolTipAnimationFinishEvent();
this.onShowEvent();
this.onCloseEvent();
this.onToolTipMouseEnterEvent();
this.onToolTipMouseLeaveEvent();
}
/**
* 取消事件绑定
*/
offEvent() {
this.offToolTipAnimationFinishEvent();
this.offShowEvent();
this.offCloseEvent();
this.offToolTipMouseEnterEvent();
this.offToolTipMouseLeaveEvent();
}
/**
* 添加关闭的timeId
* @param type
* @param timeId
*/
addCloseTimeoutId(type: ToolTipEventTypeName, timeId: number) {
if (type === "MouseEvent") {
this.$data.timeId_close_MouseEvent.push(timeId);
} else {
this.$data.timeId_close_TouchEvent.push(timeId);
}
}
/**
* 清除延迟的timeId
* @param type 事件类型
*/
clearCloseTimeoutId(type: ToolTipEventTypeName, timeId?: number) {
let timeIdList =
type === "MouseEvent"
? this.$data.timeId_close_MouseEvent
: this.$data.timeId_close_TouchEvent;
for (let index = 0; index < timeIdList.length; index++) {
const currentTimeId = timeIdList[index];
if (typeof timeId === "number") {
// 只清除一个
if (timeId == currentTimeId) {
clearTimeout(timeId);
timeIdList.splice(index, 1);
break;
}
} else {
clearTimeout(currentTimeId);
timeIdList.splice(index, 1);
index--;
}
}
}
/**
* 显示提示框
*/
show(...args: any[]) {
let event = args[0] as MouseEvent | TouchEvent;
let eventType: ToolTipEventTypeName =
event instanceof MouseEvent ? "MouseEvent" : "TouchEvent";
this.clearCloseTimeoutId(eventType);
if (typeof this.$data.config.showBeforeCallBack === "function") {
let result = this.$data.config.showBeforeCallBack(this.$el.$toolTip);
if (typeof result === "boolean" && !result) {
return;
}
}
if (!popsUtils.contains(this.$el.$shadowRoot, this.$el.$toolTip)) {
// 不在容器中,添加
this.init();
popsDOMUtils.append(this.$el.$shadowRoot, this.$el.$toolTip);
}
if (!popsUtils.contains(this.$el.$shadowContainer)) {
// 页面不存在Shadow,添加
if (typeof this.$data.config.beforeAppendToPageCallBack === "function") {
this.$data.config.beforeAppendToPageCallBack(
this.$el.$shadowRoot,
this.$el.$shadowContainer
);
}
popsDOMUtils.append(document.body, this.$el.$shadowContainer);
}
// 更新内容
this.changeContent();
// 更新tip的位置
this.changePosition();
if (typeof this.$data.config.showAfterCallBack === "function") {
this.$data.config.showAfterCallBack(this.$el.$toolTip);
}
}
/**
* 绑定 显示事件
*/
onShowEvent() {
popsDOMUtils.on(
this.$data.config.target,
this.$data.config.triggerShowEventName,
this.show,
this.$data.config.eventOption
);
}
/**
* 取消绑定 显示事件
*/
offShowEvent() {
popsDOMUtils.off(
this.$data.config.target,
this.$data.config.triggerShowEventName,
this.show,
{
capture: true,
}
);
}
/**
* 关闭提示框
*/
close(...args: any[]) {
let event = args[0] as MouseEvent | TouchEvent;
let eventType: ToolTipEventTypeName =
event instanceof MouseEvent ? "MouseEvent" : "TouchEvent";
// 只判断鼠标事件
// 其它的如Touch事件不做处理
if (event && event instanceof MouseEvent) {
let $target = event.composedPath()[0];
// 如果是目标元素的子元素/tooltip元素的子元素触发的话,那就不管
if ($target != this.$data.config.target && $target != this.$el.$toolTip) {
return;
}
}
if (typeof this.$data.config.closeBeforeCallBack === "function") {
let result = this.$data.config.closeBeforeCallBack(this.$el.$toolTip);
if (typeof result === "boolean" && !result) {
return;
}
}
if (
this.$data.config.delayCloseTime == null ||
(typeof this.$data.config.delayCloseTime === "number" &&
this.$data.config.delayCloseTime <= 0)
) {
this.$data.config.delayCloseTime = 100;
}
let timeId = setTimeout(() => {
// 设置属性触发关闭动画
this.clearCloseTimeoutId(eventType, timeId);
if (this.$el.$toolTip == null) {
// 已清除了
return;
}
this.$el.$toolTip.setAttribute(
"data-motion",
this.$el.$toolTip
.getAttribute("data-motion")!
.replace("fadeIn", "fadeOut")
);
}, this.$data.config.delayCloseTime);
this.addCloseTimeoutId(eventType, timeId);
if (typeof this.$data.config.closeAfterCallBack === "function") {
this.$data.config.closeAfterCallBack(this.$el.$toolTip);
}
}
/**
* 绑定 关闭事件
*/
onCloseEvent() {
popsDOMUtils.on(
this.$data.config.target,
this.$data.config.triggerCloseEventName,
this.close,
this.$data.config.eventOption
);
}
/**
* 取消绑定 关闭事件
*/
offCloseEvent() {
popsDOMUtils.off(
this.$data.config.target,
this.$data.config.triggerCloseEventName,
this.close,
{
capture: true,
}
);
}
/**
* 销毁元素
*/
destory() {
if (this.$el.$toolTip) {
this.$el.$shadowRoot.removeChild(this.$el.$toolTip);
}
// @ts-ignore
this.$el.$toolTip = null;
// @ts-ignore
this.$el.$arrow = null;
// @ts-ignore
this.$el.$content = null;
}
/**
* 动画结束事件
*/
toolTipAnimationFinishEvent() {
if (!this.$el.$toolTip) {
return;
}
if (this.$el.$toolTip.getAttribute("data-motion")!.includes("In")) {
return;
}
this.destory();
}
/**
* 监听tooltip的动画结束
*/
onToolTipAnimationFinishEvent() {
popsDOMUtils.on(
this.$el.$toolTip,
popsDOMUtils.getAnimationEndNameList(),
this.toolTipAnimationFinishEvent
);
}
/**
* 取消tooltip监听动画结束
*/
offToolTipAnimationFinishEvent() {
popsDOMUtils.off(
this.$el.$toolTip,
popsDOMUtils.getAnimationEndNameList(),
this.toolTipAnimationFinishEvent
);
}
/**
* 鼠标|触摸进入事件
*/
toolTipMouseEnterEvent() {
this.clearCloseTimeoutId("MouseEvent");
this.clearCloseTimeoutId("TouchEvent");
// 重置动画状态
// this.$el.$toolTip.style.animationPlayState = "paused";
// if (parseInt(getComputedStyle(toolTipElement)) > 0.5) {
// toolTipElement.style.animationPlayState = "paused";
// }
}
/**
* 监听鼠标|触摸事件
*/
onToolTipMouseEnterEvent() {
this.clearCloseTimeoutId("MouseEvent");
this.clearCloseTimeoutId("TouchEvent");
popsDOMUtils.on(
this.$el.$toolTip,
"mouseenter touchstart",
this.toolTipMouseEnterEvent,
this.$data.config.eventOption
);
}
/**
* 取消监听鼠标|触摸事件
*/
offToolTipMouseEnterEvent() {
popsDOMUtils.off(
this.$el.$toolTip,
"mouseenter touchstart",
this.toolTipMouseEnterEvent,
this.$data.config.eventOption
);
}
/**
* 鼠标|触摸离开事件
*/
toolTipMouseLeaveEvent(event: MouseEvent | PointerEvent) {
this.close(event);
// this.$el.$toolTip.style.animationPlayState = "running";
}
/**
* 监听鼠标|触摸离开事件
*/
onToolTipMouseLeaveEvent() {
popsDOMUtils.on(
this.$el.$toolTip,
"mouseleave touchend",
this.toolTipMouseLeaveEvent,
this.$data.config.eventOption
);
}
/**
* 取消监听鼠标|触摸离开事件
*/
offToolTipMouseLeaveEvent() {
popsDOMUtils.off(
this.$el.$toolTip,
"mouseleave touchend",
this.toolTipMouseLeaveEvent,
this.$data.config.eventOption
);
}
}
export type PopsTooltipResult<T extends PopsToolTipDetails> = {
guid: string;
config: T;
$shadowContainer: HTMLDivElement;
$shadowRoot: ShadowRoot;
toolTip: typeof ToolTip.prototype;
};
export class PopsTooltip {
constructor(details: PopsToolTipDetails) {
const guid = popsUtils.getRandomGUID();
// 设置当前类型
const PopsType = "tooltip";
let config = PopsTooltipConfig();
config = popsUtils.assign(config, GlobalConfig.getGlobalConfig());
config = popsUtils.assign(config, details);
if (!(config.target instanceof HTMLElement)) {
throw "config.target 必须是HTMLElement类型";
}
config = PopsHandler.handleOnly(PopsType, config);
const { $shadowContainer, $shadowRoot } = PopsHandler.handlerShadow(config);
PopsHandler.handleInit($shadowRoot, [
pops.config.cssText.index,
pops.config.cssText.anim,
pops.config.cssText.common,
pops.config.cssText.tooltipCSS,
]);
let toolTip = new ToolTip(config, guid, {
$shadowContainer,
$shadowRoot,
});
if (config.alwaysShow) {
/* 总是显示 */
/* 直接显示 */
toolTip.show();
} else {
/* 事件触发才显示 */
}
return {
guid: guid,
config: config,
$shadowContainer: $shadowContainer,
$shadowRoot: $shadowRoot,
toolTip: toolTip,
};
}
}