All files / lib scrollcontroller.svelte.ts

0% Statements 0/31
0% Branches 0/10
0% Functions 0/11
0% Lines 0/26

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64                                                                                                                               
import type { Attachment } from 'svelte/attachments';
 
export function scrollController(scroll: { y: number }): Attachment {
	return (node) => {
		if (!(node instanceof HTMLElement)) return;
		return new ScrollController(node, scroll).start();
	};
}
 
class ScrollController {
	mouseWheelActive = $state(false);
 
	constructor(
		public scrollable: HTMLElement,
		public scroll: { y: number }
	) {}
 
	/**
	 * Returns a cleanup function
	 */
	start() {
		// console.debug(
		// 	'[scroll controller] starting with scroll position',
		// 	this.scroll.y,
		// 	'on',
		// 	this.scrollable
		// );
 
		const cleanup = $effect.root(() => {
			if (this.scroll.y === this.scrollable.scrollTop) return;
			if (!this.scrollable.scrollHeight) return;
			// TODO figure out a better way to ensure the scroll happens when the container is ready
			requestAnimationFrame(() => {
				if (!this.scrollable) return;
				if (this.mouseWheelActive) return;
				console.debug('[scroll controller] syncing scroll position to', this.scroll.y);
				this.scrollable.scrollTo({
					top: this.scroll.y,
				});
			});
		});
 
		const onscroll = () => {
			this.scroll.y = this.scrollable.scrollTop;
		};
 
		const onmousewheel = () => {
			this.mouseWheelActive = true;
			setTimeout(() => {
				this.mouseWheelActive = false;
			}, 500);
		};
 
		this.scrollable.addEventListener('scroll', onscroll);
		this.scrollable.addEventListener('mousewheel', onmousewheel, { passive: true });
 
		return () => {
			cleanup();
			this.scrollable?.removeEventListener('scroll', onscroll);
			this.scrollable?.removeEventListener('mousewheel', onmousewheel);
		};
	}
}