main f6d8405

tooltip.svelte RawHistoryFolder

1<script lang="ts">
2	// eslint-disable-next-line @typescript-eslint/no-unused-vars -- $$Props is used for Svelte type inference
3	type $$Props = {
4		data: {
5			text: string;
6			direction?: 'top' | 'bottom' | 'left' | 'right';
7		};
8	};
9
10	let { children, data } = $props();
11
12	// Derive values from data with defaults
13	let text = $derived(data.text);
14	let direction = $derived(data.direction ?? 'top');
15
16	let wrapper: HTMLButtonElement;
17	let tooltipDiv = $state<HTMLDivElement | null>(null);
18	let coords = { x: 0, y: 0 };
19
20	function updateCoords() {
21		if (wrapper) {
22			const rect = wrapper.getBoundingClientRect();
23			if (direction === 'top') {
24				coords = { x: rect.left + rect.width / 2, y: rect.top - 8 };
25			} else if (direction === 'bottom') {
26				coords = { x: rect.left + rect.width / 2, y: rect.bottom + 8 };
27			} else if (direction === 'left') {
28				coords = { x: rect.left - 8, y: rect.top + rect.height / 2 };
29			} else if (direction === 'right') {
30				coords = { x: rect.right + 8, y: rect.top + rect.height / 2 };
31			}
32		}
33	}
34
35	function showTooltip() {
36		updateCoords();
37		if (tooltipDiv) return; // Prevent multiple tooltips
38		tooltipDiv = document.createElement('div');
39		tooltipDiv.className = `tooltip ${direction}`;
40		tooltipDiv.style.position = 'fixed';
41		tooltipDiv.style.zIndex = '1000000';
42		tooltipDiv.textContent = text;
43		tooltipDiv.setAttribute('role', 'tooltip');
44		tooltipDiv.setAttribute('aria-hidden', 'false');
45		document.body.appendChild(tooltipDiv);
46
47		const tooltipRect = tooltipDiv.getBoundingClientRect();
48		let finalLeft = coords.x;
49		let finalTop = coords.y;
50		if (direction === 'top') {
51			finalLeft = coords.x - tooltipRect.width / 2;
52			finalTop = coords.y - tooltipRect.height;
53		} else if (direction === 'bottom') {
54			finalLeft = coords.x - tooltipRect.width / 2;
55			finalTop = coords.y;
56		} else if (direction === 'left') {
57			finalLeft = coords.x - tooltipRect.width;
58			finalTop = coords.y - tooltipRect.height / 2;
59		} else if (direction === 'right') {
60			finalLeft = coords.x;
61			finalTop = coords.y - tooltipRect.height / 2;
62		}
63
64		if (finalLeft < 0) {
65			finalLeft = 0;
66		} else if (finalLeft + tooltipRect.width > window.innerWidth) {
67			finalLeft = window.innerWidth - tooltipRect.width;
68		}
69		if (finalTop < 0) {
70			finalTop = 0;
71		} else if (finalTop + tooltipRect.height > window.innerHeight) {
72			finalTop = window.innerHeight - tooltipRect.height;
73		}
74
75		tooltipDiv.style.left = `${finalLeft}px`;
76		tooltipDiv.style.top = `${finalTop}px`;
77	}
78
79	function hideTooltip() {
80		if (tooltipDiv && tooltipDiv.parentNode) {
81			tooltipDiv.parentNode.removeChild(tooltipDiv);
82			tooltipDiv = null;
83		}
84	}
85
86	function handleKeyDown(e: KeyboardEvent) {
87		if (e.key === 'Escape') {
88			hideTooltip();
89		}
90	}
91</script>
92
93<button
94	class="tooltip-wrapper"
95	bind:this={wrapper}
96	onmouseover={showTooltip}
97	onmouseout={hideTooltip}
98	onfocus={showTooltip}
99	onblur={hideTooltip}
100	onkeydown={handleKeyDown}
101	type="button"
102	aria-describedby={tooltipDiv?.id}
103>
104	{@render children()}
105</button>
106
107<style>
108	.tooltip-wrapper {
109		display: inline;
110		width: auto;
111		min-width: 0;
112		background: none !important;
113		border: none !important;
114		padding: 0 !important;
115		margin: 0 !important;
116		font: inherit !important;
117		color: inherit !important;
118		cursor: inherit !important;
119		text-align: inherit !important;
120		box-shadow: none !important;
121		text-shadow: inherit !important;
122		border-radius: 0 !important;
123		-webkit-appearance: none !important;
124		appearance: none !important;
125		white-space: pre;
126	}
127	.tooltip-wrapper:hover,
128	.tooltip-wrapper:active {
129		background: none !important;
130		border: none !important;
131		box-shadow: none !important;
132	}
133	.tooltip-wrapper:focus {
134		outline: none !important;
135		box-shadow: none !important;
136	}
137	.tooltip-wrapper:focus-visible {
138		outline: 2px solid #003c74 !important;
139		outline-offset: 2px !important;
140	}
141	/* Use :global for styles that need to apply to elements outside the component */
142	:global(.tooltip) {
143		white-space: nowrap;
144		padding: 0.5rem;
145		background: #ece9d8;
146		color: #000;
147		border: 1px solid #003c74;
148		box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
149		border-radius: 2px;
150		font-size: 0.875rem;
151		font-family: 'Microsoft Sans Serif', 'Segoe UI', Arial, Tahoma, sans-serif;
152	}
153
154	/* Direction-specific styles for the tooltip arrow */
155	:global(.tooltip.top)::before {
156		content: '';
157		position: absolute;
158		bottom: -6px;
159		left: 50%;
160		transform: translateX(-50%);
161		border-left: 6px solid transparent;
162		border-right: 6px solid transparent;
163		border-top: 6px solid #003c74;
164	}
165
166	:global(.tooltip.bottom)::before {
167		content: '';
168		position: absolute;
169		top: -6px;
170		left: 50%;
171		transform: translateX(-50%);
172		border-left: 6px solid transparent;
173		border-right: 6px solid transparent;
174		border-bottom: 6px solid #003c74;
175	}
176
177	:global(.tooltip.left)::before {
178		content: '';
179		position: absolute;
180		right: -6px;
181		top: 50%;
182		transform: translateY(-50%);
183		border-top: 6px solid transparent;
184		border-bottom: 6px solid transparent;
185		border-left: 6px solid #003c74;
186	}
187
188	:global(.tooltip.right)::before {
189		content: '';
190		position: absolute;
191		left: -6px;
192		top: 50%;
193		transform: translateY(-50%);
194		border-top: 6px solid transparent;
195		border-bottom: 6px solid transparent;
196		border-right: 6px solid #003c74;
197	}
198</style>

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 8

↑↓ moveenter openesc close