2024-04-01 13:31:16 +02:00
|
|
|
<script>
|
|
|
|
import Portal from "svelte-portal"
|
2024-04-01 18:14:18 +02:00
|
|
|
import { fade } from 'svelte/transition';
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-02 11:54:20 +02:00
|
|
|
export let currentTooltip
|
2024-04-01 13:31:16 +02:00
|
|
|
export let anchor
|
|
|
|
export let visible = false
|
|
|
|
export let hovering = false
|
|
|
|
|
2024-04-02 13:07:11 +02:00
|
|
|
let previousX = 0
|
|
|
|
let previousY = 0
|
|
|
|
let currentX = 0
|
|
|
|
let currentY = 0
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-02 11:54:20 +02:00
|
|
|
let currentTooltipWidth = 0
|
|
|
|
let currentTooltipHeight = 0
|
|
|
|
|
2024-04-01 13:31:16 +02:00
|
|
|
const updatePositionOnVisibilityChange = (visible, hovering) => {
|
|
|
|
if (!visible && !hovering) {
|
2024-04-02 13:07:11 +02:00
|
|
|
previousX = 0;
|
|
|
|
previousY = 0;
|
2024-04-02 11:54:20 +02:00
|
|
|
} }
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-02 13:10:26 +02:00
|
|
|
const updatePosition = (anchor, currentTooltip) => {
|
2024-04-02 11:54:20 +02:00
|
|
|
requestAnimationFrame(() => {
|
2024-04-02 13:10:26 +02:00
|
|
|
if (anchor == null || currentTooltip == null) {
|
2024-04-02 11:54:20 +02:00
|
|
|
return;
|
|
|
|
}
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-02 11:54:20 +02:00
|
|
|
const rect = anchor.getBoundingClientRect();
|
2024-04-01 18:14:18 +02:00
|
|
|
|
2024-04-02 11:54:20 +02:00
|
|
|
currentTooltipWidth = currentTooltip.clientWidth
|
|
|
|
currentTooltipHeight = currentTooltip.clientHeight
|
2024-04-01 18:14:18 +02:00
|
|
|
|
2024-04-02 13:07:11 +02:00
|
|
|
previousX = currentX
|
|
|
|
previousY = currentY
|
2024-04-02 11:54:20 +02:00
|
|
|
|
2024-04-02 13:07:11 +02:00
|
|
|
// - width to align to left side of anchor
|
|
|
|
currentX = rect.x - currentTooltipWidth
|
|
|
|
currentY = rect.y
|
2024-04-02 11:54:20 +02:00
|
|
|
|
2024-04-02 13:07:11 +02:00
|
|
|
if (previousX === 0) {
|
|
|
|
previousX = currentX
|
|
|
|
}
|
2024-04-02 11:54:20 +02:00
|
|
|
|
2024-04-02 13:07:11 +02:00
|
|
|
if (previousY === 0) {
|
|
|
|
previousY = currentY
|
2024-04-02 11:54:20 +02:00
|
|
|
}
|
|
|
|
})
|
2024-04-01 13:31:16 +02:00
|
|
|
}
|
|
|
|
|
2024-04-02 13:07:11 +02:00
|
|
|
/*
|
2024-04-01 18:14:18 +02:00
|
|
|
const getNormalizedTime = (startTime, endTime, currentTime) => {
|
|
|
|
const distanceFromStart = currentTime - startTime;
|
|
|
|
const timeDiff = endTime - startTime;
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-01 18:14:18 +02:00
|
|
|
return distanceFromStart / timeDiff;
|
|
|
|
}
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-02 11:54:20 +02:00
|
|
|
const cubicBezierInterpolation = (p1, p2, p3, p4, currentTime) => {
|
|
|
|
return (
|
|
|
|
(Math.pow(1 - currentTime, 3) * p1) +
|
|
|
|
(3 * Math.pow(1 - currentTime, 2) * currentTime * p2) +
|
|
|
|
(3 * (1 - currentTime) * Math.pow(currentTime, 2) * p3) +
|
|
|
|
(Math.pow(currentTime, 3) * p4)
|
|
|
|
)
|
2024-04-01 18:14:18 +02:00
|
|
|
}
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-01 18:14:18 +02:00
|
|
|
// Made to match the interface of the css bezier curve function
|
|
|
|
const cubicBezierEasing = (a, b, c, d, t) => {
|
|
|
|
// CSS bezier curve function implicitly provides p1 and p4
|
|
|
|
const p1 = { x: 0, y: 0 }
|
|
|
|
const p2 = { x: a, y: b }
|
|
|
|
const p3 = { x: c, y: d }
|
|
|
|
const p4 = { x: 1, y: 1 }
|
|
|
|
|
|
|
|
return {
|
|
|
|
x: cubicBezierInterpolation(p1.x, p2.x, p3.x, p4.x, t),
|
|
|
|
y: cubicBezierInterpolation(p1.y, p2.y, p3.y, p4.y, t)
|
2024-04-01 13:31:16 +02:00
|
|
|
}
|
2024-04-01 18:14:18 +02:00
|
|
|
}
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-01 18:14:18 +02:00
|
|
|
const linearInterpolation = (p1, p2, t) => {
|
|
|
|
return p1 + t * (p2 - p1);
|
|
|
|
}
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-01 18:14:18 +02:00
|
|
|
const animate = (invokedAnimationStartTime, frameTime) => {
|
|
|
|
if (invokedAnimationStartTime !== animationStartTime) {
|
|
|
|
console.log("CANCEL ANIMATION ", invokedAnimationStartTime, " ", animationStartTime);
|
|
|
|
return;
|
|
|
|
}
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-02 11:54:20 +02:00
|
|
|
const animationDuration = 300
|
2024-04-01 18:14:18 +02:00
|
|
|
const normalizedTime = getNormalizedTime(invokedAnimationStartTime, invokedAnimationStartTime + animationDuration, frameTime)
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-01 18:14:18 +02:00
|
|
|
if (normalizedTime >= 1) {
|
|
|
|
console.log("exiting");
|
|
|
|
return;
|
2024-04-01 13:31:16 +02:00
|
|
|
}
|
|
|
|
|
2024-04-01 18:14:18 +02:00
|
|
|
const easing = cubicBezierEasing(0.25, 0.1, 0.25, 1, normalizedTime)
|
|
|
|
|
|
|
|
x = linearInterpolation(startX, endX, easing.x)
|
|
|
|
y = linearInterpolation(startY, endY, easing.y)
|
2024-04-02 11:54:20 +02:00
|
|
|
w = linearInterpolation(previousTooltipWidth, currentTooltipWidth, easing.x)
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-01 18:14:18 +02:00
|
|
|
requestAnimationFrame((newFrameTime) => animate(invokedAnimationStartTime, newFrameTime))
|
2024-04-02 13:07:11 +02:00
|
|
|
}*/
|
2024-04-01 13:31:16 +02:00
|
|
|
|
2024-04-02 13:10:26 +02:00
|
|
|
$: updatePosition(anchor, currentTooltip)
|
2024-04-01 13:31:16 +02:00
|
|
|
$: updatePositionOnVisibilityChange(visible, hovering)
|
2024-04-02 13:07:11 +02:00
|
|
|
/*$: requestAnimationFrame((frameTime) => animate(animationStartTime, frameTime))*/
|
2024-04-01 13:31:16 +02:00
|
|
|
|
|
|
|
const handleMouseenter = (e) => {
|
|
|
|
hovering = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleMouseleave = (e) => {
|
|
|
|
hovering = false;
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<Portal target=".spectrum">
|
|
|
|
<div
|
|
|
|
on:mouseenter={handleMouseenter}
|
|
|
|
on:mouseleave={handleMouseleave}
|
2024-04-02 13:07:11 +02:00
|
|
|
style:width={`${currentTooltipWidth}px`}
|
2024-04-02 11:54:20 +02:00
|
|
|
style:height={`${currentTooltipHeight}px`}
|
2024-04-02 13:07:11 +02:00
|
|
|
style:left={`${currentX}px`}
|
|
|
|
style:top={`${currentY}px`}
|
2024-04-01 13:31:16 +02:00
|
|
|
class="tooltip"
|
|
|
|
class:visible={visible || hovering}
|
|
|
|
>
|
2024-04-02 13:07:11 +02:00
|
|
|
<div class="screenSize"
|
|
|
|
style:left={`${-currentX}px`}
|
|
|
|
style:top={`${-currentY}px`}
|
|
|
|
>
|
2024-04-02 11:54:20 +02:00
|
|
|
<div
|
2024-04-02 13:07:11 +02:00
|
|
|
style:left={`${currentX}px`}
|
|
|
|
style:top={`${currentY}px`}
|
2024-04-02 11:54:20 +02:00
|
|
|
bind:this={currentTooltip}
|
|
|
|
class="currentContent"
|
|
|
|
>
|
|
|
|
<slot />
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
class="previousContent"
|
2024-04-02 13:07:11 +02:00
|
|
|
style:left={`${previousX}px`}
|
|
|
|
style:top={`${previousY}px`}
|
2024-04-02 11:54:20 +02:00
|
|
|
>
|
|
|
|
<slot name="previous"/>
|
|
|
|
</div>
|
|
|
|
</div>
|
2024-04-01 13:31:16 +02:00
|
|
|
</div>
|
|
|
|
</Portal>
|
|
|
|
|
|
|
|
<style>
|
2024-04-02 13:07:11 +02:00
|
|
|
/* Screen width absolute parent for tooltip content so that'd the applied width and height
|
|
|
|
to the root doesn't affect their size */
|
2024-04-02 11:54:20 +02:00
|
|
|
.screenSize {
|
|
|
|
position: absolute;
|
|
|
|
width: 100vw;
|
|
|
|
height: 100vh;
|
2024-04-02 13:07:11 +02:00
|
|
|
transition: top 300ms ease-in, left 300ms ease-in;
|
2024-04-02 11:54:20 +02:00
|
|
|
}
|
|
|
|
|
2024-04-01 13:31:16 +02:00
|
|
|
.tooltip {
|
|
|
|
position: absolute;
|
|
|
|
z-index: 9999;
|
|
|
|
pointer-events: none;
|
2024-04-02 11:54:20 +02:00
|
|
|
background-color: red;
|
|
|
|
opacity: 0;
|
|
|
|
overflow: hidden;
|
2024-04-02 13:07:11 +02:00
|
|
|
transition: width 300ms ease-in, height 300ms ease-in, top 300ms ease-in, left 300ms ease-in;
|
2024-04-01 13:31:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
.visible {
|
|
|
|
opacity: 1;
|
|
|
|
pointer-events: auto;
|
|
|
|
}
|
2024-04-02 11:54:20 +02:00
|
|
|
|
|
|
|
.currentContent {
|
|
|
|
position: absolute;
|
|
|
|
z-index: 10000;
|
|
|
|
}
|
|
|
|
|
|
|
|
.previousContent {
|
|
|
|
position: absolute;
|
|
|
|
z-index: 10000;
|
|
|
|
}
|
2024-04-01 13:31:16 +02:00
|
|
|
</style>
|