import { EventEmitter } from 'events'; import { TipConfig } from '../../types'; export interface TipOptions extends TipConfig { target: HTMLElement; } function findTip(target: HTMLElement | null): TipOptions | null { if (!target) { return null; } // optimize deep finding on mouseover let loopupLimit = 10; while (target && loopupLimit-- > 0) { // get tip from target node if (target.dataset && target.dataset.tip) { return { children: target.dataset.tip, direction: target.dataset.direction || target.dataset.dir, theme: target.dataset.theme, target, }; } // or get tip from child nodes let child: HTMLElement | null = target.lastElementChild as HTMLElement; while (child) { if (child.dataset && child.dataset.role === 'tip') { const tipId = child.dataset.tipId; if (!tipId) { return null; } const tipProps = tipsMap.get(tipId); if (!tipProps) { return null; } return { ...tipProps, target, }; } child = child.previousElementSibling as HTMLElement; } target = target.parentNode as HTMLElement; } return null; } class TipHandler { tip: TipOptions | null = null; private showDelay: number | null = null; private hideDelay: number | null = null; private emitter = new EventEmitter(); setTarget(target: HTMLElement) { const tip = findTip(target); if (tip) { if (this.tip) { // the some target should return if (this.tip.target === tip.target) { this.tip = tip; return; } // not show already, reset show delay if (this.showDelay) { clearTimeout(this.showDelay); this.showDelay = null; this.tip = null; } else { if (this.hideDelay) { clearTimeout(this.hideDelay); this.hideDelay = null; } this.tip = tip; this.emitter.emit('tipchange'); return; } } this.tip = tip; if (this.hideDelay) { clearTimeout(this.hideDelay); this.hideDelay = null; this.emitter.emit('tipchange'); } else { this.showDelay = setTimeout(() => { this.showDelay = null; this.emitter.emit('tipchange'); }, 350) as any; } } else { if (this.showDelay) { clearTimeout(this.showDelay); this.showDelay = null; } else { this.hideDelay = setTimeout(() => { this.hideDelay = null; }, 100) as any; } this.tip = null; this.emitter.emit('tipchange'); } } hideImmediately() { if (this.hideDelay) { clearTimeout(this.hideDelay); this.hideDelay = null; } if (this.showDelay) { clearTimeout(this.showDelay); this.showDelay = null; } this.tip = null; this.emitter.emit('tipchange'); } onChange(func: () => void) { this.emitter.on('tipchange', func); return () => { this.emitter.removeListener('tipchange', func); }; } } const tipsMap = new Map(); export function saveTips(id: string, props: TipConfig | null) { if (props) { tipsMap.set(id, props); } else { tipsMap.delete(id); } } export default new TipHandler();