Skip to content

Commit

Permalink
feat(Tooltip): Support contained="window" to keep tooltip within wi…
Browse files Browse the repository at this point in the history
…ndow/viewport (along with `container` and `false`)
  • Loading branch information
techniq committed Jan 14, 2025
1 parent 7f39f93 commit e00528a
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 13 deletions.
5 changes: 5 additions & 0 deletions .changeset/metal-mangos-leave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'layerchart': minor
---

feat(Tooltip): Support `contained="window"` to keep tooltip within window/viewport (along with `container` and `false`)
34 changes: 32 additions & 2 deletions packages/layerchart/src/lib/components/tooltip/Tooltip.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
| 'bottom-right';
export let anchor: Placement = 'top-left';
export let contained: 'container' | false = 'container'; // TODO: Support 'window' using getBoundingClientRect()
export let contained: 'container' | 'window' | false = 'container';
export let variant: 'default' | 'invert' | 'none' = 'default';
/** Set to `false` to disable spring transitions */
Expand Down Expand Up @@ -134,8 +134,8 @@
rect.bottom = rect.top + tooltipHeight;
rect.right = rect.left + tooltipWidth;
// Check if outside of container and swap align side accordingly
if (contained === 'container') {
// Check if outside of container and swap align side accordingly
if ((xAlign === 'start' || xAlign === 'center') && rect.right > $containerWidth) {
rect.left = alignValue(xValue, 'end', xOffset, tooltipWidth);
}
Expand All @@ -151,11 +151,40 @@
rect.top = alignValue(yValue, 'start', yOffset, tooltipHeight);
}
rect.bottom = rect.top + tooltipHeight;
} else if (contained === 'window') {
// Check if outside of window / viewport and swap align side accordingly
// Root <div> won't be available on initial mount
if (rootEl?.parentElement) {
const parentViewportRect = rootEl.parentElement.getBoundingClientRect();
if (
(xAlign === 'start' || xAlign === 'center') &&
parentViewportRect.left + rect.right > window.innerWidth
) {
rect.left = alignValue(xValue, 'end', xOffset, tooltipWidth);
}
if ((xAlign === 'end' || xAlign === 'center') && parentViewportRect.left + rect.left < 0) {
rect.left = alignValue(xValue, 'start', xOffset, tooltipWidth);
}
rect.right = rect.left + tooltipWidth;
if (
(yAlign === 'start' || yAlign === 'center') &&
parentViewportRect.top + rect.bottom > window.innerHeight
) {
rect.top = alignValue(yValue, 'end', yOffset, tooltipHeight);
}
if ((yAlign === 'end' || yAlign === 'center') && parentViewportRect.top + rect.top < 0) {
rect.top = alignValue(yValue, 'start', yOffset, tooltipHeight);
}
rect.bottom = rect.top + tooltipHeight;
}
}
$yPos = rect.top;
$xPos = rect.left;
}
let rootEl: HTMLDivElement;
</script>

{#if $tooltip.data}
Expand All @@ -166,6 +195,7 @@
transition:fade={{ duration: 100 }}
bind:clientWidth={tooltipWidth}
bind:clientHeight={tooltipHeight}
bind:this={rootEl}
>
<div
class={cls(
Expand Down
32 changes: 22 additions & 10 deletions packages/layerchart/src/routes/docs/components/Tooltip/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
] as const;
let anchor: ComponentProps<Tooltip.Root>['anchor'] = 'top-left';
let snap: 'pointer' | 'data' = 'pointer';
let contained: ComponentProps<Tooltip.Root>['contained'] = false;
let tooltipContext: ComponentProps<Chart<any>>['tooltipContext'];
</script>
Expand Down Expand Up @@ -472,23 +473,14 @@

<h2>Anchor location</h2>

<div class="grid grid-cols-2 gap-2 mb-2">
<div class="grid grid-cols-3 gap-2 mb-2">
<Toggle let:on={open} let:toggle>
<Field label="Anchor" class="cursor-pointer" on:click={toggle}>
<span class="text-sm">
{anchor}
</span>
</Field>

<MenuField
label="Snap"
bind:value={snap}
options={[
{ label: 'pointer', value: 'pointer' },
{ label: 'data', value: 'data' },
]}
/>

<Menu {open} on:close={toggle} placement="bottom-start">
<div class="grid grid-cols-3 gap-1 p-1">
{#each anchorOptions as option}
Expand All @@ -503,6 +495,25 @@
</div>
</Menu>
</Toggle>

<MenuField
label="Snap"
bind:value={snap}
options={[
{ label: 'pointer', value: 'pointer' },
{ label: 'data', value: 'data' },
]}
/>

<MenuField
label="Contained"
bind:value={contained}
options={[
{ label: 'none', value: false },
{ label: 'container', value: 'container' },
{ label: 'window', value: 'window' },
]}
/>
</div>

<Preview data={dateSeries}>
Expand Down Expand Up @@ -533,6 +544,7 @@
xOffset={['top', 'center', 'bottom'].includes(anchor ?? '') ? 0 : 10}
y={snap}
yOffset={['left', 'center', 'right'].includes(anchor ?? '') ? 0 : 10}
{contained}
let:data
>
<Tooltip.Header>{format(data.date, 'eee, MMMM do')}</Tooltip.Header>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export async function load() {
'mix and match',
],
'Multiple instances',
'Maintain within chart container, or overflow outside',
'Maintain within chart container, window/viewport, or overflow outside',
],
related: ['components/TooltipContext', 'components/Highlight'],
},
Expand Down

0 comments on commit e00528a

Please sign in to comment.