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