UNPKG

@selenite/commons

Version:

This typescript package provides a set of frequently used utilities, types and svelte actions for building projects with Typescript and Svelte.

93 lines (92 loc) 2.89 kB
/** * Actions to add scrolling behavior, like custom scrolling or scroll into view. * * @see {@link horizontalScroll} * @see {@link scrollIntoView} * @module */ import { isOverflowing } from '../utils'; import { clamp, debounce } from 'lodash-es'; import { tweened } from 'svelte/motion'; /** * Adds horizontal scrolling to an element. * * This action removes the needs to press ctrl or right to scroll * horizontally. * @param node - The element to add horizontal scrolling to. * @param params - The duration of the scroll animation. * @returns svelte action */ export const horizontalScroll = (node, { duration = 150 } = {}) => { let isActionScroll = false; const scroll = tweened(node.scrollLeft, { duration, interpolate: (a, b) => (t) => a + (b - a) * t }); let isFirst = true; scroll.subscribe((value) => { if (isFirst) { isFirst = false; return; } if (!isActionScroll) return; node.scrollLeft = value; }); const stopClamingScroll = debounce(() => { isActionScroll = false; }, duration, { leading: false, trailing: true }); const handleWheel = (e) => { const overflowing = isOverflowing(node); if (overflowing.vertical || !overflowing.horizontal) return; if (e.deltaY === 0) return; isActionScroll = true; const firstChildRect = node.firstElementChild?.getBoundingClientRect(); const lastChildRect = node.lastElementChild?.getBoundingClientRect(); if (!firstChildRect || !lastChildRect) { console.warn('Horizontal scroll: No children found'); return; } // Ensure offset stays valid const maxOffset = lastChildRect.width + lastChildRect.x - firstChildRect.x; scroll.set(clamp(node.scrollLeft + e.deltaY, 0, maxOffset)); e.preventDefault(); stopClamingScroll(); }; node.addEventListener('wheel', handleWheel, { passive: false }); node.addEventListener('scroll', () => { if (!isActionScroll) { scroll.set(node.scrollLeft); } }); return { destroy() { node.removeEventListener('wheel', handleWheel); } }; }; /** * Scrolls into view an element. */ export const scrollIntoView = (node, enabled) => { function update(enabled_ = true) { const parent = node.parentElement; if (!parent) return; if (!enabled_) return; const parentRect = parent.getBoundingClientRect(); const nodeRect = node.getBoundingClientRect(); parent.scrollTo({ left: nodeRect.left - parentRect.left, top: nodeRect.top - parentRect.top, behavior: 'smooth' }); } update(enabled); return { update }; };