@evermade/overflow-slider 3.1.0 → 3.2.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.
Files changed (53) hide show
  1. package/README.md +59 -6
  2. package/dist/core/details.esm.js +3 -3
  3. package/dist/core/details.min.js +1 -1
  4. package/dist/core/overflow-slider.esm.js +1 -0
  5. package/dist/core/overflow-slider.min.js +1 -1
  6. package/dist/core/slider.esm.js +66 -20
  7. package/dist/core/slider.min.js +1 -1
  8. package/dist/overflow-slider.css +1 -1
  9. package/dist/plugins/arrows/arrows/index.esm.js +7 -4
  10. package/dist/plugins/arrows/arrows/index.min.js +1 -1
  11. package/dist/plugins/dots/dots/index.esm.js +0 -4
  12. package/dist/plugins/fade/fade/index.esm.js +4 -4
  13. package/dist/plugins/fade/fade/index.min.js +1 -1
  14. package/dist/plugins/full-width/full-width/index.esm.js +7 -2
  15. package/dist/plugins/full-width/full-width/index.min.js +1 -1
  16. package/dist/plugins/scroll-indicator/scroll-indicator/index.esm.js +18 -18
  17. package/dist/plugins/scroll-indicator/scroll-indicator/index.min.js +1 -1
  18. package/dist/plugins/thumbnails/thumbnails/index.esm.js +8 -5
  19. package/dist/plugins/thumbnails/thumbnails/index.min.js +1 -1
  20. package/docs/assets/demo.css +11 -1
  21. package/docs/assets/demo.js +38 -12
  22. package/docs/dist/core/details.esm.js +3 -3
  23. package/docs/dist/core/details.min.js +1 -1
  24. package/docs/dist/core/overflow-slider.esm.js +1 -0
  25. package/docs/dist/core/overflow-slider.min.js +1 -1
  26. package/docs/dist/core/slider.esm.js +66 -20
  27. package/docs/dist/core/slider.min.js +1 -1
  28. package/docs/dist/overflow-slider.css +1 -1
  29. package/docs/dist/plugins/arrows/arrows/index.esm.js +7 -4
  30. package/docs/dist/plugins/arrows/arrows/index.min.js +1 -1
  31. package/docs/dist/plugins/dots/dots/index.esm.js +0 -4
  32. package/docs/dist/plugins/fade/fade/index.esm.js +4 -4
  33. package/docs/dist/plugins/fade/fade/index.min.js +1 -1
  34. package/docs/dist/plugins/full-width/full-width/index.esm.js +7 -2
  35. package/docs/dist/plugins/full-width/full-width/index.min.js +1 -1
  36. package/docs/dist/plugins/scroll-indicator/scroll-indicator/index.esm.js +18 -18
  37. package/docs/dist/plugins/scroll-indicator/scroll-indicator/index.min.js +1 -1
  38. package/docs/dist/plugins/thumbnails/thumbnails/index.esm.js +8 -5
  39. package/docs/dist/plugins/thumbnails/thumbnails/index.min.js +1 -1
  40. package/docs/index-rtl.html +396 -0
  41. package/docs/index.html +1 -1
  42. package/package.json +1 -1
  43. package/src/core/details.ts +3 -3
  44. package/src/core/overflow-slider.ts +1 -0
  45. package/src/core/slider.ts +71 -21
  46. package/src/core/types.ts +3 -0
  47. package/src/plugins/arrows/index.ts +7 -5
  48. package/src/plugins/dots/index.ts +0 -4
  49. package/src/plugins/fade/index.ts +4 -4
  50. package/src/plugins/fade/styles.scss +10 -0
  51. package/src/plugins/full-width/index.ts +8 -2
  52. package/src/plugins/scroll-indicator/index.ts +60 -62
  53. package/src/plugins/thumbnails/index.ts +8 -5
@@ -23,10 +23,10 @@ export default function details( slider: Slider) {
23
23
 
24
24
  amountOfPages = Math.ceil(scrollableAreaWidth / containerWidth);
25
25
 
26
- if ( Math.floor( slider.container.scrollLeft ) >= 0) {
27
- currentPage = Math.floor(slider.container.scrollLeft / containerWidth);
26
+ if ( Math.floor( slider.getScrollLeft() ) >= 0) {
27
+ currentPage = Math.floor(slider.getScrollLeft() / containerWidth);
28
28
  // consider as last page if the scrollLeft + containerWidth is equal to scrollWidth
29
- if (Math.floor( slider.container.scrollLeft + containerWidth ) === Math.floor( scrollableAreaWidth ) ) {
29
+ if (Math.floor( slider.getScrollLeft() + containerWidth ) === Math.floor( scrollableAreaWidth ) ) {
30
30
  currentPage = amountOfPages - 1;
31
31
  }
32
32
  }
@@ -23,6 +23,7 @@ export default function OverflowSlider (
23
23
  slidesSelector: ":scope > *",
24
24
  emulateScrollSnap: false,
25
25
  emulateScrollSnapMaxThreshold: 64,
26
+ rtl: false,
26
27
  };
27
28
 
28
29
  const sliderOptions = { ...defaults, ...options };
@@ -207,6 +207,9 @@ export default function Slider( container: HTMLElement, options : SliderOptions,
207
207
 
208
208
  function setDataAttributes() {
209
209
  slider.container.setAttribute('data-has-overflow', slider.details.hasOverflow ? 'true' : 'false');
210
+ if ( slider.options.rtl ) {
211
+ slider.container.setAttribute('dir', 'rtl');
212
+ }
210
213
  }
211
214
 
212
215
  function ensureSlideIsInView( slide: HTMLElement, scrollBehavior: null|ScrollBehavior = null) {
@@ -237,24 +240,51 @@ export default function Slider( container: HTMLElement, options : SliderOptions,
237
240
 
238
241
  function setActiveSlideIdx() {
239
242
  const sliderRect = slider.container.getBoundingClientRect();
240
- const scrollLeft = slider.container.scrollLeft;
243
+ const scrollLeft = slider.getScrollLeft();
241
244
  const slides = slider.slides;
242
245
  let activeSlideIdx = 0;
243
246
  let scrolledPastLastSlide = false;
244
247
 
245
- for (let i = 0; i < slides.length; i++) {
246
- const slideRect = slides[i].getBoundingClientRect();
247
- const slideStart = slideRect.left - sliderRect.left + scrollLeft + getGapSize();
248
-
249
- if ( Math.floor( slideStart ) >= Math.floor( scrollLeft ) ) {
250
- activeSlideIdx = i;
251
- break;
248
+ if (slider.options.rtl) {
249
+ const scrolledDistance = slider.getInclusiveScrollWidth() - scrollLeft - slider.getInclusiveClientWidth();
250
+ const slidePositions = [];
251
+ for (let i = slides.length - 1; i >= 0; i--) {
252
+ const slideRect = slides[i].getBoundingClientRect();
253
+ const slideEnd = Math.abs(slideRect.left) - Math.abs(sliderRect.left) + scrolledDistance;
254
+ slidePositions.push({
255
+ slide: slides[i],
256
+ slideEnd: slideEnd,
257
+ });
258
+ }
259
+ let closestSlide = null;
260
+ let closestDistance = null;
261
+ for (let i = 0; i < slidePositions.length; i++) {
262
+ const distance = Math.abs(slidePositions[i].slideEnd - scrolledDistance);
263
+ if (closestDistance === null || distance < closestDistance) {
264
+ closestDistance = distance;
265
+ closestSlide = slidePositions[i].slide;
266
+ }
252
267
  }
253
- if ( i === slides.length - 1 ) {
254
- scrolledPastLastSlide = true;
268
+ if (closestSlide) {
269
+ activeSlideIdx = slides.indexOf(closestSlide);
270
+ } else {
271
+ activeSlideIdx = slides.length - 1;
272
+ }
273
+ } else {
274
+ for (let i = 0; i < slides.length; i++) {
275
+ const slideRect = slides[i].getBoundingClientRect();
276
+ const slideStart = slideRect.left - sliderRect.left + scrollLeft + getGapSize();
277
+ if (Math.floor(slideStart) >= Math.floor(scrollLeft)) {
278
+ activeSlideIdx = i;
279
+ break;
280
+ }
281
+ if ( i === slides.length - 1 ) {
282
+ scrolledPastLastSlide = true;
283
+ }
255
284
  }
256
285
  }
257
286
 
287
+
258
288
  if ( scrolledPastLastSlide ) {
259
289
  activeSlideIdx = slides.length - 1;
260
290
  }
@@ -262,6 +292,8 @@ export default function Slider( container: HTMLElement, options : SliderOptions,
262
292
  const oldActiveSlideIdx = slider.activeSlideIdx;
263
293
  slider.activeSlideIdx = activeSlideIdx;
264
294
 
295
+ // console.log('activeSlideIdx', activeSlideIdx);
296
+
265
297
  if (oldActiveSlideIdx !== activeSlideIdx) {
266
298
  slider.emit('activeSlideChanged');
267
299
  }
@@ -283,12 +315,20 @@ export default function Slider( container: HTMLElement, options : SliderOptions,
283
315
  return slider.container.clientWidth + getOutermostChildrenEdgeMarginSum(slider.container);
284
316
  }
285
317
 
318
+ function getScrollLeft() : number {
319
+ return slider.options.rtl ? Math.abs(slider.container.scrollLeft) : slider.container.scrollLeft;
320
+ };
321
+
322
+ function setScrollLeft(value: number) : void {
323
+ slider.container.scrollLeft = slider.options.rtl ? -value : value;
324
+ };
325
+
286
326
  function getGapSize() : number {
287
327
  let gapSize = 0;
288
328
  if (slider.slides.length > 1) {
289
329
  const firstSlideRect = slider.slides[0].getBoundingClientRect();
290
330
  const secondSlideRect = slider.slides[1].getBoundingClientRect();
291
- gapSize = Math.floor( secondSlideRect.left - firstSlideRect.right );
331
+ gapSize = slider.options.rtl ? Math.abs( Math.floor( secondSlideRect.right - firstSlideRect.left ) ) : Math.floor( secondSlideRect.left - firstSlideRect.right );
292
332
  }
293
333
  return gapSize;
294
334
  };
@@ -308,22 +348,25 @@ export default function Slider( container: HTMLElement, options : SliderOptions,
308
348
  const sliderRect = slider.container.getBoundingClientRect();
309
349
  const containerWidth = slider.container.offsetWidth;
310
350
  let targetScrollPosition = scrollLeft;
311
- if (direction === 'prev') {
351
+
352
+ const realDirection = slider.options.rtl ? (direction === 'prev' ? 'next' : 'prev') : direction;
353
+
354
+ if (realDirection === 'prev') {
312
355
  targetScrollPosition = Math.max(0, scrollLeft - slider.container.offsetWidth);
313
- } else if (direction === 'next') {
356
+ } else if (realDirection === 'next') {
314
357
  targetScrollPosition = Math.min(slider.getInclusiveScrollWidth(), scrollLeft + slider.container.offsetWidth);
315
358
  }
316
359
  if (scrollStrategy === 'fullSlide') {
317
360
  let fullSlideTargetScrollPosition = null;
318
361
 
319
362
  // extend targetScrollPosition to include gap
320
- if (direction === 'prev') {
363
+ if (realDirection === 'prev') {
321
364
  fullSlideTargetScrollPosition = Math.max(0, targetScrollPosition - getGapSize());
322
365
  } else {
323
366
  fullSlideTargetScrollPosition = Math.min(slider.getInclusiveScrollWidth(), targetScrollPosition + getGapSize());
324
367
  }
325
368
 
326
- if (direction === 'next') {
369
+ if (realDirection === 'next') {
327
370
  let partialSlideFound = false;
328
371
  for (let slide of slider.slides) {
329
372
  const slideRect = slide.getBoundingClientRect();
@@ -383,12 +426,12 @@ export default function Slider( container: HTMLElement, options : SliderOptions,
383
426
  };
384
427
 
385
428
  function snapToClosestSlide(direction = "prev") {
386
- const isMovingForward = direction === 'next';
429
+ const isMovingForward = slider.options.rtl ? direction === 'prev' : direction === 'next';
387
430
  const slideReference = [];
388
431
  for (let i = 0; i < slider.slides.length; i++) {
389
432
  const slide = slider.slides[i];
390
433
  const slideWidth = slide.offsetWidth;
391
- const slideStart = slide.offsetLeft;
434
+ const slideStart = slider.options.rtl ? Math.abs( slide.offsetLeft + slideWidth - slider.details.containerWidth ) : slide.offsetLeft;
392
435
  const slideEnd = slideStart + slideWidth;
393
436
  const slideMiddle = slideStart + slideWidth / 2;
394
437
  const trigger = Math.min(slideMiddle, slideStart + slider.options.emulateScrollSnapMaxThreshold);
@@ -399,10 +442,14 @@ export default function Slider( container: HTMLElement, options : SliderOptions,
399
442
  width: slideWidth,
400
443
  trigger: trigger,
401
444
  slide: slide,
445
+ // debug
446
+ offSetleft: slide.offsetLeft,
447
+ rect: slide.getBoundingClientRect(),
402
448
  });
403
449
  }
450
+ console.log('slideReference', slideReference);
404
451
  let snapTarget = null;
405
- const scrollPosition = slider.container.scrollLeft;
452
+ const scrollPosition = getScrollLeft();
406
453
  if (isMovingForward) {
407
454
  for (let i = 0; i < slideReference.length; i++) {
408
455
  const item = slideReference[i];
@@ -410,7 +457,7 @@ export default function Slider( container: HTMLElement, options : SliderOptions,
410
457
  snapTarget = 0;
411
458
  break;
412
459
  }
413
- if ( Math.floor( slider.container.scrollLeft ) <= Math.floor( item.trigger ) ) {
460
+ if ( Math.floor( getScrollLeft() ) <= Math.floor( item.trigger ) ) {
414
461
  snapTarget = item.start;
415
462
  break;
416
463
  }
@@ -422,7 +469,7 @@ export default function Slider( container: HTMLElement, options : SliderOptions,
422
469
  snapTarget = item.start;
423
470
  break;
424
471
  }
425
- if ( Math.floor( slider.container.scrollLeft ) >= Math.floor( item.trigger ) ) {
472
+ if ( Math.floor( getScrollLeft() ) >= Math.floor( item.trigger ) ) {
426
473
  snapTarget = item.start;
427
474
  break;
428
475
  }
@@ -435,8 +482,9 @@ export default function Slider( container: HTMLElement, options : SliderOptions,
435
482
  }
436
483
 
437
484
  const scrollBehavior = slider.options.scrollBehavior || 'smooth';
485
+
438
486
  slider.container.scrollTo({
439
- left: snapTarget,
487
+ left: slider.options.rtl ? -snapTarget : snapTarget,
440
488
  behavior: scrollBehavior as ScrollBehavior
441
489
  });
442
490
  }
@@ -468,6 +516,8 @@ export default function Slider( container: HTMLElement, options : SliderOptions,
468
516
  snapToClosestSlide,
469
517
  getInclusiveScrollWidth,
470
518
  getInclusiveClientWidth,
519
+ getScrollLeft,
520
+ setScrollLeft,
471
521
  on,
472
522
  options,
473
523
  };
package/src/core/types.ts CHANGED
@@ -13,6 +13,8 @@ export type Slider<O = {}, C = {}, H extends string = string> = {
13
13
  ) => void
14
14
  getInclusiveScrollWidth: () => number
15
15
  getInclusiveClientWidth: () => number
16
+ getScrollLeft: () => number
17
+ setScrollLeft: (value: number) => void
16
18
  on: (
17
19
  name: H | SliderHooks,
18
20
  cb: (props: Slider<O, C, H>) => void
@@ -28,6 +30,7 @@ export type SliderOptions = {
28
30
  slidesSelector: string;
29
31
  emulateScrollSnap: boolean;
30
32
  emulateScrollSnapMaxThreshold: number;
33
+ rtl: boolean;
31
34
  [key: string]: any;
32
35
  }
33
36
 
@@ -65,7 +65,7 @@ export default function ArrowsPlugin( args: { [key: string]: any } ) {
65
65
  prev.setAttribute( 'aria-label', options.texts.buttonPrevious );
66
66
  prev.setAttribute( 'aria-controls', slider.container.getAttribute( 'id' ) ?? '');
67
67
  prev.setAttribute( 'data-type', 'prev' );
68
- prev.innerHTML = options.icons.prev;
68
+ prev.innerHTML = slider.options.rtl ? options.icons.next : options.icons.prev;
69
69
  prev.addEventListener( 'click', () => {
70
70
  if ( prev.getAttribute('data-has-content') === 'true' ) {
71
71
  slider.moveToDirection( 'prev' );
@@ -78,7 +78,7 @@ export default function ArrowsPlugin( args: { [key: string]: any } ) {
78
78
  next.setAttribute( 'aria-label', options.texts.buttonNext );
79
79
  next.setAttribute( 'aria-controls', slider.container.getAttribute( 'id' ) ?? '');
80
80
  next.setAttribute( 'data-type', 'next' );
81
- next.innerHTML = options.icons.next;
81
+ next.innerHTML = slider.options.rtl ? options.icons.prev : options.icons.next;
82
82
  next.addEventListener( 'click', () => {
83
83
  if ( next.getAttribute('data-has-content') === 'true' ) {
84
84
  slider.moveToDirection( 'next' );
@@ -90,20 +90,22 @@ export default function ArrowsPlugin( args: { [key: string]: any } ) {
90
90
  nav.appendChild( next );
91
91
 
92
92
  const update = () => {
93
- const scrollLeft = slider.container.scrollLeft;
93
+ const scrollLeft = slider.getScrollLeft();
94
94
  const scrollWidth = slider.getInclusiveScrollWidth();
95
95
  const clientWidth = slider.getInclusiveClientWidth();
96
- const buffer = 0;
96
+ const buffer = 1;
97
97
  if ( Math.floor( scrollLeft ) === 0 ) {
98
98
  prev.setAttribute( 'data-has-content', 'false' );
99
99
  } else {
100
100
  prev.setAttribute( 'data-has-content', 'true' );
101
101
  }
102
- if ( Math.floor( scrollLeft + clientWidth ) >= Math.floor( scrollWidth ) ) {
102
+ const maxWidthDifference = Math.abs( Math.floor( scrollLeft + clientWidth ) - Math.floor( scrollWidth ) );
103
+ if ( maxWidthDifference <= buffer ) {
103
104
  next.setAttribute( 'data-has-content', 'false' );
104
105
  } else {
105
106
  next.setAttribute( 'data-has-content', 'true' );
106
107
  }
108
+ console.log( 'next', scrollLeft + clientWidth, scrollWidth );
107
109
  };
108
110
 
109
111
  if ( options.containerNext && options.containerPrev ) {
@@ -105,10 +105,6 @@ export default function DotsPlugin( args: { [key: string]: any } ) {
105
105
  };
106
106
 
107
107
  const activateDot = ( item: number ) => {
108
- // const scrollTargetPosition = slider.details.containerWidth * ( page - 1 );
109
- // slider.container.style.scrollBehavior = slider.options.scrollBehavior;
110
- // slider.container.scrollLeft = scrollTargetPosition;
111
- // slider.container.style.scrollBehavior = '';
112
108
  slider.moveToSlide( item - 1 );
113
109
  };
114
110
 
@@ -48,11 +48,11 @@ export default function FadePlugin( args: { [key: string]: any } ) {
48
48
  }
49
49
 
50
50
  const hasFadeAtStart = () => {
51
- return slider.container.scrollLeft > fadeItemStart.offsetWidth;
51
+ return slider.getScrollLeft() > fadeItemStart.offsetWidth;
52
52
  }
53
53
 
54
54
  const fadeAtStartOpacity = () => {
55
- const position = slider.container.scrollLeft;
55
+ const position = slider.getScrollLeft();
56
56
  if ( Math.floor( position ) <= Math.floor( fadeItemStart.offsetWidth ) ) {
57
57
  return position / Math.max(fadeItemStart.offsetWidth, 1);
58
58
  }
@@ -60,11 +60,11 @@ export default function FadePlugin( args: { [key: string]: any } ) {
60
60
  }
61
61
 
62
62
  const hasFadeAtEnd = () => {
63
- return Math.floor( slider.container.scrollLeft ) < Math.floor( slider.getInclusiveScrollWidth() - slider.getInclusiveClientWidth() - fadeItemEnd.offsetWidth );
63
+ return Math.floor( slider.getScrollLeft() ) < Math.floor( slider.getInclusiveScrollWidth() - slider.getInclusiveClientWidth() - fadeItemEnd.offsetWidth );
64
64
  }
65
65
 
66
66
  const fadeAtEndOpacity = () => {
67
- const position = slider.container.scrollLeft;
67
+ const position = slider.getScrollLeft();
68
68
  const maxPosition = slider.getInclusiveScrollWidth() - slider.getInclusiveClientWidth();
69
69
  const maxFadePosition = maxPosition - fadeItemEnd.offsetWidth;
70
70
  if ( Math.floor( position ) >= Math.floor( maxFadePosition ) ) {
@@ -19,9 +19,19 @@
19
19
  .overflow-slider-fade--start {
20
20
  left: 0;
21
21
  background: linear-gradient(to right, var(--overflow-slider-fade-color) 0%, transparent 100%);
22
+ [dir="rtl"] & {
23
+ left: auto;
24
+ right: 0;
25
+ background: linear-gradient(to left, var(--overflow-slider-fade-color) 0%, transparent 100%);
26
+ }
22
27
  }
23
28
 
24
29
  .overflow-slider-fade--end {
25
30
  right: 0;
26
31
  background: linear-gradient(to left, var(--overflow-slider-fade-color) 0%, transparent 100%);
32
+ [dir="rtl"] & {
33
+ right: auto;
34
+ left: 0;
35
+ background: linear-gradient(to right, var(--overflow-slider-fade-color) 0%, transparent 100%);
36
+ }
27
37
  }
@@ -29,16 +29,22 @@ export default function FullWidthPlugin( args: { [key: string]: any } ) {
29
29
 
30
30
  const marginAmount = Math.floor((window.innerWidth - options.targetWidth(slider)) / 2);
31
31
  if ( options.addMarginBefore ) {
32
- firstSlide.style.marginLeft = `${marginAmount}px`;
32
+ firstSlide.style.marginInlineStart = `${marginAmount}px`;
33
33
  }
34
34
  if ( options.addMarginAfter ) {
35
- lastSlide.style.marginRight = `${marginAmount}px`;
35
+ lastSlide.style.marginInlineEnd = `${marginAmount}px`;
36
36
  }
37
37
  slider.container.setAttribute( 'data-full-width-offset', marginAmount.toString() );
38
+ setCSS();
39
+ };
40
+
41
+ const setCSS = () => {
42
+ slider.container.style.setProperty('--slider-container-target-width', `${options.targetWidth(slider)}px`);
38
43
  };
39
44
 
40
45
  update();
41
46
  slider.on( 'contentsChanged', update );
42
47
  slider.on( 'containerSizeChanged', update );
48
+ window.addEventListener( 'resize', setCSS );
43
49
  };
44
50
  }
@@ -15,8 +15,8 @@ export type ScrollIndicatorOptions = {
15
15
  container: HTMLElement | null,
16
16
  };
17
17
 
18
- export default function ScrollIndicatorPlugin( args: { [key: string]: any } ) {
19
- return ( slider: Slider ) => {
18
+ export default function ScrollIndicatorPlugin(args: { [key: string]: any }) {
19
+ return (slider: Slider) => {
20
20
 
21
21
  const options = <ScrollIndicatorOptions>{
22
22
  classNames: {
@@ -26,42 +26,44 @@ export default function ScrollIndicatorPlugin( args: { [key: string]: any } ) {
26
26
  container: args?.container ?? null,
27
27
  };
28
28
 
29
+ const scrollbarContainer = document.createElement('div');
30
+ scrollbarContainer.setAttribute('class', options.classNames.scrollIndicator);
31
+ scrollbarContainer.setAttribute('tabindex', '0');
32
+ scrollbarContainer.setAttribute('role', 'scrollbar');
33
+ scrollbarContainer.setAttribute('aria-controls', slider.container.getAttribute('id') ?? '');
34
+ scrollbarContainer.setAttribute('aria-orientation', 'horizontal');
35
+ scrollbarContainer.setAttribute('aria-valuemax', '100');
36
+ scrollbarContainer.setAttribute('aria-valuemin', '0');
37
+ scrollbarContainer.setAttribute('aria-valuenow', '0');
29
38
 
30
- const scrollbarContainer = document.createElement( 'div' );
31
- scrollbarContainer.setAttribute( 'class', options.classNames.scrollIndicator );
32
- scrollbarContainer.setAttribute( 'tabindex', '0' );
33
- scrollbarContainer.setAttribute( 'role', 'scrollbar' );
34
- scrollbarContainer.setAttribute( 'aria-controls', slider.container.getAttribute( 'id' ) ?? '' );
35
- scrollbarContainer.setAttribute( 'aria-orientation', 'horizontal' );
36
- scrollbarContainer.setAttribute( 'aria-valuemax', '100' );
37
- scrollbarContainer.setAttribute( 'aria-valuemin', '0' );
38
- scrollbarContainer.setAttribute( 'aria-valuenow', '0' );
39
+ const scrollbar = document.createElement('div');
40
+ scrollbar.setAttribute('class', options.classNames.scrollIndicatorBar);
39
41
 
40
- const scrollbar = document.createElement( 'div' );
41
- scrollbar.setAttribute( 'class', options.classNames.scrollIndicatorBar );
42
+ const scrollbarButton = document.createElement('div');
43
+ scrollbarButton.setAttribute('class', options.classNames.scrollIndicatorButton);
44
+ scrollbarButton.setAttribute('data-is-grabbed', 'false');
42
45
 
43
- const scrollbarButton = document.createElement( 'div' );
44
- scrollbarButton.setAttribute( 'class', options.classNames.scrollIndicatorButton );
45
- scrollbarButton.setAttribute( 'data-is-grabbed', 'false' );
46
-
47
- scrollbar.appendChild( scrollbarButton );
48
- scrollbarContainer.appendChild( scrollbar );
46
+ scrollbar.appendChild(scrollbarButton);
47
+ scrollbarContainer.appendChild(scrollbar);
49
48
 
50
49
  const setDataAttributes = () => {
51
- scrollbarContainer.setAttribute( 'data-has-overflow', slider.details.hasOverflow.toString() );
50
+ scrollbarContainer.setAttribute('data-has-overflow', slider.details.hasOverflow.toString());
52
51
  }
53
52
  setDataAttributes();
54
53
 
55
54
  const getScrollbarButtonLeftOffset = () => {
56
- const contentRatio = scrollbarButton.offsetWidth / slider.details.containerWidth;
57
- return slider.container.scrollLeft * contentRatio;
55
+ const contentRatio = scrollbarButton.offsetWidth / slider.details.containerWidth;
56
+ const scrollAmount = slider.getScrollLeft() * contentRatio;
57
+ if (slider.options.rtl) {
58
+ return scrollbar.offsetWidth - scrollbarButton.offsetWidth - scrollAmount;
59
+ }
60
+ return scrollAmount;
58
61
  };
59
62
 
60
- // scrollbarbutton width and position is calculated based on the scroll position and available width
61
63
  let requestId = 0;
62
64
  const update = () => {
63
- if ( requestId ) {
64
- window.cancelAnimationFrame( requestId );
65
+ if (requestId) {
66
+ window.cancelAnimationFrame(requestId);
65
67
  }
66
68
 
67
69
  requestId = window.requestAnimationFrame(() => {
@@ -71,64 +73,60 @@ export default function ScrollIndicatorPlugin( args: { [key: string]: any } ) {
71
73
  scrollbarButton.style.width = `${scrollbarButtonWidth}%`;
72
74
  scrollbarButton.style.transform = `translateX(${scrollLeftInPortion}px)`;
73
75
 
74
- // aria-valuenow
75
- const scrollLeft = slider.container.scrollLeft;
76
+ const scrollLeft = slider.getScrollLeft();
76
77
  const scrollWidth = slider.getInclusiveScrollWidth();
77
78
  const containerWidth = slider.container.offsetWidth;
78
79
  const scrollPercentage = (scrollLeft / (scrollWidth - containerWidth)) * 100;
79
- scrollbarContainer.setAttribute( 'aria-valuenow', Math.round(Number.isNaN(scrollPercentage) ? 0 : scrollPercentage).toString() );
80
+ scrollbarContainer.setAttribute('aria-valuenow', Math.round(Number.isNaN(scrollPercentage) ? 0 : scrollPercentage).toString());
80
81
  });
81
82
  };
82
83
 
83
- // insert to DOM
84
- if ( options.container ) {
85
- options.container.appendChild( scrollbarContainer );
84
+ if (options.container) {
85
+ options.container.appendChild(scrollbarContainer);
86
86
  } else {
87
- slider.container.parentNode?.insertBefore( scrollbarContainer, slider.container.nextSibling );
87
+ slider.container.parentNode?.insertBefore(scrollbarContainer, slider.container.nextSibling);
88
88
  }
89
89
 
90
- // update the scrollbar when the slider is scrolled
91
90
  update();
92
- slider.on( 'scroll', update );
93
- slider.on( 'contentsChanged', update );
94
- slider.on( 'containerSizeChanged', update );
95
- slider.on( 'detailsChanged', setDataAttributes );
96
-
97
- // handle arrow keys while focused
98
- scrollbarContainer.addEventListener( 'keydown', (e) => {
99
- if ( e.key === 'ArrowLeft' ) {
100
- slider.moveToDirection( 'prev' );
101
- } else if ( e.key === 'ArrowRight' ) {
102
- slider.moveToDirection( 'next' );
91
+ slider.on('scroll', update);
92
+ slider.on('contentsChanged', update);
93
+ slider.on('containerSizeChanged', update);
94
+ slider.on('detailsChanged', setDataAttributes);
95
+
96
+ scrollbarContainer.addEventListener('keydown', (e) => {
97
+ if (e.key === 'ArrowLeft') {
98
+ slider.moveToDirection('prev');
99
+ } else if (e.key === 'ArrowRight') {
100
+ slider.moveToDirection('next');
103
101
  }
104
102
  });
105
103
 
106
- // handle click to before or after the scrollbar button
107
- scrollbarContainer.addEventListener( 'click', (e) => {
104
+ let isInteractionDown = false;
105
+ let startX = 0;
106
+ let scrollLeft = slider.getScrollLeft();
107
+
108
+ scrollbarContainer.addEventListener('click', (e) => {
109
+ if ( e.target == scrollbarButton ) {
110
+ return;
111
+ }
108
112
  const scrollbarButtonWidth = scrollbarButton.offsetWidth;
109
113
  const scrollbarButtonLeft = getScrollbarButtonLeftOffset();
110
114
  const scrollbarButtonRight = scrollbarButtonLeft + scrollbarButtonWidth;
111
- const clickX = e.pageX - scrollbarContainer.offsetLeft;
112
- if ( Math.floor( clickX ) < Math.floor( scrollbarButtonLeft ) ) {
113
- slider.moveToDirection( 'prev' );
114
- } else if ( Math.floor( clickX ) > Math.floor( scrollbarButtonRight ) ) {
115
- slider.moveToDirection( 'next' );
115
+ const clickX = e.pageX - Math.abs( scrollbarContainer.offsetLeft );
116
+ if (Math.floor(clickX) < Math.floor(scrollbarButtonLeft)) {
117
+ slider.moveToDirection(slider.options.rtl ? 'next' : 'prev');
118
+ } else if (Math.floor(clickX) > Math.floor(scrollbarButtonRight)) {
119
+ slider.moveToDirection(slider.options.rtl ? 'prev' : 'next');
116
120
  }
117
121
  });
118
122
 
119
- // make scrollbar button draggable via mouse/touch and update the scroll position
120
- let isInteractionDown = false;
121
- let startX = 0;
122
- let scrollLeft = 0;
123
-
124
123
  const onInteractionDown = (e: MouseEvent | TouchEvent) => {
125
124
  isInteractionDown = true;
126
125
  const pageX = (e as MouseEvent).pageX || (e as TouchEvent).touches[0].pageX;
127
126
  startX = pageX - scrollbarContainer.offsetLeft;
128
- scrollLeft = slider.container.scrollLeft;
129
- // change cursor to grabbing
127
+ scrollLeft = slider.getScrollLeft();
130
128
  scrollbarButton.style.cursor = 'grabbing';
131
- scrollbarButton.setAttribute( 'data-is-grabbed', 'true' );
129
+ scrollbarButton.setAttribute('data-is-grabbed', 'true');
132
130
 
133
131
  e.preventDefault();
134
132
  e.stopPropagation();
@@ -144,13 +142,14 @@ export default function ScrollIndicatorPlugin( args: { [key: string]: any } ) {
144
142
  const scrollingFactor = slider.details.scrollableAreaWidth / scrollbarContainer.offsetWidth;
145
143
 
146
144
  const walk = (x - startX) * scrollingFactor;
147
- slider.container.scrollLeft = scrollLeft + walk;
145
+ const distance = slider.options.rtl ? scrollLeft - walk : scrollLeft + walk;
146
+ slider.setScrollLeft(distance);
148
147
  };
149
148
 
150
149
  const onInteractionUp = () => {
151
150
  isInteractionDown = false;
152
151
  scrollbarButton.style.cursor = '';
153
- scrollbarButton.setAttribute( 'data-is-grabbed', 'false' );
152
+ scrollbarButton.setAttribute('data-is-grabbed', 'false');
154
153
  };
155
154
 
156
155
  scrollbarButton.addEventListener('mousedown', onInteractionDown);
@@ -161,6 +160,5 @@ export default function ScrollIndicatorPlugin( args: { [key: string]: any } ) {
161
160
 
162
161
  window.addEventListener('mouseup', onInteractionUp);
163
162
  window.addEventListener('touchend', onInteractionUp);
164
-
165
163
  };
166
164
  }
@@ -39,13 +39,16 @@ export default function FullWidthPlugin( args: { [key: string]: any } ) {
39
39
  setActiveThumbnail();
40
40
  addClickListeners();
41
41
 
42
- // @todo debounce on scroll
43
- mainSlider.on( 'activeSlideChanged', () => {
42
+ mainSlider.on( 'scrollEnd', () => {
44
43
  setTimeout(() => {
45
- const activeSlideIdx = mainSlider.activeSlideIdx;
46
- const activeThumbnail = slider.slides[activeSlideIdx] as HTMLElement;
44
+ const mainActiveSlideIdx = mainSlider.activeSlideIdx;
45
+ const thumbActiveSlideIdx = slider.activeSlideIdx;
46
+ if ( thumbActiveSlideIdx === mainActiveSlideIdx ) {
47
+ return;
48
+ }
49
+ const activeThumbnail = slider.slides[mainActiveSlideIdx] as HTMLElement;
47
50
  setActiveThumbnail(activeThumbnail);
48
- slider.moveToSlide(activeSlideIdx);
51
+ slider.moveToSlide(mainActiveSlideIdx);
49
52
  }, 50);
50
53
  });
51
54