UNPKG

qmsg

Version:

一款优雅的页面消息提示插件,兼容性良好,无任何依赖。

469 lines (458 loc) 15.6 kB
import { QmsgAnimation, type QmsgAnimationState } from "./QmsgAnimation"; import { QmsgCSS } from "./QmsgCSS"; import { QmsgDefaultConfig } from "./QmsgDefaultConfig"; import { QmsgIcon, QmsgHeaderCloseIcon } from "./QmsgIcon"; import { QmsgInstStorage } from "./QmsgInstStorage"; import type { QmsgConfig } from "./QmsgConfig"; import { QmsgUtils } from "./QmsgUtils"; /** * 每条消息的构造函数 */ export class QmsgMsg { /** * setTimeout的id */ timeId: number | undefined = void 0; /** * 启动时间 */ startTime: number | null; /** * 关闭时间 */ endTime: number | null; /** * Qmsg的配置 */ setting: Required<QmsgConfig>; /** * uuid */ uuid: string; /** * 当前动画状态 */ state: keyof QmsgAnimationState; /** * 当前相同消息的数量 */ repeatNum: number; /** * 主元素 */ $Qmsg: HTMLElement; constructor(config: QmsgConfig, uuid: string) { this.timeId = void 0; this.startTime = Date.now(); this.endTime = null; // this.#setting = Object.assign({}, QmsgStore.DEFAULT, this.option); this.setting = QmsgUtils.toDynamicObject(QmsgDefaultConfig.config, config, QmsgDefaultConfig.INS_DEFAULT); this.uuid = uuid; this.state = "opening"; this.$Qmsg = document.createElement("div"); this.repeatNum = 1; this.detectionType(); this.init(); const consoleLogContent = typeof this.setting.consoleLogContent === "function" ? this.setting.consoleLogContent(this) : this.setting.consoleLogContent; if (consoleLogContent) { // 控制台输出content console.log(this.setting.content); } if (typeof this.setting.afterRender === "function") { this.setting.afterRender(this); } } /** * 获取当前配置 */ getSetting() { return this.setting; } /** * 获取当前相同的数量 */ getRepeatNum() { return this.repeatNum; } /** * 设置repeatNum值 * @param num 重复的数量 */ setRepeatNum(num: number) { this.repeatNum = num; } /** * 设置repeatNum自增 */ setRepeatNumIncreasing() { this.repeatNum++; } /** * 初始化元素 */ private init() { const QmsgContext = this; if (this.setting.customClass && typeof this.setting.customClass === "string") { /* 设置自定义类名 */ this.$Qmsg.classList.add(this.setting.customClass); } // 设置svg图标 const $svg = QmsgIcon[this.setting.type || "info"]; let contentClassName = QmsgUtils.getNameSpacify("content-" + this.setting.type || "info"); if (this.setting.showClose) { // 显示 关闭图标 contentClassName += " " + QmsgUtils.getNameSpacify("content-with-close"); } // 内容兼容处理 const content = this.setting.content || ""; // 关闭图标 自定义额外className let extraCloseIconClassName = ""; // 关闭图标svg const $closeSvg = QmsgHeaderCloseIcon; if (this.setting.showMoreContent) { // 显示更多内容 contentClassName += "qmsg-show-more-content"; extraCloseIconClassName += "qmsg-show-more-content"; } let $closeIcon = ""; if (this.setting.showClose) { /* 显示右上角的关闭图标按钮 */ $closeIcon = /*html*/ `<i class="qmsg-icon qmsg-icon-close ${extraCloseIconClassName}">${$closeSvg}</i>`; } /* 内容 */ const $content = document.createElement("span"); const $positionClassName = QmsgUtils.getNameSpacify("data-position", this.setting.position.toLowerCase()); const isHTML = this.setting.isHTML; if (isHTML) { /* 内容是html */ QmsgUtils.setSafeHTML($content, content); } else { /* 内容是纯文本 */ $content.innerText = content; } if (this.setting.isLimitWidth) { /* 限制宽度 */ let limitWidthNum = this.setting.limitWidthNum; if (typeof limitWidthNum === "string") { if (QmsgUtils.isNumber(limitWidthNum)) { limitWidthNum = limitWidthNum + "px"; } } else { limitWidthNum = limitWidthNum.toString() + "px"; } $content.style.maxWidth = limitWidthNum; $content.style.width = limitWidthNum; /* 设置换行 */ if (this.setting.limitWidthWrap === "no-wrap") { /* 禁止换行 */ $content.style.whiteSpace = "nowrap"; } else if (this.setting.limitWidthWrap === "ellipsis") { /* 禁止换行且显示省略号 */ $content.style.whiteSpace = "nowrap"; $content.style.overflow = "hidden"; $content.style.textOverflow = "ellipsis"; } else if (this.setting.limitWidthWrap === "wrap") { /* 允许换行 */ /* 默认的 */ $content.style.whiteSpace = ""; } } QmsgUtils.setSafeHTML( this.$Qmsg, /*html*/ ` <div class="qmsg-content"> <div class="${contentClassName}"> ${this.setting.showIcon ? `<i class="qmsg-icon">${$svg}</i>` : ""} ${$content.outerHTML} ${$closeIcon} </div> </div> ` ); /** 内容容器 */ const $contentContainer = this.$Qmsg.querySelector<HTMLElement>(".qmsg-content")!; this.$Qmsg.classList.add(QmsgUtils.getNameSpacify("item")); this.$Qmsg.setAttribute(QmsgUtils.getNameSpacify("uuid"), this.uuid); /** 总根元素 */ let $shadowContainer: HTMLElement | null; /** 根元素 */ let $shadowRoot: ShadowRoot | null | undefined | HTMLElement; /** 容器包裹的元素 */ let $wrapper: HTMLElement | null; $shadowContainer = document.querySelector<HTMLElement>(".qmsg-shadow-container"); $shadowRoot = this.setting.useShadowRoot ? $shadowContainer?.shadowRoot : $shadowContainer; if (!$shadowContainer) { // 页面中不存在ShadowRoot容器元素 // 添加新增的ShadowRoot容器元素 $shadowContainer = document.createElement("div"); $shadowContainer.className = "qmsg-shadow-container"; if (this.setting.useShadowRoot) { $shadowRoot = $shadowContainer.attachShadow({ mode: this.setting.shadowRootMode, }); } else { $shadowRoot = $shadowContainer; } $shadowRoot.appendChild(QmsgCSS.getStyleElement()); if (this.setting.style != null) { // 插入自定义的style // 这里需要插入到每一条的Qmsg内,以便移除实例时把style也移除 const __$ownStyle__ = document.createElement("style"); __$ownStyle__.setAttribute("type", "text/css"); __$ownStyle__.setAttribute("data-id", this.uuid); QmsgUtils.setSafeHTML(__$ownStyle__, this.setting.style); $contentContainer.insertAdjacentElement("afterend", __$ownStyle__); } this.setting.parent.appendChild($shadowContainer); } if ($shadowRoot == null) { throw new Error("QmsgInst " + QmsgDefaultConfig.PLUGIN_NAME + " $shadowRoot is null"); } $wrapper = $shadowRoot.querySelector<HTMLElement>(`.${QmsgDefaultConfig.NAMESPACE}.${$positionClassName}`); if (!$wrapper) { $wrapper = document.createElement("div"); $wrapper.classList.add( QmsgDefaultConfig.NAMESPACE, QmsgUtils.getNameSpacify("wrapper"), QmsgUtils.getNameSpacify("is-initialized"), $positionClassName ); $shadowRoot.appendChild($wrapper); } if (this.setting.showReverse) { $wrapper.style.flexDirection = "column-reverse"; } else { $wrapper.style.flexDirection = "column"; } let zIndex = this.setting.zIndex; if (typeof zIndex === "function") { zIndex = zIndex(); } if (!isNaN(zIndex)) { $wrapper.style.zIndex = zIndex.toString(); } $wrapper.appendChild(this.$Qmsg); this.setState(this.$Qmsg, "opening"); if (this.setting.showClose) { /* 关闭按钮绑定点击事件 */ const $closeIcon = this.$Qmsg.querySelector<HTMLElement>(".qmsg-icon-close"); if ($closeIcon) { // eslint-disable-next-line @typescript-eslint/no-unused-vars $closeIcon.addEventListener("click", (_event) => { QmsgContext.close(); }); } } /* 监听动画完成 */ // eslint-disable-next-line @typescript-eslint/no-unused-vars const animationendEvent = (_event: AnimationEvent) => { const animationNameValue = QmsgAnimation.getStyleAnimationNameValue(QmsgContext.$Qmsg); if (animationNameValue === QmsgAnimation.$state.closing) { // 当前触发的是关闭 QmsgContext.endTime = Date.now(); QmsgContext.destroy(); } QmsgAnimation.setStyleAnimationName(QmsgContext.$Qmsg); }; QmsgAnimation.$name.endNameList.forEach(function (animationendName) { QmsgContext.$Qmsg.addEventListener<"animationend">(animationendName as any, animationendEvent); }); /* 自动关闭 */ if (this.setting.autoClose && this.setting.listenEventToPauseAutoClose) { // 鼠标|触摸滑入时,清除自动关闭的定时器 // 鼠标|触摸滑出时,重新设置定时器 this.resetAutoCloseTimer(); /** * 鼠标滑入 * * + 清除定时器 * + 清除开始时间 * + 清除结束时间 */ // eslint-disable-next-line @typescript-eslint/no-unused-vars const enterEvent = (_event: MouseEvent | TouchEvent) => { this.clearAutoCloseTimer(); }; /** 鼠标滑出,重启定时器,创建新的开始时间和timeId */ // eslint-disable-next-line @typescript-eslint/no-unused-vars const leaveEvent = (_event: MouseEvent | TouchEvent) => { if (this.timeId != null) { // 似乎enterEvent函数未正确调用? console.warn("QmsgInst timeId is not null,mouseenter may be not first trigger,timeId:" + this.timeId); return; } this.startAutoCloseTimer(); }; let isRemoveMouseEvent = false; this.$Qmsg.addEventListener("mouseenter", enterEvent); this.$Qmsg.addEventListener("mouseleave", leaveEvent); this.$Qmsg.addEventListener("touchstart", (evt) => { // 由于移动端不支持mouseout且会触发mouseenter // 那么需要移除该监听 if (!isRemoveMouseEvent) { isRemoveMouseEvent = true; this.$Qmsg.removeEventListener("mouseenter", enterEvent); this.$Qmsg.removeEventListener("mouseleave", leaveEvent); } enterEvent(evt); }); this.$Qmsg.addEventListener("touchend", leaveEvent); this.$Qmsg.addEventListener("touchcancel", leaveEvent); } } /** * 对timeout进行检测并转换 * 当timeout为string时,转换为number * timeout必须在规定范围内 */ private detectionType() { if (this.setting.timeout != null && typeof this.setting.timeout === "string") { this.setting.timeout = parseInt(this.setting.timeout); } if (isNaN(this.setting.timeout)) { this.setting.timeout = QmsgDefaultConfig.config.timeout; } if ( !( this.setting.timeout != null && parseInt(this.setting.timeout.toString()) >= 0 && parseInt(this.setting.timeout.toString()) <= Number.MAX_VALUE ) ) { this.setting.timeout = QmsgDefaultConfig.config.timeout; } if (typeof this.setting.zIndex === "function") { this.setting.zIndex = this.setting.zIndex(); } if (this.setting.zIndex != null && typeof this.setting.zIndex === "string") { this.setting.zIndex = parseInt(this.setting.zIndex); } if (isNaN(this.setting.zIndex)) { this.setting.zIndex = typeof QmsgDefaultConfig.config.zIndex === "function" ? QmsgDefaultConfig.config.zIndex() : QmsgDefaultConfig.config.zIndex; } } /** * 设置元素动画状态 开启/关闭 * @param QmsgMsg * @param state */ private setState(element: HTMLElement, state: keyof QmsgAnimationState) { if (!state || !QmsgAnimation.$state[state]) return; this.state = state; QmsgAnimation.setStyleAnimationName(element, QmsgAnimation.$state[state]); } /** * 设置消息数量统计 */ setMsgCount() { const countClassName = QmsgUtils.getNameSpacify("count"); const wrapperClassName = `div.${QmsgUtils.getNameSpacify( "data-position", this.setting.position.toLowerCase() )} [class^="qmsg-content-"]`; const $content = this.$Qmsg.querySelector<HTMLElement>(wrapperClassName); if (!$content) { throw new Error("QmsgInst $content is null"); } let $count = $content.querySelector<HTMLElement>("." + countClassName); if (!$count) { $count = document.createElement("span"); $count.classList.add(countClassName); $content.appendChild($count); } // 获取重复显示内容的实例数量 const repeatNum = this.getRepeatNum(); QmsgUtils.setSafeHTML($count, repeatNum.toString()); QmsgAnimation.setStyleAnimationName($count); QmsgAnimation.setStyleAnimationName($count, "MessageShake"); this.resetAutoCloseTimer(); } /** * 清除旧的自动关闭定时器 */ clearAutoCloseTimer() { /* 重置定时器 */ QmsgUtils.clearTimeout(this.timeId); this.timeId = void 0; this.startTime = null; this.endTime = null; } /** * 开始自动关闭定时器 */ startAutoCloseTimer() { if (this.setting.autoClose && this.setting.listenEventToPauseAutoClose) { this.startTime = Date.now(); this.endTime = null; this.timeId = QmsgUtils.setTimeout(() => { this.close(); }, this.setting.timeout); } } /** * 重置自动关闭定时器(会自动清理旧的定时器) */ resetAutoCloseTimer() { this.clearAutoCloseTimer(); this.startAutoCloseTimer(); } /** * 关闭Qmsg(会触发动画) */ close() { this.setState(this.$Qmsg, "closing"); if (QmsgAnimation.CAN_ANIMATION) { /* 支持动画 */ QmsgInstStorage.remove(this.uuid); } else { /* 不支持动画 */ this.destroy(); } const onCloseCallBack = this.setting.onClose; if (onCloseCallBack && typeof onCloseCallBack === "function") { onCloseCallBack.call(this); } } /** * 销毁Qmsg */ destroy() { this.endTime = Date.now(); this.$Qmsg.remove(); QmsgUtils.clearTimeout(this.timeId); QmsgInstStorage.remove(this.uuid); this.timeId = void 0; } /** * 获取内容元素 */ get $content() { const $content = this.$Qmsg.querySelector<HTMLSpanElement>("div[class^=qmsg-content-] > span"); if (!$content) { throw new Error("QmsgInst $content is null"); } return $content; } /** * 设置内容文本 */ setText(text: string) { const $content = this.$content; $content.innerText = text; this.setting.content = text; } /** * 设置内容超文本 */ setHTML(text: string) { const $content = this.$content; QmsgUtils.setSafeHTML($content, text); this.setting.content = text; } }