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

174 lines
4.4 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 anchor
export let visible = false
2024-04-03 00:07:48 +02:00
export let offset = 0;
let hovering = false
let wrapper
let resetWrapper
let currentTooltip
let previousTooltip
2024-04-01 13:31:16 +02:00
2024-04-02 17:46:31 +02:00
let initialShow = true;
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-02 17:46:31 +02:00
const handleVisibilityChange = (visible, hovering) => {
2024-04-01 13:31:16 +02:00
if (!visible && !hovering) {
2024-04-02 17:46:31 +02:00
initialShow = true;
}
}
2024-04-01 13:31:16 +02:00
2024-04-03 00:07:48 +02:00
const updatePosition = (anchor, currentTooltip, previousTooltip, wrapper, resetWrapper) => {
2024-04-02 11:54:20 +02:00
requestAnimationFrame(() => {
2024-04-03 00:07:48 +02:00
if (anchor == null || currentTooltip == null || previousTooltip == null || wrapper == null || resetWrapper == 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
2024-04-03 00:07:48 +02:00
currentX = rect.x - currentTooltipWidth - offset
2024-04-02 13:07:11 +02:00
currentY = rect.y
2024-04-03 00:07:48 +02:00
const fadeIn = [{ opacity: "0" }, { opacity: "1" }];
const fadeOut = [{ opacity: "1" }, { opacity: "0" }];
const fadeTiming = {
2024-04-03 08:50:43 +02:00
duration: 200,
2024-04-03 00:07:48 +02:00
iterations: 1,
2024-04-03 08:50:43 +02:00
easing: "ease-in",
fill: "forwards"
2024-04-03 00:07:48 +02:00
};
currentTooltip.animate(fadeIn, fadeTiming);
previousTooltip.animate(fadeOut, fadeTiming);
2024-04-02 11:54:20 +02:00
2024-04-02 17:46:31 +02:00
// Bypass animations if the tooltip has only just been opened
if (initialShow) {
initialShow = false;
2024-04-01 13:31:16 +02:00
2024-04-03 08:50:43 +02:00
previousTooltip.style.visibility = "hidden"
2024-04-02 17:46:31 +02:00
wrapper.style.transition = "none";
wrapper.style.width = `${currentTooltipWidth}px`
wrapper.style.height = `${currentTooltipHeight}px`
wrapper.style.top = `${currentY}px`
wrapper.style.left = `${currentX}px`
2024-04-01 13:31:16 +02:00
2024-04-02 17:46:31 +02:00
resetWrapper.style.transition = "none";
resetWrapper.style.top = `${-currentY}px`
resetWrapper.style.left = `${-currentX}px`
2024-04-01 13:31:16 +02:00
2024-04-02 17:46:31 +02:00
requestAnimationFrame(() => {
wrapper.style.removeProperty("transition");
resetWrapper.style.removeProperty("transition");
})
2024-04-03 08:50:43 +02:00
} else {
previousTooltip.style.removeProperty("visibility");
2024-04-02 17:46:31 +02:00
}
})
2024-04-01 18:14:18 +02:00
}
2024-04-01 13:31:16 +02:00
2024-04-03 00:07:48 +02:00
$: updatePosition(anchor, currentTooltip, previousTooltip, wrapper, resetWrapper)
2024-04-02 17:46:31 +02:00
$: handleVisibilityChange(visible, hovering)
2024-04-01 13:31:16 +02:00
const handleMouseenter = (e) => {
hovering = true;
}
const handleMouseleave = (e) => {
hovering = false;
}
</script>
<Portal target=".spectrum">
<div
2024-04-02 17:46:31 +02:00
bind:this={wrapper}
2024-04-01 13:31:16 +02:00
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 17:46:31 +02:00
<!-- absolutely position element with the opposite positioning, so that the tooltip elements can be positioned
using the same values as the root wrapper, while still being occluded by it.
-->
<div class="screenSizeAbsoluteWrapper"
bind:this={resetWrapper}
2024-04-02 13:07:11 +02:00
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-03 00:07:48 +02:00
bind:this={previousTooltip}
2024-04-02 11:54:20 +02:00
>
<slot name="previous"/>
</div>
</div>
2024-04-01 13:31:16 +02:00
</div>
</Portal>
<style>
.tooltip {
position: absolute;
z-index: 9999;
pointer-events: none;
2024-04-02 17:46:31 +02:00
background-color: var(--spectrum-global-color-gray-200);
2024-04-03 00:07:48 +02:00
border-radius: 5px;
box-sizing: border-box;
2024-04-02 17:46:31 +02:00
2024-04-02 11:54:20 +02:00
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
}
2024-04-02 17:46:31 +02:00
.screenSizeAbsoluteWrapper {
position: absolute;
width: 100vw;
height: 100vh;
transition: 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;
2024-04-03 00:07:48 +02:00
z-index: 10001;
2024-04-02 11:54:20 +02:00
}
.previousContent {
position: absolute;
z-index: 10000;
}
2024-04-01 13:31:16 +02:00
</style>