@evermade/overflow-slider 2.0.2 → 3.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.
Files changed (51) hide show
  1. package/.nvmrc +1 -1
  2. package/README.md +12 -2
  3. package/dist/core/overflow-slider.esm.js +2 -0
  4. package/dist/core/overflow-slider.min.js +1 -1
  5. package/dist/core/slider.esm.js +159 -9
  6. package/dist/core/slider.min.js +1 -1
  7. package/dist/overflow-slider.css +1 -1
  8. package/dist/plugins/dots/dots/index.esm.js +21 -22
  9. package/dist/plugins/dots/dots/index.min.js +1 -1
  10. package/dist/plugins/drag-scrolling/drag-scrolling/index.esm.js +30 -3
  11. package/dist/plugins/drag-scrolling/drag-scrolling/index.min.js +1 -1
  12. package/dist/plugins/fade/fade/index.esm.js +81 -0
  13. package/dist/plugins/fade/fade/index.min.js +1 -0
  14. package/dist/plugins/full-width/full-width/index.esm.js +1 -0
  15. package/dist/plugins/full-width/full-width/index.min.js +1 -1
  16. package/dist/plugins/scroll-indicator/scroll-indicator/index.esm.js +0 -2
  17. package/dist/plugins/scroll-indicator/scroll-indicator/index.min.js +1 -1
  18. package/dist/plugins/thumbnails/thumbnails/index.esm.js +2 -1
  19. package/dist/plugins/thumbnails/thumbnails/index.min.js +1 -1
  20. package/docs/assets/demo.css +16 -0
  21. package/docs/assets/demo.js +44 -7
  22. package/docs/dist/core/overflow-slider.esm.js +2 -0
  23. package/docs/dist/core/overflow-slider.min.js +1 -1
  24. package/docs/dist/core/slider.esm.js +159 -9
  25. package/docs/dist/core/slider.min.js +1 -1
  26. package/docs/dist/overflow-slider.css +1 -1
  27. package/docs/dist/plugins/dots/dots/index.esm.js +21 -22
  28. package/docs/dist/plugins/dots/dots/index.min.js +1 -1
  29. package/docs/dist/plugins/drag-scrolling/drag-scrolling/index.esm.js +30 -3
  30. package/docs/dist/plugins/drag-scrolling/drag-scrolling/index.min.js +1 -1
  31. package/docs/dist/plugins/fade/fade/index.esm.js +81 -0
  32. package/docs/dist/plugins/fade/fade/index.min.js +1 -0
  33. package/docs/dist/plugins/full-width/full-width/index.esm.js +1 -0
  34. package/docs/dist/plugins/full-width/full-width/index.min.js +1 -1
  35. package/docs/dist/plugins/scroll-indicator/scroll-indicator/index.esm.js +0 -2
  36. package/docs/dist/plugins/scroll-indicator/scroll-indicator/index.min.js +1 -1
  37. package/docs/dist/plugins/thumbnails/thumbnails/index.esm.js +2 -1
  38. package/docs/dist/plugins/thumbnails/thumbnails/index.min.js +1 -1
  39. package/docs/index.html +39 -7
  40. package/package.json +5 -1
  41. package/src/core/overflow-slider.ts +2 -0
  42. package/src/core/slider.ts +176 -14
  43. package/src/core/types.ts +31 -1
  44. package/src/overflow-slider.scss +2 -1
  45. package/src/plugins/dots/index.ts +21 -23
  46. package/src/plugins/drag-scrolling/index.ts +34 -5
  47. package/src/plugins/fade/index.ts +102 -0
  48. package/src/plugins/fade/styles.scss +27 -0
  49. package/src/plugins/full-width/index.ts +1 -0
  50. package/src/plugins/scroll-indicator/index.ts +0 -2
  51. package/src/plugins/thumbnails/index.ts +2 -1
@@ -45,8 +45,8 @@ export default function DotsPlugin( args: { [key: string]: any } ) {
45
45
 
46
46
  const dotsList = document.createElement( 'ul' );
47
47
 
48
- const pages = slider.details.amountOfPages;
49
- const currentPage = slider.details.currentPage;
48
+ const pages = slider.details.slideCount;
49
+ const currentItem = slider.activeSlideIdx;
50
50
 
51
51
  if ( pages <= 1 ) {
52
52
  return;
@@ -58,22 +58,22 @@ export default function DotsPlugin( args: { [key: string]: any } ) {
58
58
  dot.setAttribute( 'type', 'button' );
59
59
  dot.setAttribute( 'class', options.classNames.dotsItem );
60
60
  dot.setAttribute( 'aria-label', options.texts.dotDescription.replace( '%d', ( i + 1 ).toString() ).replace( '%d', pages.toString() ) );
61
- dot.setAttribute( 'aria-pressed', ( i === currentPage ).toString() );
62
- dot.setAttribute( 'data-page', ( i + 1 ).toString() );
61
+ dot.setAttribute( 'aria-pressed', ( i === currentItem ).toString() );
62
+ dot.setAttribute( 'data-item', ( i + 1 ).toString() );
63
63
  dotListItem.appendChild( dot );
64
64
  dotsList.appendChild( dotListItem );
65
65
  dot.addEventListener( 'click', () => activateDot( i + 1 ) );
66
66
  dot.addEventListener( 'focus', () => pageFocused = i + 1 );
67
67
  dot.addEventListener( 'keydown', ( e ) => {
68
- const currentPageItem = dots.querySelector( `[aria-pressed="true"]` );
69
- if ( ! currentPageItem ) {
68
+ const currentItemItem = dots.querySelector( `[aria-pressed="true"]` );
69
+ if ( ! currentItemItem ) {
70
70
  return;
71
71
  }
72
- const currentPage = parseInt( currentPageItem.getAttribute( 'data-page' ) ?? '1' );
72
+ const currentItem = parseInt( currentItemItem.getAttribute( 'data-item' ) ?? '1' );
73
73
  if ( e.key === 'ArrowLeft' ) {
74
- const previousPage = currentPage - 1;
74
+ const previousPage = currentItem - 1;
75
75
  if ( previousPage > 0 ) {
76
- const matchingDot = dots.querySelector( `[data-page="${previousPage}"]` );
76
+ const matchingDot = dots.querySelector( `[data-item="${previousPage}"]` );
77
77
  if ( matchingDot ) {
78
78
  ( <HTMLElement>matchingDot ).focus();
79
79
  }
@@ -81,9 +81,9 @@ export default function DotsPlugin( args: { [key: string]: any } ) {
81
81
  }
82
82
  }
83
83
  if ( e.key === 'ArrowRight' ) {
84
- const nextPage = currentPage + 1;
84
+ const nextPage = currentItem + 1;
85
85
  if ( nextPage <= pages ) {
86
- const matchingDot = dots.querySelector( `[data-page="${nextPage}"]` );
86
+ const matchingDot = dots.querySelector( `[data-item="${nextPage}"]` );
87
87
  if ( matchingDot ) {
88
88
  ( <HTMLElement>matchingDot ).focus();
89
89
  }
@@ -97,20 +97,19 @@ export default function DotsPlugin( args: { [key: string]: any } ) {
97
97
 
98
98
  // return focus to same page after rebuild
99
99
  if ( pageFocused ) {
100
- const matchingDot = dots.querySelector( `[data-page="${pageFocused}"]` );
100
+ const matchingDot = dots.querySelector( `[data-item="${pageFocused}"]` );
101
101
  if ( matchingDot ) {
102
102
  ( <HTMLElement>matchingDot ).focus();
103
103
  }
104
104
  }
105
105
  };
106
106
 
107
- const activateDot = ( page: number ) => {
108
- const scrollTargetPosition = slider.details.containerWidth * ( page - 1 );
109
- slider.container.style.scrollBehavior = slider.options.scrollBehavior;
110
- slider.container.style.scrollSnapType = 'none';
111
- slider.container.scrollLeft = scrollTargetPosition;
112
- slider.container.style.scrollBehavior = '';
113
- slider.container.style.scrollSnapType = '';
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
+ slider.moveToSlide( item - 1 );
114
113
  };
115
114
 
116
115
  buildDots();
@@ -121,9 +120,8 @@ export default function DotsPlugin( args: { [key: string]: any } ) {
121
120
  slider.container.parentNode?.insertBefore( dots, slider.container.nextSibling );
122
121
  }
123
122
 
124
- slider.on( 'detailsChanged', () => {
125
- buildDots();
126
- } );
127
-
123
+ slider.on( 'scrollEnd', buildDots );
124
+ slider.on( 'contentsChanged', buildDots );
125
+ slider.on( 'containerSizeChanged', buildDots );
128
126
  };
129
127
  };
@@ -15,23 +15,26 @@ export default function DragScrollingPlugin( args: { [key: string]: any } ) {
15
15
  let startX = 0;
16
16
  let scrollLeft = 0;
17
17
 
18
+ let isMovingForward = false;
19
+ let programmaticScrollStarted = false;
20
+ let mayNeedToSnap = false;
21
+
18
22
  // add data attribute to container
19
23
  slider.container.setAttribute( 'data-has-drag-scrolling', 'true' );
20
24
 
21
25
  const mouseDown = (e: MouseEvent) => {
26
+ programmaticScrollStarted = false;
22
27
  if ( ! slider.details.hasOverflow ) {
23
28
  return;
24
29
  }
25
30
  if ( ! slider.container.contains( e.target as Node ) ) {
26
31
  return;
27
32
  }
28
-
29
33
  isMouseDown = true;
30
34
  startX = e.pageX - slider.container.offsetLeft;
31
35
  scrollLeft = slider.container.scrollLeft;
32
36
  // change cursor to grabbing
33
37
  slider.container.style.cursor = 'grabbing';
34
- slider.container.style.scrollSnapType = 'none';
35
38
  slider.container.style.scrollBehavior = 'auto';
36
39
  // prevent focus going to the slides
37
40
  e.preventDefault();
@@ -40,15 +43,26 @@ export default function DragScrollingPlugin( args: { [key: string]: any } ) {
40
43
 
41
44
  const mouseMove = (e: MouseEvent) => {
42
45
  if ( ! slider.details.hasOverflow ) {
46
+ programmaticScrollStarted = false;
43
47
  return;
44
48
  }
45
49
  if (!isMouseDown) {
50
+ programmaticScrollStarted = false;
46
51
  return;
47
52
  }
48
53
  e.preventDefault();
54
+ if (!programmaticScrollStarted) {
55
+ programmaticScrollStarted = true;
56
+ slider.emit('programmaticScrollStart');
57
+ }
49
58
  const x = e.pageX - slider.container.offsetLeft;
50
59
  const walk = (x - startX);
51
- slider.container.scrollLeft = scrollLeft - walk;
60
+ const newScrollLeft = scrollLeft - walk;
61
+ mayNeedToSnap = true;
62
+ if ( slider.container.scrollLeft !== newScrollLeft ) {
63
+ isMovingForward = slider.container.scrollLeft < newScrollLeft;
64
+ }
65
+ slider.container.scrollLeft = newScrollLeft;
52
66
 
53
67
  const absWalk = Math.abs(walk);
54
68
  const slides = slider.container.querySelectorAll( slider.options.slidesSelector );
@@ -59,13 +73,15 @@ export default function DragScrollingPlugin( args: { [key: string]: any } ) {
59
73
  };
60
74
 
61
75
  const mouseUp = () => {
62
- if ( ! slider.details.hasOverflow ) {
76
+ if (!slider.details.hasOverflow) {
77
+ programmaticScrollStarted = false;
63
78
  return;
64
79
  }
65
80
  isMouseDown = false;
81
+
66
82
  slider.container.style.cursor = '';
67
83
  setTimeout(() => {
68
- slider.container.style.scrollSnapType = '';
84
+ programmaticScrollStarted = false;
69
85
  slider.container.style.scrollBehavior = '';
70
86
  const slides = slider.container.querySelectorAll( slider.options.slidesSelector );
71
87
  slides.forEach((slide) => {
@@ -78,5 +94,18 @@ export default function DragScrollingPlugin( args: { [key: string]: any } ) {
78
94
  window.addEventListener('mousemove', mouseMove);
79
95
  window.addEventListener('mouseup', mouseUp);
80
96
 
97
+ // emulate scroll snapping
98
+ if ( slider.options.emulateScrollSnap ) {
99
+ const snap = () => {
100
+ if (!mayNeedToSnap || isMouseDown) {
101
+ return;
102
+ }
103
+ mayNeedToSnap = false;
104
+ slider.snapToClosestSlide(isMovingForward ? 'next' : 'prev');
105
+ };
106
+ slider.on( 'programmaticScrollEnd', snap );
107
+ window.addEventListener( 'mouseup', snap );
108
+ }
109
+
81
110
  };
82
111
  };
@@ -0,0 +1,102 @@
1
+ import { Slider } from '../../core/types';
2
+
3
+ export type FadeOptions = {
4
+ classNames: {
5
+ fadeItem: string;
6
+ fadeItemStart: string;
7
+ fadeItemEnd: string;
8
+ },
9
+ container: HTMLElement | null,
10
+ containerStart: HTMLElement | null,
11
+ containerEnd: HTMLElement | null,
12
+ };
13
+
14
+ export default function FadePlugin( args: { [key: string]: any } ) {
15
+ return ( slider: Slider ) => {
16
+
17
+ const options = <FadeOptions>{
18
+ classNames: {
19
+ fadeItem: 'overflow-slider-fade',
20
+ fadeItemStart: 'overflow-slider-fade--start',
21
+ fadeItemEnd: 'overflow-slider-fade--end',
22
+ },
23
+ container: args?.container ?? null,
24
+ containerStart: args?.containerStart ?? null,
25
+ containerEnd: args?.containerEnd ?? null,
26
+ };
27
+
28
+ const fadeItemStart = document.createElement( 'div' );
29
+ fadeItemStart.classList.add( options.classNames.fadeItem, options.classNames.fadeItemStart );
30
+ fadeItemStart.setAttribute( 'aria-hidden', 'true' );
31
+ fadeItemStart.setAttribute( 'tabindex', '-1' );
32
+
33
+ const fadeItemEnd = document.createElement( 'div' );
34
+ fadeItemEnd.classList.add( options.classNames.fadeItem, options.classNames.fadeItemEnd );
35
+ fadeItemEnd.setAttribute( 'aria-hidden', 'true' );
36
+ fadeItemEnd.setAttribute( 'tabindex', '-1' );
37
+
38
+ if ( options.containerStart ) {
39
+ options.containerStart.appendChild( fadeItemStart );
40
+ } else if ( options.container ) {
41
+ options.container.appendChild( fadeItemStart );
42
+ }
43
+
44
+ if ( options.containerEnd ) {
45
+ options.containerEnd.appendChild( fadeItemEnd );
46
+ } else if ( options.container ) {
47
+ options.container.appendChild( fadeItemEnd );
48
+ }
49
+
50
+ const hasFadeAtStart = () => {
51
+ return slider.container.scrollLeft > fadeItemStart.offsetWidth;
52
+ }
53
+
54
+ const fadeAtStartOpacity = () => {
55
+ const position = slider.container.scrollLeft;
56
+ if ( position <= fadeItemStart.offsetWidth ) {
57
+ return position / Math.max(fadeItemStart.offsetWidth, 1);
58
+ }
59
+ return 1;
60
+ }
61
+
62
+ const hasFadeAtEnd = () => {
63
+ return slider.container.scrollLeft < (slider.container.scrollWidth - slider.container.clientWidth - fadeItemEnd.offsetWidth);
64
+ }
65
+
66
+ const fadeAtEndOpacity = () => {
67
+ const position = slider.container.scrollLeft;
68
+ const maxPosition = slider.container.scrollWidth - slider.container.clientWidth;
69
+ const maxFadePosition = maxPosition - fadeItemEnd.offsetWidth;
70
+ if ( position >= maxFadePosition ) {
71
+ return ( ( maxFadePosition - position ) / Math.max(fadeItemEnd.offsetWidth, 1) ) + 1;
72
+ }
73
+ return 1;
74
+ }
75
+
76
+ const update = () => {
77
+ fadeItemStart.setAttribute( 'data-has-fade', hasFadeAtStart().toString() );
78
+ fadeItemStart.style.opacity = fadeAtStartOpacity().toString();
79
+ fadeItemEnd.setAttribute( 'data-has-fade', hasFadeAtEnd().toString() );
80
+ fadeItemEnd.style.opacity = fadeAtEndOpacity().toString();
81
+ };
82
+
83
+ update();
84
+ slider.on( 'created', update );
85
+ slider.on( 'contentsChanged', update );
86
+ slider.on( 'containerSizeChanged', update );
87
+ slider.on( 'scrollEnd', update );
88
+ slider.on( 'scrollStart', update );
89
+ let requestId = 0;
90
+ const debouncedUpdate = () => {
91
+ if ( requestId ) {
92
+ window.cancelAnimationFrame( requestId );
93
+ }
94
+ requestId = window.requestAnimationFrame(() => {
95
+ update();
96
+ });
97
+ };
98
+ slider.on('scroll', debouncedUpdate);
99
+
100
+
101
+ };
102
+ }
@@ -0,0 +1,27 @@
1
+ /* --------------------------------------------------------------
2
+ # FadePlugin
3
+ -------------------------------------------------------------- */
4
+
5
+ :root {
6
+ --overflow-slider-fade-color: #fff;
7
+ --overflow-slider-fade-width: 3rem;
8
+ }
9
+
10
+ .overflow-slider-fade {
11
+ position: absolute;
12
+ top: 0;
13
+ width: var(--overflow-slider-fade-width);
14
+ height: 100%;
15
+ pointer-events: none;
16
+ z-index: 1;
17
+ }
18
+
19
+ .overflow-slider-fade--start {
20
+ left: 0;
21
+ background: linear-gradient(to right, var(--overflow-slider-fade-color) 0%, transparent 100%);
22
+ }
23
+
24
+ .overflow-slider-fade--end {
25
+ right: 0;
26
+ background: linear-gradient(to left, var(--overflow-slider-fade-color) 0%, transparent 100%);
27
+ }
@@ -34,6 +34,7 @@ export default function FullWidthPlugin( args: { [key: string]: any } ) {
34
34
  if ( options.addMarginAfter ) {
35
35
  lastSlide.style.marginRight = `${marginAmount}px`;
36
36
  }
37
+ slider.container.setAttribute( 'data-full-width-offset', marginAmount.toString() );
37
38
  };
38
39
 
39
40
  update();
@@ -128,7 +128,6 @@ export default function ScrollIndicatorPlugin( args: { [key: string]: any } ) {
128
128
  scrollLeft = slider.container.scrollLeft;
129
129
  // change cursor to grabbing
130
130
  scrollbarButton.style.cursor = 'grabbing';
131
- slider.container.style.scrollSnapType = 'none';
132
131
  scrollbarButton.setAttribute( 'data-is-grabbed', 'true' );
133
132
 
134
133
  e.preventDefault();
@@ -151,7 +150,6 @@ export default function ScrollIndicatorPlugin( args: { [key: string]: any } ) {
151
150
  const onInteractionUp = () => {
152
151
  isInteractionDown = false;
153
152
  scrollbarButton.style.cursor = '';
154
- slider.container.style.scrollSnapType = '';
155
153
  scrollbarButton.setAttribute( 'data-is-grabbed', 'false' );
156
154
  };
157
155
 
@@ -40,7 +40,8 @@ export default function FullWidthPlugin( args: { [key: string]: any } ) {
40
40
  setActiveThumbnail();
41
41
  addClickListeners();
42
42
 
43
- mainSlider.on( 'activeSlideChanged', () => {
43
+ // @todo debounce on scroll
44
+ mainSlider.on( 'scrollEnd', () => {
44
45
  setTimeout(() => {
45
46
  const activeSlideIdx = mainSlider.activeSlideIdx;
46
47
  const activeThumbnail = slider.slides[activeSlideIdx] as HTMLElement;