budibase/packages/bbui/src/Tooltip/Context.svelte

143 lines
3.3 KiB
Svelte
Raw Normal View History

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
export let tooltip
export let anchor
export let visible = false
export let hovering = false
2024-04-01 18:14:18 +02:00
let startX = 0
let startY = 0
let endX = 0
let endY = 0
2024-04-01 13:31:16 +02:00
let x = 0;
let y = 0;
2024-04-01 18:14:18 +02:00
let animationStartTime = 0;
2024-04-01 13:31:16 +02:00
const updatePositionOnVisibilityChange = (visible, hovering) => {
if (!visible && !hovering) {
x = 0;
y = 0;
}
}
const updatePosition = (anchor, tooltip) => {
if (anchor == null) {
return;
}
const rect = anchor.getBoundingClientRect();
const tooltipWidth = tooltip?.getBoundingClientRect()?.width ?? 0;
2024-04-01 18:14:18 +02:00
startX = x
startY = y
endX = rect.x - tooltipWidth
endY = rect.y
2024-04-01 13:47:09 +02:00
animationStartTime = document.timeline.currentTime
2024-04-01 13:31:16 +02:00
if (x === 0 && y === 0) {
2024-04-01 18:14:18 +02:00
startX = endX
startY = endY
2024-04-01 13:31:16 +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-01 18:14:18 +02:00
const cubicBezierInterpolation = (p1, p2, p3, p4, t) => {
return Math.pow(1 - t, 3) * p1 +
3 * Math.pow(1 - t, 2) * t * p2 +
3 * (1 - t) * Math.pow(t, 2) * p3 +
Math.pow(t, 3) * p4;
}
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-01 18:14:18 +02:00
const animationDuration = 200
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-01 13:31:16 +02:00
2024-04-01 18:14:18 +02:00
requestAnimationFrame((newFrameTime) => animate(invokedAnimationStartTime, newFrameTime))
2024-04-01 13:31:16 +02:00
}
$: updatePosition(anchor, tooltip)
$: updatePositionOnVisibilityChange(visible, hovering)
2024-04-01 18:14:18 +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
bind:this={tooltip}
on:mouseenter={handleMouseenter}
on:mouseleave={handleMouseleave}
style:top={`${y}px`}
style:left={`${x}px`}
class="tooltip"
class:visible={visible || hovering}
>
<slot />
</div>
2024-04-01 18:14:18 +02:00
<slot name="previous" />
2024-04-01 13:31:16 +02:00
</Portal>
<style>
.tooltip {
position: absolute;
z-index: 9999;
opacity: 0;
pointer-events: none;
}
.visible {
opacity: 1;
pointer-events: auto;
}
</style>