@evermade/overflow-slider 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,709 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.OverflowSlider = {}));
5
+ })(this, (function (exports) { 'use strict';
6
+
7
+ function details(slider) {
8
+ let instance;
9
+ let hasOverflow = false;
10
+ let slideCount = 0;
11
+ let containerWidth = 0;
12
+ let scrollableAreaWidth = 0;
13
+ let amountOfPages = 0;
14
+ let currentPage = 1;
15
+ if (slider.container.scrollWidth > slider.container.clientWidth) {
16
+ hasOverflow = true;
17
+ }
18
+ slideCount = Array.from(slider.container.querySelectorAll(':scope > *')).length;
19
+ containerWidth = slider.container.offsetWidth;
20
+ scrollableAreaWidth = slider.container.scrollWidth;
21
+ amountOfPages = Math.ceil(scrollableAreaWidth / containerWidth);
22
+ if (slider.container.scrollLeft >= 0) {
23
+ currentPage = Math.floor(slider.container.scrollLeft / containerWidth);
24
+ // consider as last page if the scrollLeft + containerWidth is equal to scrollWidth
25
+ if (slider.container.scrollLeft + containerWidth === scrollableAreaWidth) {
26
+ currentPage = amountOfPages - 1;
27
+ }
28
+ }
29
+ instance = {
30
+ hasOverflow,
31
+ slideCount,
32
+ containerWidth,
33
+ scrollableAreaWidth,
34
+ amountOfPages,
35
+ currentPage,
36
+ };
37
+ return instance;
38
+ }
39
+
40
+ function generateId(prefix, i = 1) {
41
+ const id = `${prefix}-${i}`;
42
+ if (document.getElementById(id)) {
43
+ return generateId(prefix, i + 1);
44
+ }
45
+ return id;
46
+ }
47
+ function objectsAreEqual(obj1, obj2) {
48
+ const keys1 = Object.keys(obj1);
49
+ const keys2 = Object.keys(obj2);
50
+ if (keys1.length !== keys2.length) {
51
+ return false;
52
+ }
53
+ for (let key of keys1) {
54
+ if (obj2.hasOwnProperty(key) === false || obj1[key] !== obj2[key]) {
55
+ return false;
56
+ }
57
+ }
58
+ return true;
59
+ }
60
+
61
+ function Slider(container, options, plugins) {
62
+ let slider;
63
+ let subs = {};
64
+ function init() {
65
+ slider.container = container;
66
+ // ensure container has id
67
+ let containerId = container.getAttribute('id');
68
+ if (containerId === null) {
69
+ containerId = generateId('overflow-slider');
70
+ container.setAttribute('id', containerId);
71
+ }
72
+ setDetails(true);
73
+ slider.on('contentsChanged', () => setDetails());
74
+ slider.on('containerSizeChanged', () => setDetails());
75
+ let requestId = 0;
76
+ const setDetailsDebounce = () => {
77
+ if (requestId) {
78
+ window.cancelAnimationFrame(requestId);
79
+ }
80
+ requestId = window.requestAnimationFrame(() => {
81
+ setDetails();
82
+ });
83
+ };
84
+ slider.on('scroll', setDetailsDebounce);
85
+ addEventListeners();
86
+ setDataAttributes();
87
+ setCSSVariables();
88
+ if (plugins) {
89
+ for (const plugin of plugins) {
90
+ plugin(slider);
91
+ }
92
+ }
93
+ slider.on('detailsChanged', () => {
94
+ setDataAttributes();
95
+ setCSSVariables();
96
+ });
97
+ slider.emit('created');
98
+ }
99
+ function setDetails(isInit = false) {
100
+ const oldDetails = slider.details;
101
+ const newDetails = details(slider);
102
+ slider.details = newDetails;
103
+ if (!isInit && !objectsAreEqual(oldDetails, newDetails)) {
104
+ slider.emit('detailsChanged');
105
+ }
106
+ else if (isInit) {
107
+ slider.emit('detailsChanged');
108
+ }
109
+ }
110
+ function addEventListeners() {
111
+ // changes to DOM
112
+ const observer = new MutationObserver(() => slider.emit('contentsChanged'));
113
+ observer.observe(slider.container, { childList: true });
114
+ // container size changes
115
+ const resizeObserver = new ResizeObserver(() => slider.emit('containerSizeChanged'));
116
+ resizeObserver.observe(slider.container);
117
+ // scroll event with debouncing
118
+ slider.container.addEventListener('scroll', () => slider.emit('scroll'));
119
+ // Listen for mouse down and touch start events on the document
120
+ // This handles both mouse clicks and touch interactions
121
+ let wasInteractedWith = false;
122
+ slider.container.addEventListener('mousedown', () => {
123
+ wasInteractedWith = true;
124
+ });
125
+ slider.container.addEventListener('touchstart', () => {
126
+ wasInteractedWith = true;
127
+ }, { passive: true });
128
+ slider.container.addEventListener('focusin', (e) => {
129
+ // move target parents as long as they are not the container
130
+ // but only if focus didn't start from mouse or touch
131
+ if (!wasInteractedWith) {
132
+ let target = e.target;
133
+ while (target.parentElement !== slider.container) {
134
+ if (target.parentElement) {
135
+ target = target.parentElement;
136
+ }
137
+ else {
138
+ break;
139
+ }
140
+ }
141
+ ensureSlideIsInView(target);
142
+ }
143
+ wasInteractedWith = false;
144
+ });
145
+ }
146
+ function setCSSVariables() {
147
+ slider.container.style.setProperty('--slider-container-width', `${slider.details.containerWidth}px`);
148
+ slider.container.style.setProperty('--slider-scrollable-width', `${slider.details.scrollableAreaWidth}px`);
149
+ slider.container.style.setProperty('--slider-slides-count', `${slider.details.slideCount}`);
150
+ }
151
+ function setDataAttributes() {
152
+ slider.container.setAttribute('data-has-overflow', slider.details.hasOverflow ? 'true' : 'false');
153
+ }
154
+ function ensureSlideIsInView(slide) {
155
+ const slideRect = slide.getBoundingClientRect();
156
+ const sliderRect = slider.container.getBoundingClientRect();
157
+ const containerWidth = slider.container.offsetWidth;
158
+ const scrollLeft = slider.container.scrollLeft;
159
+ const slideStart = slideRect.left - sliderRect.left + scrollLeft;
160
+ const slideEnd = slideStart + slideRect.width;
161
+ let scrollTarget = null;
162
+ if (slideStart < scrollLeft) {
163
+ scrollTarget = slideStart;
164
+ }
165
+ else if (slideEnd > scrollLeft + containerWidth) {
166
+ scrollTarget = slideEnd - containerWidth;
167
+ }
168
+ if (scrollTarget) {
169
+ slider.container.style.scrollSnapType = 'none';
170
+ slider.container.scrollLeft = scrollTarget;
171
+ // @todo resume scroll snapping but at least proximity gives a lot of trouble
172
+ // and it's not really needed for this use case but it would be nice to have
173
+ // it back in case it's needed. We need to calculate scrollLeft some other way
174
+ }
175
+ }
176
+ function moveToDirection(direction = "prev") {
177
+ const scrollStrategy = slider.options.scrollStrategy;
178
+ const scrollLeft = slider.container.scrollLeft;
179
+ const sliderRect = slider.container.getBoundingClientRect();
180
+ const containerWidth = slider.container.offsetWidth;
181
+ let targetScrollPosition = scrollLeft;
182
+ if (direction === 'prev') {
183
+ targetScrollPosition = Math.max(0, scrollLeft - slider.container.offsetWidth);
184
+ }
185
+ else if (direction === 'next') {
186
+ targetScrollPosition = Math.min(slider.container.scrollWidth, scrollLeft + slider.container.offsetWidth);
187
+ }
188
+ if (scrollStrategy === 'fullSlide') {
189
+ let fullSldeTargetScrollPosition = null;
190
+ const slides = Array.from(slider.container.querySelectorAll(':scope > *'));
191
+ let gapSize = 0;
192
+ if (slides.length > 1) {
193
+ const firstSlideRect = slides[0].getBoundingClientRect();
194
+ const secondSlideRect = slides[1].getBoundingClientRect();
195
+ gapSize = secondSlideRect.left - firstSlideRect.right;
196
+ }
197
+ // extend targetScrollPosition to include gap
198
+ if (direction === 'prev') {
199
+ fullSldeTargetScrollPosition = Math.max(0, targetScrollPosition - gapSize);
200
+ }
201
+ else {
202
+ fullSldeTargetScrollPosition = Math.min(slider.container.scrollWidth, targetScrollPosition + gapSize);
203
+ }
204
+ if (direction === 'next') {
205
+ let partialSlideFound = false;
206
+ for (let slide of slides) {
207
+ const slideRect = slide.getBoundingClientRect();
208
+ const slideStart = slideRect.left - sliderRect.left + scrollLeft;
209
+ const slideEnd = slideStart + slideRect.width;
210
+ if (slideStart < targetScrollPosition && slideEnd > targetScrollPosition) {
211
+ fullSldeTargetScrollPosition = slideStart;
212
+ partialSlideFound = true;
213
+ break;
214
+ }
215
+ }
216
+ if (!partialSlideFound) {
217
+ fullSldeTargetScrollPosition = Math.min(targetScrollPosition, slider.container.scrollWidth - slider.container.offsetWidth);
218
+ }
219
+ if (fullSldeTargetScrollPosition && fullSldeTargetScrollPosition > scrollLeft) {
220
+ targetScrollPosition = fullSldeTargetScrollPosition;
221
+ }
222
+ }
223
+ else {
224
+ let partialSlideFound = false;
225
+ for (let slide of slides) {
226
+ const slideRect = slide.getBoundingClientRect();
227
+ const slideStart = slideRect.left - sliderRect.left + scrollLeft;
228
+ const slideEnd = slideStart + slideRect.width;
229
+ if (slideStart < scrollLeft && slideEnd > scrollLeft) {
230
+ fullSldeTargetScrollPosition = slideEnd - containerWidth;
231
+ partialSlideFound = true;
232
+ break;
233
+ }
234
+ }
235
+ if (!partialSlideFound) {
236
+ fullSldeTargetScrollPosition = Math.max(0, scrollLeft - containerWidth);
237
+ }
238
+ if (fullSldeTargetScrollPosition && fullSldeTargetScrollPosition < scrollLeft) {
239
+ targetScrollPosition = fullSldeTargetScrollPosition;
240
+ }
241
+ }
242
+ }
243
+ slider.container.style.scrollBehavior = 'smooth';
244
+ slider.container.scrollLeft = targetScrollPosition;
245
+ setTimeout(() => slider.container.style.scrollBehavior = '', 50);
246
+ }
247
+ function on(name, cb) {
248
+ if (!subs[name]) {
249
+ subs[name] = [];
250
+ }
251
+ subs[name].push(cb);
252
+ }
253
+ function emit(name) {
254
+ var _a;
255
+ if (subs && subs[name]) {
256
+ subs[name].forEach(cb => {
257
+ cb(slider);
258
+ });
259
+ }
260
+ const optionCallBack = (_a = slider === null || slider === void 0 ? void 0 : slider.options) === null || _a === void 0 ? void 0 : _a[name];
261
+ if (typeof optionCallBack === 'function') {
262
+ optionCallBack(slider);
263
+ }
264
+ }
265
+ slider = {
266
+ emit,
267
+ moveToDirection,
268
+ on,
269
+ options,
270
+ };
271
+ init();
272
+ return slider;
273
+ }
274
+
275
+ function OverflowSlider(container, options, plugins) {
276
+ try {
277
+ // check that container HTML element
278
+ if (!(container instanceof Element)) {
279
+ throw new Error(`Container must be HTML element, found ${typeof container}`);
280
+ }
281
+ const defaults = {
282
+ scrollBehavior: "smooth",
283
+ scrollStrategy: "fullSlide",
284
+ };
285
+ const sliderOptions = Object.assign(Object.assign({}, defaults), options);
286
+ // disable smooth scrolling if user prefers reduced motion
287
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
288
+ sliderOptions.scrollBehavior = "auto";
289
+ }
290
+ return Slider(container, sliderOptions, plugins);
291
+ }
292
+ catch (e) {
293
+ console.error(e);
294
+ }
295
+ }
296
+
297
+ const DEFAULT_TEXTS$2 = {
298
+ skipList: 'Skip list'
299
+ };
300
+ const DEFAULT_CLASS_NAMES$3 = {
301
+ skipLink: 'screen-reader-text',
302
+ skipLinkTarget: 'overflow-slider__skip-link-target',
303
+ };
304
+ function SkipLinksPlugin(args) {
305
+ return (slider) => {
306
+ var _a, _b, _c, _d, _e, _f;
307
+ const options = {
308
+ texts: Object.assign(Object.assign({}, DEFAULT_TEXTS$2), (args === null || args === void 0 ? void 0 : args.texts) || []),
309
+ classNames: Object.assign(Object.assign({}, DEFAULT_CLASS_NAMES$3), (args === null || args === void 0 ? void 0 : args.classNames) || []),
310
+ containerBefore: (_a = args === null || args === void 0 ? void 0 : args.containerAfter) !== null && _a !== void 0 ? _a : null,
311
+ containerAfter: (_b = args === null || args === void 0 ? void 0 : args.containerAfter) !== null && _b !== void 0 ? _b : null,
312
+ };
313
+ const skipId = generateId('overflow-slider-skip');
314
+ const skipLinkEl = document.createElement('a');
315
+ skipLinkEl.setAttribute('href', `#${skipId}`);
316
+ skipLinkEl.textContent = options.texts.skipList;
317
+ skipLinkEl.classList.add(options.classNames.skipLink);
318
+ const skipTargetEl = document.createElement('div');
319
+ skipTargetEl.setAttribute('id', skipId);
320
+ skipTargetEl.setAttribute('tabindex', '-1');
321
+ if (options.containerBefore) {
322
+ (_c = options.containerBefore.parentNode) === null || _c === void 0 ? void 0 : _c.insertBefore(skipLinkEl, options.containerBefore);
323
+ }
324
+ else {
325
+ (_d = slider.container.parentNode) === null || _d === void 0 ? void 0 : _d.insertBefore(skipLinkEl, slider.container);
326
+ }
327
+ if (options.containerAfter) {
328
+ (_e = options.containerAfter.parentNode) === null || _e === void 0 ? void 0 : _e.insertBefore(skipTargetEl, options.containerAfter.nextSibling);
329
+ }
330
+ else {
331
+ (_f = slider.container.parentNode) === null || _f === void 0 ? void 0 : _f.insertBefore(skipTargetEl, slider.container.nextSibling);
332
+ }
333
+ };
334
+ }
335
+
336
+ const DEFAULT_TEXTS$1 = {
337
+ buttonPrevious: 'Previous items',
338
+ buttonNext: 'Next items',
339
+ };
340
+ const DEFAULT_ICONS = {
341
+ 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>',
342
+ 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>',
343
+ };
344
+ const DEFAULT_CLASS_NAMES$2 = {
345
+ navContainer: 'overflow-slider__arrows',
346
+ prevButton: 'overflow-slider__arrows-button overflow-slider__arrows-button--prev',
347
+ nextButton: 'overflow-slider__arrows-button overflow-slider__arrows-button--next',
348
+ };
349
+ function ArrowsPlugin(args) {
350
+ return (slider) => {
351
+ var _a, _b, _c, _d;
352
+ const options = {
353
+ texts: Object.assign(Object.assign({}, DEFAULT_TEXTS$1), (args === null || args === void 0 ? void 0 : args.texts) || []),
354
+ icons: Object.assign(Object.assign({}, DEFAULT_ICONS), (args === null || args === void 0 ? void 0 : args.icons) || []),
355
+ classNames: Object.assign(Object.assign({}, DEFAULT_CLASS_NAMES$2), (args === null || args === void 0 ? void 0 : args.classNames) || []),
356
+ container: (_a = args === null || args === void 0 ? void 0 : args.container) !== null && _a !== void 0 ? _a : null,
357
+ };
358
+ const nav = document.createElement('div');
359
+ nav.classList.add(options.classNames.navContainer);
360
+ const prev = document.createElement('button');
361
+ prev.setAttribute('class', options.classNames.prevButton);
362
+ prev.setAttribute('type', 'button');
363
+ prev.setAttribute('aria-label', options.texts.buttonPrevious);
364
+ prev.setAttribute('aria-controls', (_b = slider.container.getAttribute('id')) !== null && _b !== void 0 ? _b : '');
365
+ prev.setAttribute('data-type', 'prev');
366
+ prev.innerHTML = options.icons.prev;
367
+ prev.addEventListener('click', () => slider.moveToDirection('prev'));
368
+ const next = document.createElement('button');
369
+ next.setAttribute('class', options.classNames.nextButton);
370
+ next.setAttribute('type', 'button');
371
+ next.setAttribute('aria-label', options.texts.buttonNext);
372
+ next.setAttribute('aria-controls', (_c = slider.container.getAttribute('id')) !== null && _c !== void 0 ? _c : '');
373
+ next.setAttribute('data-type', 'next');
374
+ next.innerHTML = options.icons.next;
375
+ next.addEventListener('click', () => slider.moveToDirection('next'));
376
+ // insert buttons to the nav
377
+ nav.appendChild(prev);
378
+ nav.appendChild(next);
379
+ const update = () => {
380
+ const scrollLeft = slider.container.scrollLeft;
381
+ const scrollWidth = slider.container.scrollWidth;
382
+ const clientWidth = slider.container.clientWidth;
383
+ if (scrollLeft === 0) {
384
+ prev.setAttribute('data-has-content', 'false');
385
+ }
386
+ else {
387
+ prev.setAttribute('data-has-content', 'true');
388
+ }
389
+ if (scrollLeft + clientWidth >= scrollWidth) {
390
+ next.setAttribute('data-has-content', 'false');
391
+ }
392
+ else {
393
+ next.setAttribute('data-has-content', 'true');
394
+ }
395
+ };
396
+ if (options.container) {
397
+ options.container.appendChild(nav);
398
+ }
399
+ else {
400
+ (_d = slider.container.parentNode) === null || _d === void 0 ? void 0 : _d.insertBefore(nav, slider.container.nextSibling);
401
+ }
402
+ update();
403
+ slider.on('scroll', update);
404
+ slider.on('contentsChanged', update);
405
+ slider.on('containerSizeChanged', update);
406
+ };
407
+ }
408
+
409
+ const DEFAULT_CLASS_NAMES$1 = {
410
+ scrollIndicator: 'overflow-slider__scroll-indicator',
411
+ scrollIndicatorBar: 'overflow-slider__scroll-indicator-bar',
412
+ scrollIndicatorButton: 'overflow-slider__scroll-indicator-button',
413
+ };
414
+ function ScrollIndicatorPlugin(args) {
415
+ return (slider) => {
416
+ var _a, _b, _c;
417
+ const options = {
418
+ classNames: Object.assign(Object.assign({}, DEFAULT_CLASS_NAMES$1), (args === null || args === void 0 ? void 0 : args.classNames) || []),
419
+ container: (_a = args === null || args === void 0 ? void 0 : args.container) !== null && _a !== void 0 ? _a : null,
420
+ };
421
+ const scrollbarContainer = document.createElement('div');
422
+ scrollbarContainer.setAttribute('class', options.classNames.scrollIndicator);
423
+ scrollbarContainer.setAttribute('tabindex', '0');
424
+ scrollbarContainer.setAttribute('role', 'scrollbar');
425
+ scrollbarContainer.setAttribute('aria-controls', (_b = slider.container.getAttribute('id')) !== null && _b !== void 0 ? _b : '');
426
+ scrollbarContainer.setAttribute('aria-orientation', 'horizontal');
427
+ scrollbarContainer.setAttribute('aria-valuemax', '100');
428
+ scrollbarContainer.setAttribute('aria-valuemin', '0');
429
+ scrollbarContainer.setAttribute('aria-valuenow', '0');
430
+ const scrollbar = document.createElement('div');
431
+ scrollbar.setAttribute('class', options.classNames.scrollIndicatorBar);
432
+ const scrollbarButton = document.createElement('div');
433
+ scrollbarButton.setAttribute('class', options.classNames.scrollIndicatorButton);
434
+ scrollbarButton.setAttribute('data-is-grabbed', 'false');
435
+ scrollbar.appendChild(scrollbarButton);
436
+ scrollbarContainer.appendChild(scrollbar);
437
+ const setDataAttributes = () => {
438
+ scrollbarContainer.setAttribute('data-has-overflow', slider.details.hasOverflow.toString());
439
+ };
440
+ setDataAttributes();
441
+ const getScrollbarButtonLeftOffset = () => {
442
+ const scrollbarRatio = slider.container.offsetWidth / slider.container.scrollWidth;
443
+ return slider.container.scrollLeft * scrollbarRatio;
444
+ };
445
+ // scrollbarbutton width and position is calculated based on the scroll position and available width
446
+ let requestId = 0;
447
+ const update = () => {
448
+ if (requestId) {
449
+ window.cancelAnimationFrame(requestId);
450
+ }
451
+ requestId = window.requestAnimationFrame(() => {
452
+ const scrollbarButtonWidth = (slider.container.offsetWidth / slider.container.scrollWidth) * 100;
453
+ const scrollLeftInPortion = getScrollbarButtonLeftOffset();
454
+ scrollbarButton.style.width = `${scrollbarButtonWidth}%`;
455
+ scrollbarButton.style.transform = `translateX(${scrollLeftInPortion}px)`;
456
+ // aria-valuenow
457
+ const scrollLeft = slider.container.scrollLeft;
458
+ const scrollWidth = slider.container.scrollWidth;
459
+ const containerWidth = slider.container.offsetWidth;
460
+ const scrollPercentage = (scrollLeft / (scrollWidth - containerWidth)) * 100;
461
+ scrollbarContainer.setAttribute('aria-valuenow', Math.round(Number.isNaN(scrollPercentage) ? 0 : scrollPercentage).toString());
462
+ });
463
+ };
464
+ // insert to DOM
465
+ if (options.container) {
466
+ options.container.appendChild(scrollbarContainer);
467
+ }
468
+ else {
469
+ (_c = slider.container.parentNode) === null || _c === void 0 ? void 0 : _c.insertBefore(scrollbarContainer, slider.container.nextSibling);
470
+ }
471
+ // update the scrollbar when the slider is scrolled
472
+ update();
473
+ slider.on('scroll', update);
474
+ slider.on('contentsChanged', update);
475
+ slider.on('containerSizeChanged', update);
476
+ slider.on('detailsChanged', setDataAttributes);
477
+ // handle arrow keys while focused
478
+ scrollbarContainer.addEventListener('keydown', (e) => {
479
+ if (e.key === 'ArrowLeft') {
480
+ slider.moveToDirection('prev');
481
+ }
482
+ else if (e.key === 'ArrowRight') {
483
+ slider.moveToDirection('next');
484
+ }
485
+ });
486
+ // handle click to before or after the scrollbar button
487
+ scrollbarContainer.addEventListener('click', (e) => {
488
+ const scrollbarButtonWidth = scrollbarButton.offsetWidth;
489
+ const scrollbarButtonLeft = getScrollbarButtonLeftOffset();
490
+ const scrollbarButtonRight = scrollbarButtonLeft + scrollbarButtonWidth;
491
+ const clickX = e.pageX - scrollbarContainer.offsetLeft;
492
+ if (clickX < scrollbarButtonLeft) {
493
+ slider.moveToDirection('prev');
494
+ }
495
+ else if (clickX > scrollbarButtonRight) {
496
+ slider.moveToDirection('next');
497
+ }
498
+ });
499
+ // make scrollbar button draggable via mouse/touch and update the scroll position
500
+ let isMouseDown = false;
501
+ let startX = 0;
502
+ let scrollLeft = 0;
503
+ scrollbarButton.addEventListener('mousedown', (e) => {
504
+ isMouseDown = true;
505
+ startX = e.pageX - scrollbarContainer.offsetLeft;
506
+ scrollLeft = slider.container.scrollLeft;
507
+ // change cursor to grabbing
508
+ scrollbarButton.style.cursor = 'grabbing';
509
+ slider.container.style.scrollSnapType = 'none';
510
+ scrollbarButton.setAttribute('data-is-grabbed', 'true');
511
+ e.preventDefault();
512
+ e.stopPropagation();
513
+ });
514
+ window.addEventListener('mouseup', () => {
515
+ isMouseDown = false;
516
+ scrollbarButton.style.cursor = '';
517
+ slider.container.style.scrollSnapType = '';
518
+ scrollbarButton.setAttribute('data-is-grabbed', 'false');
519
+ });
520
+ window.addEventListener('mousemove', (e) => {
521
+ if (!isMouseDown) {
522
+ return;
523
+ }
524
+ e.preventDefault();
525
+ const x = e.pageX - scrollbarContainer.offsetLeft;
526
+ const scrollingFactor = slider.container.scrollWidth / scrollbarContainer.offsetWidth;
527
+ const walk = (x - startX) * scrollingFactor;
528
+ slider.container.scrollLeft = scrollLeft + walk;
529
+ });
530
+ };
531
+ }
532
+
533
+ const DEFAULT_DRAGGED_DISTANCE_THAT_PREVENTS_CLICK = 20;
534
+ function DragScrollingPlugin(args) {
535
+ var _a;
536
+ const options = {
537
+ draggedDistanceThatPreventsClick: (_a = args === null || args === void 0 ? void 0 : args.draggedDistanceThatPreventsClick) !== null && _a !== void 0 ? _a : DEFAULT_DRAGGED_DISTANCE_THAT_PREVENTS_CLICK,
538
+ };
539
+ return (slider) => {
540
+ let isMouseDown = false;
541
+ let startX = 0;
542
+ let scrollLeft = 0;
543
+ const hasOverflow = () => {
544
+ return slider.details.hasOverflow;
545
+ };
546
+ slider.container.addEventListener('mousedown', (e) => {
547
+ if (!hasOverflow()) {
548
+ return;
549
+ }
550
+ isMouseDown = true;
551
+ startX = e.pageX - slider.container.offsetLeft;
552
+ scrollLeft = slider.container.scrollLeft;
553
+ // change cursor to grabbing
554
+ slider.container.style.cursor = 'grabbing';
555
+ slider.container.style.scrollSnapType = 'none';
556
+ // prevent pointer events on the slides
557
+ // const slides = slider.container.querySelectorAll( ':scope > *' );
558
+ // slides.forEach((slide) => {
559
+ // (<HTMLElement>slide).style.pointerEvents = 'none';
560
+ // });
561
+ // prevent focus going to the slides
562
+ // e.preventDefault();
563
+ // e.stopPropagation();
564
+ });
565
+ window.addEventListener('mouseup', () => {
566
+ if (!hasOverflow()) {
567
+ return;
568
+ }
569
+ isMouseDown = false;
570
+ slider.container.style.cursor = '';
571
+ slider.container.style.scrollSnapType = '';
572
+ setTimeout(() => {
573
+ const slides = slider.container.querySelectorAll(':scope > *');
574
+ slides.forEach((slide) => {
575
+ slide.style.pointerEvents = '';
576
+ });
577
+ }, 50);
578
+ });
579
+ window.addEventListener('mousemove', (e) => {
580
+ if (!hasOverflow()) {
581
+ return;
582
+ }
583
+ if (!isMouseDown) {
584
+ return;
585
+ }
586
+ e.preventDefault();
587
+ const x = e.pageX - slider.container.offsetLeft;
588
+ const walk = (x - startX);
589
+ slider.container.scrollLeft = scrollLeft - walk;
590
+ // if walk is more than 30px, don't allow click event
591
+ // e.preventDefault();
592
+ const absWalk = Math.abs(walk);
593
+ const slides = slider.container.querySelectorAll(':scope > *');
594
+ const pointerEvents = absWalk > options.draggedDistanceThatPreventsClick ? 'none' : '';
595
+ slides.forEach((slide) => {
596
+ slide.style.pointerEvents = pointerEvents;
597
+ });
598
+ });
599
+ };
600
+ }
601
+
602
+ const DEFAULT_TEXTS = {
603
+ dotDescription: 'Page %d of %d',
604
+ };
605
+ const DEFAULT_CLASS_NAMES = {
606
+ dotsContainer: 'overflow-slider__dots',
607
+ dotsItem: 'overflow-slider__dot-item',
608
+ };
609
+ function DotsPlugin(args) {
610
+ return (slider) => {
611
+ var _a, _b;
612
+ const options = {
613
+ texts: Object.assign(Object.assign({}, DEFAULT_TEXTS), (args === null || args === void 0 ? void 0 : args.texts) || []),
614
+ classNames: Object.assign(Object.assign({}, DEFAULT_CLASS_NAMES), (args === null || args === void 0 ? void 0 : args.classNames) || []),
615
+ container: (_a = args === null || args === void 0 ? void 0 : args.container) !== null && _a !== void 0 ? _a : null,
616
+ };
617
+ const dots = document.createElement('div');
618
+ dots.classList.add(options.classNames.dotsContainer);
619
+ let pageFocused = null;
620
+ const buildDots = () => {
621
+ dots.setAttribute('data-has-content', slider.details.hasOverflow.toString());
622
+ dots.innerHTML = '';
623
+ const dotsList = document.createElement('ul');
624
+ const pages = slider.details.amountOfPages;
625
+ const currentPage = slider.details.currentPage;
626
+ if (pages <= 1) {
627
+ return;
628
+ }
629
+ for (let i = 0; i < pages; i++) {
630
+ const dotListItem = document.createElement('li');
631
+ const dot = document.createElement('button');
632
+ dot.setAttribute('type', 'button');
633
+ dot.setAttribute('class', options.classNames.dotsItem);
634
+ dot.setAttribute('aria-label', options.texts.dotDescription.replace('%d', (i + 1).toString()).replace('%d', pages.toString()));
635
+ dot.setAttribute('aria-pressed', (i === currentPage).toString());
636
+ dot.setAttribute('data-page', (i + 1).toString());
637
+ dotListItem.appendChild(dot);
638
+ dotsList.appendChild(dotListItem);
639
+ dot.addEventListener('click', () => activateDot(i + 1));
640
+ dot.addEventListener('focus', () => pageFocused = i + 1);
641
+ dot.addEventListener('keydown', (e) => {
642
+ var _a;
643
+ const currentPageItem = dots.querySelector(`[aria-pressed="true"]`);
644
+ if (!currentPageItem) {
645
+ return;
646
+ }
647
+ const currentPage = parseInt((_a = currentPageItem.getAttribute('data-page')) !== null && _a !== void 0 ? _a : '1');
648
+ if (e.key === 'ArrowLeft') {
649
+ const previousPage = currentPage - 1;
650
+ if (previousPage > 0) {
651
+ const matchingDot = dots.querySelector(`[data-page="${previousPage}"]`);
652
+ if (matchingDot) {
653
+ matchingDot.focus();
654
+ }
655
+ activateDot(previousPage);
656
+ }
657
+ }
658
+ if (e.key === 'ArrowRight') {
659
+ const nextPage = currentPage + 1;
660
+ if (nextPage <= pages) {
661
+ const matchingDot = dots.querySelector(`[data-page="${nextPage}"]`);
662
+ if (matchingDot) {
663
+ matchingDot.focus();
664
+ }
665
+ activateDot(nextPage);
666
+ }
667
+ }
668
+ });
669
+ }
670
+ dots.appendChild(dotsList);
671
+ // return focus to same page after rebuild
672
+ if (pageFocused) {
673
+ const matchingDot = dots.querySelector(`[data-page="${pageFocused}"]`);
674
+ if (matchingDot) {
675
+ matchingDot.focus();
676
+ }
677
+ }
678
+ };
679
+ const activateDot = (page) => {
680
+ const scrollTargetPosition = slider.details.containerWidth * (page - 1);
681
+ slider.container.style.scrollBehavior = slider.options.scrollBehavior;
682
+ slider.container.style.scrollSnapType = 'none';
683
+ slider.container.scrollLeft = scrollTargetPosition;
684
+ slider.container.style.scrollBehavior = '';
685
+ slider.container.style.scrollSnapType = '';
686
+ };
687
+ buildDots();
688
+ if (options.container) {
689
+ options.container.appendChild(dots);
690
+ }
691
+ else {
692
+ (_b = slider.container.parentNode) === null || _b === void 0 ? void 0 : _b.insertBefore(dots, slider.container.nextSibling);
693
+ }
694
+ slider.on('detailsChanged', () => {
695
+ buildDots();
696
+ });
697
+ };
698
+ }
699
+
700
+ exports.ArrowsPlugin = ArrowsPlugin;
701
+ exports.DotsPlugin = DotsPlugin;
702
+ exports.DragScrollingPlugin = DragScrollingPlugin;
703
+ exports.OverflowSlider = OverflowSlider;
704
+ exports.ScrollIndicatorPlugin = ScrollIndicatorPlugin;
705
+ exports.SkipLinksPlugin = SkipLinksPlugin;
706
+
707
+ Object.defineProperty(exports, '__esModule', { value: true });
708
+
709
+ }));