Skip to content

Commit

Permalink
Separate core and plugins into separate files
Browse files Browse the repository at this point in the history
  • Loading branch information
TeemuSuoranta committed Feb 23, 2024
1 parent a9d9f93 commit c349069
Show file tree
Hide file tree
Showing 93 changed files with 5,595 additions and 3,847 deletions.
74 changes: 74 additions & 0 deletions dist/arrows.esm.js
@@ -0,0 +1,74 @@
const DEFAULT_TEXTS = {
buttonPrevious: 'Previous items',
buttonNext: 'Next items',
};
const DEFAULT_ICONS = {
prev: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8.6 3.4l-7.6 7.6 7.6 7.6 1.4-1.4-5-5h12.6v-2h-12.6l5-5z"/></svg>',
next: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.4 3.4l-1.4 1.4 5 5h-12.6v2h12.6l-5 5 1.4 1.4 7.6-7.6z"/></svg>',
};
const DEFAULT_CLASS_NAMES = {
navContainer: 'overflow-slider__arrows',
prevButton: 'overflow-slider__arrows-button overflow-slider__arrows-button--prev',
nextButton: 'overflow-slider__arrows-button overflow-slider__arrows-button--next',
};
function ArrowsPlugin(args) {
return (slider) => {
var _a, _b, _c, _d;
const options = {
texts: Object.assign(Object.assign({}, DEFAULT_TEXTS), (args === null || args === void 0 ? void 0 : args.texts) || []),
icons: Object.assign(Object.assign({}, DEFAULT_ICONS), (args === null || args === void 0 ? void 0 : args.icons) || []),
classNames: Object.assign(Object.assign({}, DEFAULT_CLASS_NAMES), (args === null || args === void 0 ? void 0 : args.classNames) || []),
container: (_a = args === null || args === void 0 ? void 0 : args.container) !== null && _a !== void 0 ? _a : null,
};
const nav = document.createElement('div');
nav.classList.add(options.classNames.navContainer);
const prev = document.createElement('button');
prev.setAttribute('class', options.classNames.prevButton);
prev.setAttribute('type', 'button');
prev.setAttribute('aria-label', options.texts.buttonPrevious);
prev.setAttribute('aria-controls', (_b = slider.container.getAttribute('id')) !== null && _b !== void 0 ? _b : '');
prev.setAttribute('data-type', 'prev');
prev.innerHTML = options.icons.prev;
prev.addEventListener('click', () => slider.moveToDirection('prev'));
const next = document.createElement('button');
next.setAttribute('class', options.classNames.nextButton);
next.setAttribute('type', 'button');
next.setAttribute('aria-label', options.texts.buttonNext);
next.setAttribute('aria-controls', (_c = slider.container.getAttribute('id')) !== null && _c !== void 0 ? _c : '');
next.setAttribute('data-type', 'next');
next.innerHTML = options.icons.next;
next.addEventListener('click', () => slider.moveToDirection('next'));
// insert buttons to the nav
nav.appendChild(prev);
nav.appendChild(next);
const update = () => {
const scrollLeft = slider.container.scrollLeft;
const scrollWidth = slider.container.scrollWidth;
const clientWidth = slider.container.clientWidth;
if (scrollLeft === 0) {
prev.setAttribute('data-has-content', 'false');
}
else {
prev.setAttribute('data-has-content', 'true');
}
if (scrollLeft + clientWidth >= scrollWidth) {
next.setAttribute('data-has-content', 'false');
}
else {
next.setAttribute('data-has-content', 'true');
}
};
if (options.container) {
options.container.appendChild(nav);
}
else {
(_d = slider.container.parentNode) === null || _d === void 0 ? void 0 : _d.insertBefore(nav, slider.container.nextSibling);
}
update();
slider.on('scroll', update);
slider.on('contentsChanged', update);
slider.on('containerSizeChanged', update);
};
}

export { ArrowsPlugin as default };
1 change: 1 addition & 0 deletions dist/arrows.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions dist/core/details.esm.js
@@ -0,0 +1,34 @@
function details(slider) {
let instance;
let hasOverflow = false;
let slideCount = 0;
let containerWidth = 0;
let scrollableAreaWidth = 0;
let amountOfPages = 0;
let currentPage = 1;
if (slider.container.scrollWidth > slider.container.clientWidth) {
hasOverflow = true;
}
slideCount = Array.from(slider.container.querySelectorAll(':scope > *')).length;
containerWidth = slider.container.offsetWidth;
scrollableAreaWidth = slider.container.scrollWidth;
amountOfPages = Math.ceil(scrollableAreaWidth / containerWidth);
if (slider.container.scrollLeft >= 0) {
currentPage = Math.floor(slider.container.scrollLeft / containerWidth);
// consider as last page if the scrollLeft + containerWidth is equal to scrollWidth
if (slider.container.scrollLeft + containerWidth === scrollableAreaWidth) {
currentPage = amountOfPages - 1;
}
}
instance = {
hasOverflow,
slideCount,
containerWidth,
scrollableAreaWidth,
amountOfPages,
currentPage,
};
return instance;
}

export { details as default };
34 changes: 34 additions & 0 deletions dist/core/details.js
@@ -0,0 +1,34 @@
function details(slider) {
let instance;
let hasOverflow = false;
let slideCount = 0;
let containerWidth = 0;
let scrollableAreaWidth = 0;
let amountOfPages = 0;
let currentPage = 1;
if (slider.container.scrollWidth > slider.container.clientWidth) {
hasOverflow = true;
}
slideCount = Array.from(slider.container.querySelectorAll(':scope > *')).length;
containerWidth = slider.container.offsetWidth;
scrollableAreaWidth = slider.container.scrollWidth;
amountOfPages = Math.ceil(scrollableAreaWidth / containerWidth);
if (slider.container.scrollLeft >= 0) {
currentPage = Math.floor(slider.container.scrollLeft / containerWidth);
// consider as last page if the scrollLeft + containerWidth is equal to scrollWidth
if (slider.container.scrollLeft + containerWidth === scrollableAreaWidth) {
currentPage = amountOfPages - 1;
}
}
instance = {
hasOverflow,
slideCount,
containerWidth,
scrollableAreaWidth,
amountOfPages,
currentPage,
};
return instance;
}

export { details as default };
1 change: 1 addition & 0 deletions dist/core/details.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

218 changes: 218 additions & 0 deletions dist/core/slider.esm.js
@@ -0,0 +1,218 @@
import details from './details.esm.js';
import { generateId, objectsAreEqual } from './utils.esm.js';

function Slider(container, options, plugins) {
let slider;
let subs = {};
function init() {
slider.container = container;
// ensure container has id
let containerId = container.getAttribute('id');
if (containerId === null) {
containerId = generateId('overflow-slider');
container.setAttribute('id', containerId);
}
setDetails(true);
slider.on('contentsChanged', () => setDetails());
slider.on('containerSizeChanged', () => setDetails());
let requestId = 0;
const setDetailsDebounce = () => {
if (requestId) {
window.cancelAnimationFrame(requestId);
}
requestId = window.requestAnimationFrame(() => {
setDetails();
});
};
slider.on('scroll', setDetailsDebounce);
addEventListeners();
setDataAttributes();
setCSSVariables();
if (plugins) {
for (const plugin of plugins) {
plugin(slider);
}
}
slider.on('detailsChanged', () => {
setDataAttributes();
setCSSVariables();
});
slider.emit('created');
}
function setDetails(isInit = false) {
const oldDetails = slider.details;
const newDetails = details(slider);
slider.details = newDetails;
if (!isInit && !objectsAreEqual(oldDetails, newDetails)) {
slider.emit('detailsChanged');
}
else if (isInit) {
slider.emit('detailsChanged');
}
}
function addEventListeners() {
// changes to DOM
const observer = new MutationObserver(() => slider.emit('contentsChanged'));
observer.observe(slider.container, { childList: true });
// container size changes
const resizeObserver = new ResizeObserver(() => slider.emit('containerSizeChanged'));
resizeObserver.observe(slider.container);
// scroll event with debouncing
slider.container.addEventListener('scroll', () => slider.emit('scroll'));
// Listen for mouse down and touch start events on the document
// This handles both mouse clicks and touch interactions
let wasInteractedWith = false;
slider.container.addEventListener('mousedown', () => {
wasInteractedWith = true;
});
slider.container.addEventListener('touchstart', () => {
wasInteractedWith = true;
}, { passive: true });
slider.container.addEventListener('focusin', (e) => {
// move target parents as long as they are not the container
// but only if focus didn't start from mouse or touch
if (!wasInteractedWith) {
let target = e.target;
while (target.parentElement !== slider.container) {
if (target.parentElement) {
target = target.parentElement;
}
else {
break;
}
}
ensureSlideIsInView(target);
}
wasInteractedWith = false;
});
}
function setCSSVariables() {
slider.container.style.setProperty('--slider-container-width', `${slider.details.containerWidth}px`);
slider.container.style.setProperty('--slider-scrollable-width', `${slider.details.scrollableAreaWidth}px`);
slider.container.style.setProperty('--slider-slides-count', `${slider.details.slideCount}`);
}
function setDataAttributes() {
slider.container.setAttribute('data-has-overflow', slider.details.hasOverflow ? 'true' : 'false');
}
function ensureSlideIsInView(slide) {
const slideRect = slide.getBoundingClientRect();
const sliderRect = slider.container.getBoundingClientRect();
const containerWidth = slider.container.offsetWidth;
const scrollLeft = slider.container.scrollLeft;
const slideStart = slideRect.left - sliderRect.left + scrollLeft;
const slideEnd = slideStart + slideRect.width;
let scrollTarget = null;
if (slideStart < scrollLeft) {
scrollTarget = slideStart;
}
else if (slideEnd > scrollLeft + containerWidth) {
scrollTarget = slideEnd - containerWidth;
}
if (scrollTarget) {
slider.container.style.scrollSnapType = 'none';
slider.container.scrollLeft = scrollTarget;
// @todo resume scroll snapping but at least proximity gives a lot of trouble
// and it's not really needed for this use case but it would be nice to have
// it back in case it's needed. We need to calculate scrollLeft some other way
}
}
function moveToDirection(direction = "prev") {
const scrollStrategy = slider.options.scrollStrategy;
const scrollLeft = slider.container.scrollLeft;
const sliderRect = slider.container.getBoundingClientRect();
const containerWidth = slider.container.offsetWidth;
let targetScrollPosition = scrollLeft;
if (direction === 'prev') {
targetScrollPosition = Math.max(0, scrollLeft - slider.container.offsetWidth);
}
else if (direction === 'next') {
targetScrollPosition = Math.min(slider.container.scrollWidth, scrollLeft + slider.container.offsetWidth);
}
if (scrollStrategy === 'fullSlide') {
let fullSldeTargetScrollPosition = null;
const slides = Array.from(slider.container.querySelectorAll(':scope > *'));
let gapSize = 0;
if (slides.length > 1) {
const firstSlideRect = slides[0].getBoundingClientRect();
const secondSlideRect = slides[1].getBoundingClientRect();
gapSize = secondSlideRect.left - firstSlideRect.right;
}
// extend targetScrollPosition to include gap
if (direction === 'prev') {
fullSldeTargetScrollPosition = Math.max(0, targetScrollPosition - gapSize);
}
else {
fullSldeTargetScrollPosition = Math.min(slider.container.scrollWidth, targetScrollPosition + gapSize);
}
if (direction === 'next') {
let partialSlideFound = false;
for (let slide of slides) {
const slideRect = slide.getBoundingClientRect();
const slideStart = slideRect.left - sliderRect.left + scrollLeft;
const slideEnd = slideStart + slideRect.width;
if (slideStart < targetScrollPosition && slideEnd > targetScrollPosition) {
fullSldeTargetScrollPosition = slideStart;
partialSlideFound = true;
break;
}
}
if (!partialSlideFound) {
fullSldeTargetScrollPosition = Math.min(targetScrollPosition, slider.container.scrollWidth - slider.container.offsetWidth);
}
if (fullSldeTargetScrollPosition && fullSldeTargetScrollPosition > scrollLeft) {
targetScrollPosition = fullSldeTargetScrollPosition;
}
}
else {
let partialSlideFound = false;
for (let slide of slides) {
const slideRect = slide.getBoundingClientRect();
const slideStart = slideRect.left - sliderRect.left + scrollLeft;
const slideEnd = slideStart + slideRect.width;
if (slideStart < scrollLeft && slideEnd > scrollLeft) {
fullSldeTargetScrollPosition = slideEnd - containerWidth;
partialSlideFound = true;
break;
}
}
if (!partialSlideFound) {
fullSldeTargetScrollPosition = Math.max(0, scrollLeft - containerWidth);
}
if (fullSldeTargetScrollPosition && fullSldeTargetScrollPosition < scrollLeft) {
targetScrollPosition = fullSldeTargetScrollPosition;
}
}
}
slider.container.style.scrollBehavior = slider.options.scrollBehavior;
slider.container.scrollLeft = targetScrollPosition;
setTimeout(() => slider.container.style.scrollBehavior = '', 50);
}
function on(name, cb) {
if (!subs[name]) {
subs[name] = [];
}
subs[name].push(cb);
}
function emit(name) {
var _a;
if (subs && subs[name]) {
subs[name].forEach(cb => {
cb(slider);
});
}
const optionCallBack = (_a = slider === null || slider === void 0 ? void 0 : slider.options) === null || _a === void 0 ? void 0 : _a[name];
if (typeof optionCallBack === 'function') {
optionCallBack(slider);
}
}
slider = {
emit,
moveToDirection,
on,
options,
};
init();
return slider;
}

export { Slider as default };

0 comments on commit c349069

Please sign in to comment.