diff options
| author | Fuwn <[email protected]> | 2024-04-18 13:49:27 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-04-18 13:49:27 -0700 |
| commit | 6f567f29f92dab681547d489f163e3ebf63c537b (patch) | |
| tree | 2ef8609de1d1a60f1a8772485d4420d0b1179718 /src/lib/Tooltip | |
| parent | fix(list): tooltip wrap title (diff) | |
| download | due.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.svelte | 200 |
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> |