aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Tooltip
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-04-18 13:49:27 -0700
committerFuwn <[email protected]>2024-04-18 13:49:27 -0700
commit6f567f29f92dab681547d489f163e3ebf63c537b (patch)
tree2ef8609de1d1a60f1a8772485d4420d0b1179718 /src/lib/Tooltip
parentfix(list): tooltip wrap title (diff)
downloaddue.moe-6f567f29f92dab681547d489f163e3ebf63c537b.tar.xz
due.moe-6f567f29f92dab681547d489f163e3ebf63c537b.zip
refactor(tooltip): move sliding tooltip component
Diffstat (limited to 'src/lib/Tooltip')
-rw-r--r--src/lib/Tooltip/LinkedTooltip.svelte200
1 files changed, 200 insertions, 0 deletions
diff --git a/src/lib/Tooltip/LinkedTooltip.svelte b/src/lib/Tooltip/LinkedTooltip.svelte
new file mode 100644
index 00000000..7857a58b
--- /dev/null
+++ b/src/lib/Tooltip/LinkedTooltip.svelte
@@ -0,0 +1,200 @@
+<script lang="ts">
+ import tooltipPosition from '$stores/tooltipPosition';
+ import { fade } from 'svelte/transition';
+
+ export let id: string;
+ export let pin: string | undefined = undefined;
+ export let content: string;
+ export let disable: boolean = false;
+ export let pinPosition: 'top' | 'bottom' | 'left' | 'right' = 'top';
+ export let offset = 10;
+ export let tooltipTransitionTime = 200;
+ export let tooltipHideDelay = 10;
+ export let debounceDelay = 100;
+ export let tooltipOpacityTransitionTime = 200;
+
+ let tooltipDiv: HTMLDivElement | null = null;
+ let hideTimeout: number | null = null;
+ let debounceTimer: number | null = null;
+ let opacity = 0;
+
+ const createTooltip = () => {
+ if (!tooltipDiv) {
+ tooltipDiv = document.createElement('div');
+ tooltipDiv.style.position = 'absolute';
+ tooltipDiv.style.zIndex = '1000';
+ opacity = 0;
+ 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) {
+ if (pin) {
+ const pinnedElement = document.getElementById(pin);
+
+ if (pinnedElement) {
+ const rect = pinnedElement.getBoundingClientRect();
+ const tooltipWidth = tooltipDiv.offsetWidth;
+ const tooltipHeight = tooltipDiv.offsetHeight;
+ let top = rect.top - tooltipHeight - offset;
+ let left = rect.left + rect.width / 2 - tooltipWidth / 2;
+
+ switch (pinPosition) {
+ case 'top':
+ top = rect.top - tooltipHeight - offset;
+ left = rect.left + rect.width / 2 - tooltipWidth / 2;
+
+ break;
+ case 'bottom':
+ top = rect.top + rect.height + offset;
+ left = rect.left + rect.width / 2 - tooltipWidth / 2;
+
+ break;
+ case 'left':
+ top = rect.top + rect.height / 2 - tooltipHeight / 2;
+ left = rect.left - tooltipWidth - offset;
+
+ break;
+ case 'right':
+ top = rect.top + rect.height / 2 - tooltipHeight / 2;
+ left = rect.left + rect.width + offset;
+
+ break;
+ }
+
+ if (left < 0) left = offset;
+ if (left + tooltipWidth > window.innerWidth)
+ left = window.innerWidth - tooltipWidth - offset;
+ if (top < 0) top = rect.top + rect.height + offset;
+ if (top + tooltipHeight > window.innerHeight)
+ top = window.innerHeight - tooltipHeight - offset;
+
+ tooltipPosition.set({
+ x: left,
+ y: top + window.scrollY
+ });
+
+ return;
+ }
+ }
+
+ 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;
+
+ tooltipPosition.set({
+ x: left,
+ y: top
+ });
+ }
+ };
+
+ const showTooltip = (content: string, x: number, y: number) => {
+ if (hideTimeout !== null) {
+ clearTimeout(hideTimeout);
+
+ hideTimeout = null;
+ }
+
+ createTooltip();
+
+ if (tooltipDiv) {
+ tooltipDiv.innerHTML = content.replace(/\n/g, '<br>');
+
+ updateTooltipPosition(x, y);
+ setTimeout(() => {
+ if (tooltipDiv) {
+ opacity = 1;
+ }
+ }, 10);
+ }
+ };
+
+ const hideTooltip = () => {
+ setTimeout(() => {
+ if (tooltipDiv) {
+ opacity = 0;
+
+ hideTimeout = window.setTimeout(() => {
+ if (tooltipDiv) {
+ document.body.removeChild(tooltipDiv);
+
+ tooltipDiv = null;
+ }
+ }, tooltipTransitionTime);
+ }
+ }, tooltipHideDelay);
+ };
+
+ const handleMouseEnter = (event: MouseEvent) => {
+ if (disable) return;
+
+ if (hideTimeout !== null) {
+ clearTimeout(hideTimeout);
+
+ hideTimeout = null;
+ }
+
+ if (!tooltipDiv) showTooltip(content, event.pageX, event.pageY);
+ };
+
+ const handleMouseMove = (event: MouseEvent) => {
+ if (debounceTimer !== null) clearTimeout(debounceTimer);
+
+ debounceTimer = window.setTimeout(() => {
+ if (tooltipDiv && opacity === 1) updateTooltipPosition(event.pageX, event.pageY);
+ }, debounceDelay);
+ };
+
+ const handleMouseLeave = () => {
+ hideTooltip();
+ };
+</script>
+
+<span
+ {id}
+ data-tooltippin={pin}
+ on:mouseenter={handleMouseEnter}
+ on:mousemove={handleMouseMove}
+ on:mouseleave={handleMouseLeave}
+ role="tooltip"
+>
+ <slot />
+</span>
+
+{#if tooltipDiv}
+ <div
+ class="tooltip card card-small"
+ style={`left: ${$tooltipPosition.x}px; top: ${$tooltipPosition.y}px; opacity: ${opacity}; --tooltip-opacity-transition-time: ${tooltipOpacityTransitionTime}ms;`}
+ >
+ {#key content}
+ <span
+ in:fade={{ duration: tooltipTransitionTime }}
+ out:fade={{ duration: tooltipTransitionTime }}
+ >
+ {@html content.replace(/\n/g, '<br>')}
+ </span>
+ {/key}
+ </div>
+{/if}
+
+<style>
+ .tooltip {
+ position: absolute;
+ z-index: 1000;
+ transition: opacity var(--tooltip-opacity-transition-time) ease-in-out;
+ pointer-events: none;
+ white-space: nowrap;
+ }
+</style>