@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.
- package/.nvmrc +1 -1
- package/README.md +12 -2
- package/dist/core/overflow-slider.esm.js +2 -0
- package/dist/core/overflow-slider.min.js +1 -1
- package/dist/core/slider.esm.js +159 -9
- package/dist/core/slider.min.js +1 -1
- package/dist/overflow-slider.css +1 -1
- package/dist/plugins/dots/dots/index.esm.js +21 -22
- package/dist/plugins/dots/dots/index.min.js +1 -1
- package/dist/plugins/drag-scrolling/drag-scrolling/index.esm.js +30 -3
- package/dist/plugins/drag-scrolling/drag-scrolling/index.min.js +1 -1
- package/dist/plugins/fade/fade/index.esm.js +81 -0
- package/dist/plugins/fade/fade/index.min.js +1 -0
- package/dist/plugins/full-width/full-width/index.esm.js +1 -0
- package/dist/plugins/full-width/full-width/index.min.js +1 -1
- package/dist/plugins/scroll-indicator/scroll-indicator/index.esm.js +0 -2
- package/dist/plugins/scroll-indicator/scroll-indicator/index.min.js +1 -1
- package/dist/plugins/thumbnails/thumbnails/index.esm.js +2 -1
- package/dist/plugins/thumbnails/thumbnails/index.min.js +1 -1
- package/docs/assets/demo.css +16 -0
- package/docs/assets/demo.js +44 -7
- package/docs/dist/core/overflow-slider.esm.js +2 -0
- package/docs/dist/core/overflow-slider.min.js +1 -1
- package/docs/dist/core/slider.esm.js +159 -9
- package/docs/dist/core/slider.min.js +1 -1
- package/docs/dist/overflow-slider.css +1 -1
- package/docs/dist/plugins/dots/dots/index.esm.js +21 -22
- package/docs/dist/plugins/dots/dots/index.min.js +1 -1
- package/docs/dist/plugins/drag-scrolling/drag-scrolling/index.esm.js +30 -3
- package/docs/dist/plugins/drag-scrolling/drag-scrolling/index.min.js +1 -1
- package/docs/dist/plugins/fade/fade/index.esm.js +81 -0
- package/docs/dist/plugins/fade/fade/index.min.js +1 -0
- package/docs/dist/plugins/full-width/full-width/index.esm.js +1 -0
- package/docs/dist/plugins/full-width/full-width/index.min.js +1 -1
- package/docs/dist/plugins/scroll-indicator/scroll-indicator/index.esm.js +0 -2
- package/docs/dist/plugins/scroll-indicator/scroll-indicator/index.min.js +1 -1
- package/docs/dist/plugins/thumbnails/thumbnails/index.esm.js +2 -1
- package/docs/dist/plugins/thumbnails/thumbnails/index.min.js +1 -1
- package/docs/index.html +39 -7
- package/package.json +5 -1
- package/src/core/overflow-slider.ts +2 -0
- package/src/core/slider.ts +176 -14
- package/src/core/types.ts +31 -1
- package/src/overflow-slider.scss +2 -1
- package/src/plugins/dots/index.ts +21 -23
- package/src/plugins/drag-scrolling/index.ts +34 -5
- package/src/plugins/fade/index.ts +102 -0
- package/src/plugins/fade/styles.scss +27 -0
- package/src/plugins/full-width/index.ts +1 -0
- package/src/plugins/scroll-indicator/index.ts +0 -2
- 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.
|
|
49
|
-
const
|
|
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 ===
|
|
62
|
-
dot.setAttribute( 'data-
|
|
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
|
|
69
|
-
if ( !
|
|
68
|
+
const currentItemItem = dots.querySelector( `[aria-pressed="true"]` );
|
|
69
|
+
if ( ! currentItemItem ) {
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
|
-
const
|
|
72
|
+
const currentItem = parseInt( currentItemItem.getAttribute( 'data-item' ) ?? '1' );
|
|
73
73
|
if ( e.key === 'ArrowLeft' ) {
|
|
74
|
-
const previousPage =
|
|
74
|
+
const previousPage = currentItem - 1;
|
|
75
75
|
if ( previousPage > 0 ) {
|
|
76
|
-
const matchingDot = dots.querySelector( `[data-
|
|
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 =
|
|
84
|
+
const nextPage = currentItem + 1;
|
|
85
85
|
if ( nextPage <= pages ) {
|
|
86
|
-
const matchingDot = dots.querySelector( `[data-
|
|
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-
|
|
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 = (
|
|
108
|
-
const scrollTargetPosition = slider.details.containerWidth * ( page - 1 );
|
|
109
|
-
slider.container.style.scrollBehavior = slider.options.scrollBehavior;
|
|
110
|
-
slider.container.
|
|
111
|
-
slider.container.
|
|
112
|
-
slider.
|
|
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( '
|
|
125
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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;
|