|
| 1 | +<script lang="ts"> |
| 2 | + import { fade } from "svelte/transition"; |
| 3 | + import { onDestroy } from "svelte"; |
| 4 | + import IconChevron from "./icons/IconChevron.svelte"; |
| 5 | +
|
| 6 | + export let scrollNode: HTMLElement; |
| 7 | + export { className as class }; |
| 8 | +
|
| 9 | + let visible = false; |
| 10 | + let className = ""; |
| 11 | + let observer: ResizeObserver | null = null; |
| 12 | +
|
| 13 | + $: if (scrollNode) { |
| 14 | + destroy(); |
| 15 | +
|
| 16 | + if (window.ResizeObserver) { |
| 17 | + observer = new ResizeObserver(() => { |
| 18 | + updateVisibility(); |
| 19 | + }); |
| 20 | + observer.observe(scrollNode); |
| 21 | + } |
| 22 | + scrollNode.addEventListener("scroll", updateVisibility); |
| 23 | + } |
| 24 | +
|
| 25 | + function updateVisibility() { |
| 26 | + if (!scrollNode) return; |
| 27 | + visible = |
| 28 | + Math.ceil(scrollNode.scrollTop) + 200 < scrollNode.scrollHeight - scrollNode.clientHeight && |
| 29 | + scrollNode.scrollTop > 200; |
| 30 | + } |
| 31 | +
|
| 32 | + function scrollToPrevious() { |
| 33 | + if (!scrollNode) return; |
| 34 | + const messages = scrollNode.querySelectorAll('[id^="message-"]'); |
| 35 | + const scrollTop = scrollNode.scrollTop; |
| 36 | + let previousMessage: Element | null = null; |
| 37 | +
|
| 38 | + for (let i = messages.length - 1; i >= 0; i--) { |
| 39 | + const messageTop = |
| 40 | + messages[i].getBoundingClientRect().top + |
| 41 | + scrollTop - |
| 42 | + scrollNode.getBoundingClientRect().top; |
| 43 | + if (messageTop < scrollTop - 1) { |
| 44 | + previousMessage = messages[i]; |
| 45 | + break; |
| 46 | + } |
| 47 | + } |
| 48 | +
|
| 49 | + if (previousMessage) { |
| 50 | + previousMessage.scrollIntoView({ behavior: "smooth", block: "start" }); |
| 51 | + } |
| 52 | + } |
| 53 | +
|
| 54 | + function destroy() { |
| 55 | + observer?.disconnect(); |
| 56 | + scrollNode?.removeEventListener("scroll", updateVisibility); |
| 57 | + } |
| 58 | +
|
| 59 | + onDestroy(destroy); |
| 60 | +</script> |
| 61 | + |
| 62 | +{#if visible} |
| 63 | + <button |
| 64 | + transition:fade={{ duration: 150 }} |
| 65 | + on:click={scrollToPrevious} |
| 66 | + class="btn absolute flex h-[41px] w-[41px] rounded-full border bg-white shadow-md transition-all hover:bg-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:shadow-gray-950 dark:hover:bg-gray-600 {className}" |
| 67 | + > |
| 68 | + <IconChevron classNames="rotate-180 mt-[2px]" /> |
| 69 | + </button> |
| 70 | +{/if} |
0 commit comments