tooltip.svelte RawHistoryFolder
1<script lang="ts">
2 type $$Props = {
3 data: {
4 text: string,
5 direction?: 'top' | 'bottom' | 'left' | 'right'
6 }
7 };
8
9 let { children, data } = $props();
10
11 // Derive values from data with defaults
12 let text = $derived(data.text);
13 let direction = $derived(data.direction ?? 'top');
14
15 let wrapper: HTMLDivElement;
16 let tooltipDiv: HTMLDivElement | null = null;
17 let coords = { x: 0, y: 0 };
18
19 function updateCoords() {
20 if (wrapper) {
21 const rect = wrapper.getBoundingClientRect();
22 if (direction === 'top') {
23 coords = { x: rect.left + rect.width / 2, y: rect.top - 8 };
24 } else if (direction === 'bottom') {
25 coords = { x: rect.left + rect.width / 2, y: rect.bottom + 8 };
26 } else if (direction === 'left') {
27 coords = { x: rect.left - 8, y: rect.top + rect.height / 2 };
28 } else if (direction === 'right') {
29 coords = { x: rect.right + 8, y: rect.top + rect.height / 2 };
30 }
31 }
32 }
33
34 function showTooltip() {
35 updateCoords();
36 tooltipDiv = document.createElement('div');
37 tooltipDiv.className = `tooltip ${direction}`;
38 tooltipDiv.style.position = 'fixed';
39 tooltipDiv.style.zIndex = '1000000';
40 tooltipDiv.textContent = text;
41 document.body.appendChild(tooltipDiv);
42
43 const tooltipRect = tooltipDiv.getBoundingClientRect();
44 let finalLeft = coords.x;
45 let finalTop = coords.y;
46 if (direction === 'top') {
47 finalLeft = coords.x - tooltipRect.width / 2;
48 finalTop = coords.y - tooltipRect.height;
49 } else if (direction === 'bottom') {
50 finalLeft = coords.x - tooltipRect.width / 2;
51 finalTop = coords.y;
52 } else if (direction === 'left') {
53 finalLeft = coords.x - tooltipRect.width;
54 finalTop = coords.y - tooltipRect.height / 2;
55 } else if (direction === 'right') {
56 finalLeft = coords.x;
57 finalTop = coords.y - tooltipRect.height / 2;
58 }
59
60 if (finalLeft < 0) {
61 finalLeft = 0;
62 } else if (finalLeft + tooltipRect.width > window.innerWidth) {
63 finalLeft = window.innerWidth - tooltipRect.width;
64 }
65 if (finalTop < 0) {
66 finalTop = 0;
67 } else if (finalTop + tooltipRect.height > window.innerHeight) {
68 finalTop = window.innerHeight - tooltipRect.height;
69 }
70
71 tooltipDiv.style.left = `${finalLeft}px`;
72 tooltipDiv.style.top = `${finalTop}px`;
73 }
74
75 function hideTooltip() {
76 if (tooltipDiv && tooltipDiv.parentNode) {
77 tooltipDiv.parentNode.removeChild(tooltipDiv);
78 tooltipDiv = null;
79 }
80 }
81</script>
82
83<div
84 class="tooltip-wrapper"
85 bind:this={wrapper}
86 onmouseover={showTooltip}
87 onmouseout={hideTooltip}
88 onfocus={showTooltip}
89 onblur={hideTooltip}
90 role="tooltip"
91 tabindex="-1"
92>
93 {@render children()}
94</div>
95
96<style>
97 .tooltip-wrapper {
98 display: inline-block;
99 }
100 /* Use :global for styles that need to apply to elements outside the component */
101 :global(.tooltip) {
102 white-space: nowrap;
103 padding: 0.5rem;
104 background: #ECE9D8;
105 color: #000;
106 border: 1px solid #003C74;
107 box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
108 border-radius: 2px;
109 font-size: 0.875rem;
110 font-family: 'Microsoft Sans Serif', 'Segoe UI', Arial, Tahoma, sans-serif;
111 }
112
113 /* Direction-specific styles for the tooltip arrow */
114 :global(.tooltip.top)::before {
115 content: '';
116 position: absolute;
117 bottom: -6px;
118 left: 50%;
119 transform: translateX(-50%);
120 border-left: 6px solid transparent;
121 border-right: 6px solid transparent;
122 border-top: 6px solid #003C74;
123 }
124
125 :global(.tooltip.bottom)::before {
126 content: '';
127 position: absolute;
128 top: -6px;
129 left: 50%;
130 transform: translateX(-50%);
131 border-left: 6px solid transparent;
132 border-right: 6px solid transparent;
133 border-bottom: 6px solid #003C74;
134 }
135
136 :global(.tooltip.left)::before {
137 content: '';
138 position: absolute;
139 right: -6px;
140 top: 50%;
141 transform: translateY(-50%);
142 border-top: 6px solid transparent;
143 border-bottom: 6px solid transparent;
144 border-left: 6px solid #003C74;
145 }
146
147 :global(.tooltip.right)::before {
148 content: '';
149 position: absolute;
150 left: -6px;
151 top: 50%;
152 transform: translateY(-50%);
153 border-top: 6px solid transparent;
154 border-bottom: 6px solid transparent;
155 border-right: 6px solid #003C74;
156 }
157</style>