@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
JavaScript
/**
* 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
};
};