leafer-x-tooltip
Version:
leafer tooltip plugin
335 lines (331 loc) • 11.8 kB
JavaScript
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 };