aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Tooltip/tooltip.ts
blob: ca3c35705ddc49b838f7f1d4b9191a20502f2f67 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
const tooltip = (element: HTMLElement) => {
	let tooltipDiv: HTMLDivElement | null = null;
	const offset = 10;
	const tooltipTransitionTime = 200;
	const tooltipHideDelay = 10;
	const debounceDelay = 100;
	let hideTimeout: number | null = null;
	let debounceTimer: number | null = null;

	const createTooltip = () => {
		if (!tooltipDiv) {
			tooltipDiv = document.createElement('div');

			tooltipDiv.style.position = 'absolute';
			tooltipDiv.style.zIndex = '1000';
			tooltipDiv.style.opacity = '0';
			tooltipDiv.style.transition = `opacity ${tooltipTransitionTime}ms ease-in-out, top 0.3s ease, left 0.3s ease`;
			tooltipDiv.style.pointerEvents = 'none';
			tooltipDiv.style.whiteSpace = 'nowrap';

			tooltipDiv.classList.add('card');
			tooltipDiv.classList.add('card-small');
			document.body.appendChild(tooltipDiv);
		}
	};

	const updateTooltipPosition = (x: number, y: number) => {
		if (tooltipDiv) {
			const tooltipWidth = tooltipDiv.offsetWidth;
			const tooltipHeight = tooltipDiv.offsetHeight;
			let top = y - tooltipHeight - offset;
			let left = x - tooltipWidth / 2;

			if (left < 0) left = offset;
			if (left + tooltipWidth > window.innerWidth) left = window.innerWidth - tooltipWidth - offset;
			if (top < 0) top = y + offset;

			tooltipDiv.style.left = `${left}px`;
			tooltipDiv.style.top = `${top}px`;
		}
	};

	const showTooltip = (content: string, x: number, y: number) => {
		if (hideTimeout !== null) {
			clearTimeout(hideTimeout);

			hideTimeout = null;
		}

		createTooltip();

		if (tooltipDiv) {
			tooltipDiv.innerHTML = content;

			updateTooltipPosition(x, y);
			setTimeout(() => {
				if (tooltipDiv) tooltipDiv.style.opacity = '1';
			}, 10);
		}
	};

	const hideTooltip = () => {
		hideTimeout = window.setTimeout(() => {
			if (tooltipDiv) {
				tooltipDiv.style.opacity = '0';

				setTimeout(() => {
					if (tooltipDiv) {
						document.body.removeChild(tooltipDiv);
						tooltipDiv = null;
					}
				}, tooltipTransitionTime);
			}
		}, tooltipHideDelay);
	};

	const handleMouseEnter = (event: MouseEvent) => {
		const title = element.getAttribute('title');

		if (title) {
			element.removeAttribute('title');
			showTooltip(title, event.pageX, event.pageY);
		}
	};

	const handleMouseMove = (event: MouseEvent) => {
		if (debounceTimer !== null) clearTimeout(debounceTimer);

		debounceTimer = window.setTimeout(() => {
			if (tooltipDiv && tooltipDiv.style.opacity === '1')
				updateTooltipPosition(event.pageX, event.pageY);
		}, debounceDelay);
	};

	const handleMouseLeave = () => {
		element.setAttribute('title', tooltipDiv ? tooltipDiv.textContent || '' : '');
		hideTooltip();
	};

	element.addEventListener('mouseenter', handleMouseEnter);
	element.addEventListener('mousemove', handleMouseMove);
	element.addEventListener('mouseleave', handleMouseLeave);

	return {
		destroy() {
			element.removeEventListener('mouseenter', handleMouseEnter);
			element.removeEventListener('mousemove', handleMouseMove);
			element.removeEventListener('mouseleave', handleMouseLeave);

			if (hideTimeout !== null) clearTimeout(hideTimeout);
			if (debounceTimer !== null) clearTimeout(debounceTimer);

			if (tooltipDiv && document.body.contains(tooltipDiv)) {
				document.body.removeChild(tooltipDiv);

				tooltipDiv = null;
			}
		}
	};
};

export default tooltip;