@evermade/overflow-slider
Version:
Accessible slider that is powered by overflow: auto.
108 lines (106 loc) • 4.84 kB
JavaScript
const DEFAULT_CLASS_NAMES = {
visible: 'is-visible',
partlyVisible: 'is-partly-visible',
hidden: 'is-hidden',
};
function ClassNamesPlugin(args) {
return (slider) => {
var _a, _b;
const providedClassNames = (_a = args === null || args === void 0 ? void 0 : args.classNames) !== null && _a !== void 0 ? _a : args === null || args === void 0 ? void 0 : args.classnames;
const options = {
classNames: Object.assign(Object.assign({}, DEFAULT_CLASS_NAMES), providedClassNames !== null && providedClassNames !== void 0 ? providedClassNames : {}),
freezeStateOnVisible: (_b = args === null || args === void 0 ? void 0 : args.freezeStateOnVisible) !== null && _b !== void 0 ? _b : false,
};
const slideStates = new WeakMap();
const uniqueClassNames = Array.from(new Set(Object.values(options.classNames).filter((className) => Boolean(className))));
const getTargetBounds = () => {
const sliderRect = slider.container.getBoundingClientRect();
const sliderWidth = sliderRect.width;
if (!sliderWidth) {
return { targetStart: sliderRect.left, targetEnd: sliderRect.right };
}
let targetWidth = 0;
if (typeof slider.options.targetWidth === 'function') {
try {
targetWidth = slider.options.targetWidth(slider);
}
catch (error) {
targetWidth = 0;
}
}
if (!Number.isFinite(targetWidth) || targetWidth <= 0) {
targetWidth = sliderWidth;
}
const effectiveTargetWidth = Math.min(targetWidth, sliderWidth);
const offset = (sliderWidth - effectiveTargetWidth) / 2;
const clampedOffset = Math.max(offset, 0);
return {
targetStart: sliderRect.left + clampedOffset,
targetEnd: sliderRect.right - clampedOffset,
};
};
const update = () => {
const { targetStart, targetEnd } = getTargetBounds();
slider.slides.forEach((slide) => {
const slideRect = slide.getBoundingClientRect();
const slideLeft = slideRect.left;
const slideRight = slideRect.right;
const tolerance = 2;
const overlapsTarget = (slideRight - tolerance) > targetStart && (slideLeft + tolerance) < targetEnd;
const fullyInsideTarget = (slideLeft + tolerance) >= targetStart && (slideRight - tolerance) <= targetEnd;
let nextState = 'hidden';
if (overlapsTarget) {
nextState = fullyInsideTarget ? 'visible' : 'partlyVisible';
}
const prevState = slideStates.get(slide);
// If freezeStateOnVisible is enabled and slide was previously visible, keep it frozen
if (options.freezeStateOnVisible && prevState === 'visible') {
return;
}
if (prevState === nextState) {
return;
}
const nextClass = options.classNames[nextState];
if (prevState) {
const prevClass = options.classNames[prevState];
if (prevClass !== nextClass && prevClass) {
slide.classList.remove(prevClass);
}
}
else {
uniqueClassNames.forEach((className) => {
if (className !== nextClass) {
slide.classList.remove(className);
}
});
}
if (nextClass && !slide.classList.contains(nextClass)) {
slide.classList.add(nextClass);
}
slideStates.set(slide, nextState);
});
};
slider.on('created', update);
slider.on('pluginsLoaded', update);
slider.on('fullWidthPluginUpdate', update);
slider.on('contentsChanged', update);
slider.on('containerSizeChanged', update);
slider.on('detailsChanged', update);
slider.on('scrollEnd', update);
slider.on('scrollStart', update);
requestAnimationFrame(() => {
requestAnimationFrame(() => update());
});
let requestId = 0;
const debouncedUpdate = () => {
if (requestId) {
window.cancelAnimationFrame(requestId);
}
requestId = window.requestAnimationFrame(() => {
update();
});
};
slider.on('scroll', debouncedUpdate);
};
}
export { ClassNamesPlugin as default };