Skip to content

Commit

Permalink
scroll area work
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed Jul 26, 2024
1 parent 76c7b62 commit fb02c23
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 46 deletions.
77 changes: 46 additions & 31 deletions packages/bits-ui/src/lib/bits/scroll-area/scroll-area.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,32 +222,31 @@ class ScrollAreaScrollbarHoverState {
const scrollAreaNode = this.root.scrollAreaNode;
const hideDelay = this.root.scrollHideDelay.current;
let hideTimer = 0;
if (scrollAreaNode) {
const handlePointerEnter = () => {
window.clearTimeout(hideTimer);
untrack(() => (this.isVisible = true));
};

const handlePointerLeave = () => {
if (hideTimer) window.clearTimeout(hideTimer);
hideTimer = window.setTimeout(() => {
untrack(() => {
this.scrollbar.hasThumb = false;
this.isVisible = false;
});
}, hideDelay);
};

const unsubListeners = executeCallbacks(
addEventListener(scrollAreaNode, "pointerenter", handlePointerEnter),
addEventListener(scrollAreaNode, "pointerleave", handlePointerLeave)
);
if (!scrollAreaNode) return;
const handlePointerEnter = () => {
window.clearTimeout(hideTimer);
untrack(() => (this.isVisible = true));
};

return () => {
window.clearTimeout(hideTimer);
unsubListeners();
};
}
const handlePointerLeave = () => {
if (hideTimer) window.clearTimeout(hideTimer);
hideTimer = window.setTimeout(() => {
untrack(() => {
this.scrollbar.hasThumb = false;
this.isVisible = false;
});
}, hideDelay);
};

const unsubListeners = executeCallbacks(
addEventListener(scrollAreaNode, "pointerenter", handlePointerEnter),
addEventListener(scrollAreaNode, "pointerleave", handlePointerLeave)
);

return () => {
window.clearTimeout(hideTimer);
unsubListeners();
};
});
}

Expand Down Expand Up @@ -382,6 +381,7 @@ class ScrollAreaScrollbarVisibleState {
});
thumbRatio = $derived.by(() => getThumbRatio(this.sizes.viewport, this.sizes.content));
hasThumb = $derived.by(() => Boolean(this.thumbRatio > 0 && this.thumbRatio < 1));
prevTransformStyle = "";

constructor(scrollbar: ScrollAreaScrollbarState) {
this.scrollbar = scrollbar;
Expand All @@ -390,6 +390,12 @@ class ScrollAreaScrollbarVisibleState {
$effect(() => {
this.scrollbar.hasThumb = this.hasThumb;
});

$effect.pre(() => {
if (!this.scrollbar.hasThumb && this.thumbNode) {
this.prevTransformStyle = this.thumbNode.style.transform;
}
});
}

setSizes = (sizes: Sizes) => {
Expand Down Expand Up @@ -421,7 +427,8 @@ class ScrollAreaScrollbarVisibleState {
sizes: this.sizes,
dir: this.root.dir.current,
});
this.thumbNode.style.transform = `translate3d(${offset}px, 0, 0)`;
const transformStyle = `translate3d(${offset}px, 0, 0)`;
this.thumbNode.style.transform = transformStyle;
};

xOnWheelScroll = (scrollPos: number) => {
Expand All @@ -441,7 +448,8 @@ class ScrollAreaScrollbarVisibleState {
if (!(this.root.viewportNode && this.thumbNode)) return;
const scrollPos = this.root.viewportNode.scrollTop;
const offset = getThumbOffsetFromScroll({ scrollPos, sizes: this.sizes });
this.thumbNode.style.transform = `translate3d(0, ${offset}px, 0)`;
const transformStyle = `translate3d(0, ${offset}px, 0)`;
this.thumbNode.style.transform = transformStyle;
};

yOnWheelScroll = (scrollPos: number) => {
Expand All @@ -451,7 +459,10 @@ class ScrollAreaScrollbarVisibleState {

yOnDragScroll = (pointerPos: number) => {
if (!this.root.viewportNode) return;
this.root.viewportNode.scrollTop = this.getScrollPosition(pointerPos);
this.root.viewportNode.scrollTop = this.getScrollPosition(
pointerPos,
this.root.dir.current
);
};

createScrollbarX(props: ScrollbarAxisStateProps) {
Expand Down Expand Up @@ -554,8 +565,7 @@ class ScrollAreaScrollbarXState implements ScrollbarAxisState {
};

thumbSize = $derived.by(() => {
const ts = getThumbSize(this.scrollbarVis.sizes);
return ts;
return getThumbSize(this.scrollbarVis.sizes);
});

props = $derived.by(
Expand Down Expand Up @@ -731,6 +741,10 @@ class ScrollAreaScrollbarSharedState {
untrack(() => this.handleThumbPositionChange());
});

$effect(() => {
this.handleThumbPositionChange();
});

useResizeObserver(() => this.scrollbarState.ref.current, this.handleResize);
useResizeObserver(() => this.root.contentNode, this.handleResize);
}
Expand Down Expand Up @@ -854,7 +868,7 @@ class ScrollAreaThumbImplState {
this.#scrollbarState.handleThumbPointerDown({ x, y });
};

#onpointerup = (e: PointerEvent) => {
#onpointerup = () => {
this.#scrollbarState.handleThumbPointerUp();
};

Expand All @@ -866,6 +880,7 @@ class ScrollAreaThumbImplState {
style: {
width: "var(--bits-scroll-area-thumb-width)",
height: "var(--bits-scroll-area-thumb-height)",
transform: this.#scrollbarState.scrollbarVis.prevTransformStyle,
},
onpointerdowncapture: this.#onpointerdowncapture,
onpointerup: this.#onpointerup,
Expand Down
14 changes: 11 additions & 3 deletions sites/docs/content/components/scroll-area.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ We'll use this custom component in the following examples to demonstrate how to

The `hover` type is the default type of the scroll area, demonstrated in the featured example above. It only shows scrollbars when the user hovers over the scroll area and the content is larger than the viewport.

```svelte {1}
<CustomScrollArea type="hover">
<!-- ... -->
</CustomScrollArea>
```

<ScrollAreaDemoCustom type="hover" />

### Scroll

The `scroll` type displays the scrollbars when the user scrolls the content. This is similar to the behavior of MacOS.
Expand All @@ -115,15 +123,15 @@ The `auto` type behaves similarly to your typical browser scrollbars. When the c

### Always

The `always` type behaves as if you set `overflow: scroll` on the scroll area. Scrollbars will always be visible, even when the content is smaller than the viewport.
The `always` type behaves as if you set `overflow: scroll` on the scroll area. Scrollbars will always be visible, even when the content is smaller than the viewport. We've also set the `orientation` prop on the `CustomScrollArea` to `'both'` to ensure both scrollbars are rendered.

```svelte {1}
<CustomScrollArea type="always">
<CustomScrollArea type="always" orientation="both">
<!-- ... -->
</CustomScrollArea>
```

<ScrollAreaDemoCustom type="always" />
<ScrollAreaDemoCustom type="always" orientation="both" />

## Customizing the Hide Delay

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,14 @@
{orientation}
class="flex w-2.5 touch-none select-none rounded-full border-l border-l-transparent bg-muted p-px transition-all duration-200 hover:w-3 hover:bg-dark-10 data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out-0 data-[state=visible]:fade-in-0"
>
<ScrollArea.Thumb
class="flex-1 rounded-full bg-muted-foreground data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out-0 data-[state=visible]:fade-in-0"
/>
<ScrollArea.Thumb class="flex-1 rounded-full bg-muted-foreground" />
</ScrollArea.Scrollbar>
{:else}
<ScrollArea.Scrollbar
{orientation}
class="flex h-2.5 touch-none select-none rounded-full border-t border-t-transparent bg-muted p-px transition-all duration-200 hover:h-3 hover:bg-dark-10 data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out-0 data-[state=visible]:fade-in-0"
>
<ScrollArea.Thumb
class="rounded-full bg-muted-foreground data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out-0 data-[state=visible]:fade-in-0"
/>
<ScrollArea.Thumb class="d rounded-full bg-muted-foreground" />
</ScrollArea.Scrollbar>
{/if}
{/snippet}
Expand Down
8 changes: 2 additions & 6 deletions sites/docs/src/lib/components/demos/scroll-area-demo.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,13 @@
orientation="vertical"
class="flex w-2.5 touch-none select-none rounded-full border-l border-l-transparent bg-muted p-px transition-all duration-200 hover:w-3 hover:bg-dark-10 data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out-0 data-[state=visible]:fade-in-0"
>
<ScrollArea.Thumb
class="flex-1 rounded-full bg-muted-foreground data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out-0 data-[state=visible]:fade-in-0"
/>
<ScrollArea.Thumb class="flex-1 rounded-full bg-muted-foreground" />
</ScrollArea.Scrollbar>
<ScrollArea.Scrollbar
orientation="horizontal"
class="flex h-2.5 touch-none select-none rounded-full border-t border-t-transparent bg-muted p-px transition-all duration-200 hover:h-3 hover:bg-dark-10 data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out-0 data-[state=visible]:fade-in-0"
>
<ScrollArea.Thumb
class="rounded-full bg-muted-foreground data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out-0 data-[state=visible]:fade-in-0"
/>
<ScrollArea.Thumb class="rounded-full bg-muted-foreground" />
</ScrollArea.Scrollbar>
<ScrollArea.Corner />
</ScrollArea.Root>

0 comments on commit fb02c23

Please sign in to comment.