UNPKG

leafer-x-tooltip

Version:
335 lines (331 loc) 11.8 kB
import { PointerEvent, LeaferEvent } from '@leafer-ui/core'; const PLUGIN_NAME = 'leafer-x-tooltip'; const ATTRS_NAME = 'data-lxt-id'; function assert(condition, msg) { if (condition) { throw new Error(`[${PLUGIN_NAME}]: ${msg}`); } } function addStyle(element, cssStyles) { requestAnimationFrame(() => { Object.entries(cssStyles).forEach(([property, value]) => { element.style[property] = value; }); }); } function randomStr(length = 8) { return Math.random().toString(36).slice(2, length + 2); } function allowNodeType(includeTypes, type) { if (!Array.isArray(includeTypes)) return true; if (includeTypes.length === 0) return true; return includeTypes.includes(type); } function denyNodeType(excludeTypes, type) { if (!Array.isArray(excludeTypes)) return false; if (excludeTypes.length === 0) return false; return excludeTypes.includes(type); } function getTooltip(dataId) { return document.querySelector(`[${ATTRS_NAME}=${dataId}]`); } function camelCaseToDash(str) { return str.replace(/([A-Z])/g, '-$1').toLowerCase(); } function createCssClass(selector, useRules, userStyleElement) { let styleElement = userStyleElement; if (!styleElement || !(userStyleElement instanceof HTMLStyleElement)) { styleElement = document.createElement('style'); styleElement.setAttribute(PLUGIN_NAME, ''); document.head.appendChild(styleElement); } let rules = typeof useRules === 'string' ? useRules : ''; if (typeof useRules === 'object') { Object.keys(useRules).forEach((prop) => { rules += `${camelCaseToDash(prop)}: ${useRules[prop]};`; }); } if (styleElement.sheet) { styleElement.sheet.insertRule(`${selector} { ${rules} }`, 0); } else { styleElement.appendChild(document.createTextNode(rules)); } return styleElement; } class TooltipPlugin { constructor(app, config) { this.app = app; this.config = config; if (!this.config.triggerType) { this.config.triggerType = 'hover'; } this.domId = `lxt--${randomStr(8)}`; this.bindEventIds = []; this.initEvent(); this.initCssClass(); this.initCreateTooltip(); this._moveTooltip = (event) => this.moveTooltip(event); this._hideTooltip = () => this.hideTooltip(); } initEvent() { const eventIds = []; let event = PointerEvent.MOVE; let eventFunc = this.leaferPointMove; if (this.config.triggerType === 'click') { event = PointerEvent.CLICK; eventFunc = this.leaferPointClick; } const eventId = this.app.on_(event, eventFunc, this); eventIds.push(eventId); const viewReadyId = this.app.on_(LeaferEvent.VIEW_READY, this.viewReadyEvent, this); eventIds.push(viewReadyId); this.bindEventIds.push(...eventIds); } shouldShowTooltip(node, event) { if (!node || node.isLeafer || node.isApp) { this.hideTooltip(); return false; } const isAllowType = allowNodeType(this.config.includeTypes, node.tag); const isDenyType = denyNodeType(this.config.excludeTypes, node.tag); const isShouldBegin = this.config.shouldBegin ? this.config.shouldBegin(event) : true; if (!isAllowType || isDenyType || !isShouldBegin) { this.hideTooltip(); return false; } return true; } leaferPointMove(event) { const node = event.target; if (this.activeNode === node) return; if (!this.shouldShowTooltip(node, event)) return; this.activeNode = node; } leaferPointClick(event) { const node = event.target; if (!this.shouldShowTooltip(node, event)) return; if (this.activeNode === node) { this.hideTooltip(); return; } this.activeNode = node; if (this.app.view instanceof HTMLElement) { this.moveTooltip(event); } } viewReadyEvent() { var _a; if (!(this.app.view instanceof HTMLElement)) return; assert(((_a = this.app.view) === null || _a === void 0 ? void 0 : _a.addEventListener) === undefined, 'leafer.view 加载失败!'); if (this.config.triggerType === 'hover') { this.app.view.addEventListener('mousemove', this._moveTooltip); this.app.view.addEventListener('mouseleave', this._hideTooltip); } } initCssClass() { if (this.styleSheetElement) return; const styleSheetElement = document.querySelector(`.${PLUGIN_NAME}`); if (styleSheetElement) { this.styleSheetElement = styleSheetElement; return; } this.styleSheetElement = createCssClass(`.${PLUGIN_NAME}`, { border: 'none', borderRadius: '6px', padding: '10px 14px', backgroundColor: 'rgba(255, 255, 255, 0.95)', color: '#333', fontSize: '13px', fontWeight: '400', boxShadow: '0 3px 14px rgba(0, 0, 0, 0.15)', backdropFilter: 'blur(8px)', transition: 'opacity,top, left 0.2s ease-in-out', }); } initCreateTooltip() { let container = getTooltip(this.domId); const isExists = container !== null; if (!isExists) { container = document.createElement('div'); } container.setAttribute(ATTRS_NAME, this.domId); container.style.display = 'none'; if (this.config.className) { container.className = this.config.className; } else if (!isExists) { container.className = PLUGIN_NAME; } if (!isExists) { document.body.appendChild(container); } return container; } hideTooltip() { this.activeNode = null; const tooltipDOM = getTooltip(this.domId); if (tooltipDOM) { tooltipDOM.style.display = 'none'; } } calculateTooltipPosition(event, tooltipElem) { const windowWidth = window.innerWidth; const windowHeight = window.innerHeight; const pageXOffset = window.scrollX; const pageYOffset = window.scrollY; let mouseX = 0, mouseY = 0; if (event instanceof PointerEvent) { mouseX = event.origin.x + pageXOffset; mouseY = event.origin.y + pageYOffset; } else if (event instanceof MouseEvent) { mouseX = event.clientX + pageXOffset; mouseY = event.clientY + pageYOffset; } else { mouseX = event.x + pageXOffset; mouseY = event.y + pageYOffset; } const tooltipWidth = tooltipElem.offsetWidth; const tooltipHeight = tooltipElem.offsetHeight; const offset = this.getOffset(); let x = mouseX + offset.x; let y = mouseY + offset.y; if (x + tooltipWidth > windowWidth + pageXOffset) { x = mouseX - tooltipWidth - offset.x; } if (y + tooltipHeight > windowHeight + pageYOffset) { y = mouseY - tooltipHeight - offset.y; } return { x, y }; } getTooltipContent() { const argumentType = typeof this.config.getContent; assert(argumentType !== 'function', `getContent 为必传参数,且必须是一个函数,当前为:${argumentType} 类型`); const content = this.config.getContent(this.activeNode); assert(content === undefined || content === null || content === '', `getContent 返回值不能为空`); return content; } moveTooltip(event) { if (this.activeNode === null) return; let tooltipContainer = getTooltip(this.domId); if (!tooltipContainer) { tooltipContainer = this.initCreateTooltip(); } tooltipContainer.innerHTML = this.getTooltipContent(); const { x, y } = this.calculateTooltipPosition(event, tooltipContainer); addStyle(tooltipContainer, { display: 'block', position: 'absolute', left: `${x}px`, top: `${y}px`, }); } getDomId() { return this.domId; } getOffset() { const offset = this.config.offset; if (typeof offset === 'number') return { x: offset, y: offset }; if (Array.isArray(offset)) { const [x, y] = offset; return { x, y }; } if (typeof offset === 'object') return offset; return { x: 6, y: 6 }; } createStyleRule(selector, useRules) { createCssClass(`${selector}[${ATTRS_NAME}=${this.domId}]`, useRules, this.styleSheetElement); } removeStyleRule(selector) { const styleSheet = this.styleSheetElement.sheet; if (!styleSheet) return; const index = this.findStyleRuleIndex(selector); if (index === -1) return; styleSheet.deleteRule(index); } findStyleRuleIndex(selector) { const styleSheet = this.styleSheetElement.sheet; if (!styleSheet) return -1; const rules = styleSheet.cssRules; const fullSelector = `${selector}[${ATTRS_NAME}=${this.domId}]`; for (let i = 0; i < rules.length; i++) { const rule = rules[i]; if (rule.selectorText === fullSelector) return i; } return -1; } addClass(className) { const container = getTooltip(this.domId); if (container) { if (Array.isArray(className)) { className.forEach((item) => container.classList.add(item)); } else { container.classList.add(className); } } } removeClass(className) { const container = getTooltip(this.domId); if (container) { if (Array.isArray(className)) { className.forEach((item) => container.classList.remove(item)); } else { container.classList.remove(className); } } } destroy() { this.app.off_(this.bindEventIds); this.bindEventIds.length = 0; if (this.app.view instanceof HTMLElement) { this.app.view.removeEventListener('mousemove', this._moveTooltip); this.app.view.removeEventListener('mouseleave', this._hideTooltip); } this.activeNode = null; const tooltipDOM = getTooltip(this.domId); if (tooltipDOM && tooltipDOM.parentNode) { tooltipDOM.parentNode.removeChild(tooltipDOM); } } setTriggerType(triggerType) { if (this.config.triggerType === triggerType) return; const prevTriggerType = this.config.triggerType; this.app.off_(this.bindEventIds); this.bindEventIds.length = 0; this.config.triggerType = triggerType; this.initEvent(); if (this.app.view instanceof HTMLElement) { if (triggerType === 'click' && prevTriggerType === 'hover') { this.app.view.removeEventListener('mousemove', this._moveTooltip); this.app.view.removeEventListener('mouseleave', this._hideTooltip); } else if (triggerType === 'hover' && prevTriggerType === 'click') { this.app.view.addEventListener('mousemove', this._moveTooltip); this.app.view.addEventListener('mouseleave', this._hideTooltip); } } this.hideTooltip(); } } export { TooltipPlugin };