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

193 lines
4.7 KiB
Svelte
Raw Normal View History

2024-04-01 13:31:16 +02:00
<script>
import Portal from "svelte-portal"
2024-04-03 10:08:31 +02:00
import { getContext } from "svelte"
import Context from "../context"
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;
2024-04-05 10:48:38 +02:00
export let noAnimation = false
2024-04-03 00:07:48 +02:00
2024-04-04 09:38:25 +02:00
$: target = getContext(Context.PopoverRoot) || "#app"
2024-04-03 10:08:31 +02:00
2024-04-03 00:07:48 +02:00
let hovering = false
let wrapper
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-04 08:53:30 +02:00
const updatePosition = (anchor, currentTooltip, previousTooltip, wrapper) => {
2024-04-02 11:54:20 +02:00
requestAnimationFrame(() => {
2024-04-04 08:53:30 +02:00
if (anchor == null || currentTooltip == null || previousTooltip == null || wrapper == null) {
2024-04-02 11:54:20 +02:00
return;
}
2024-04-05 09:44:00 +02:00
2024-04-02 11:54:20 +02:00
const rect = anchor.getBoundingClientRect();
2024-04-04 08:53:30 +02:00
2024-04-04 23:16:25 +02:00
const windowOffset = (window.innerHeight - offset) - (currentTooltip.clientHeight + rect.y)
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-04 23:16:25 +02:00
currentY = windowOffset < 0 ? rect.y + windowOffset : rect.y
2024-04-03 00:07:48 +02:00
const fadeIn = [{ opacity: "0" }, { opacity: "1" }];
const fadeOut = [{ opacity: "1" }, { opacity: "0" }];
2024-04-03 11:08:40 +02:00
const fadeInTiming = {
2024-04-05 07:52:50 +02:00
duration: 200,
delay: 100,
2024-04-03 11:08:40 +02:00
iterations: 1,
easing: "ease-in",
fill: "both"
};
const fadeOutTiming = {
2024-04-05 07:52:50 +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
};
2024-04-05 10:48:38 +02:00
if (!noAnimation) {
2024-04-03 11:08:40 +02:00
currentTooltip.animate(fadeIn, fadeInTiming);
previousTooltip.animate(fadeOut, fadeOutTiming);
2024-04-05 10:48:38 +02:00
}
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";
2024-04-04 08:53:30 +02:00
//wrapper.style.width = `${currentTooltipWidth}px`
//wrapper.style.height = `${currentTooltipHeight}px`
2024-04-02 17:46:31 +02:00
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
requestAnimationFrame(() => {
wrapper.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-04 08:53:30 +02:00
$: updatePosition(anchor, currentTooltip, previousTooltip, wrapper)
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>
2024-04-03 10:08:31 +02:00
<Portal {target}>
2024-04-01 13:31:16 +02:00
<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:left={`${currentX}px`}
style:top={`${currentY}px`}
2024-04-04 08:53:30 +02:00
style:width={`${currentTooltipWidth}px`}
style:height={`${currentTooltipHeight}px`}
2024-04-01 13:31:16 +02:00
class="tooltip"
class:visible={visible || hovering}
2024-04-05 10:48:38 +02:00
class:noAnimation
2024-04-01 13:31:16 +02:00
>
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.
-->
2024-04-04 08:53:30 +02:00
<div class="screenSizeAbsoluteWrapper"
style:left={`${-currentX}px`}
style:top={`${-currentY}px`}
2024-04-05 10:48:38 +02:00
class:noAnimation
2024-04-04 08:53:30 +02:00
>
2024-04-02 11:54:20 +02:00
<div
bind:this={currentTooltip}
class="currentContent"
2024-04-04 08:53:30 +02:00
style:left={`${currentX}px`}
style:top={`${currentY}px`}
2024-04-02 11:54:20 +02:00
>
<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-05 08:33:14 +02:00
background-color: var(--background-alt);
box-shadow: 2px 2px 5px 0px rgba(0,0,0,0.42);
2024-04-03 00:07:48 +02:00
border-radius: 5px;
box-sizing: border-box;
2024-04-04 15:53:36 +02:00
border: 1px solid var(--grey-4);
2024-04-17 11:09:31 +02:00
opacity: 0;
2024-04-18 07:51:54 +02:00
overflow: hidden;
2024-04-04 08:53:30 +02:00
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 {
2024-04-04 08:53:30 +02:00
width: 200vw;
2024-04-04 23:16:25 +02:00
height: 100vh;
2024-04-02 17:46:31 +02:00
position: absolute;
transition: top 300ms ease-in, left 300ms ease-in;
}
2024-04-05 10:48:38 +02:00
.noAnimation {
transition: none;
}
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>