@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,230 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en-US">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <link href="dist/overflow-slider.css" rel="stylesheet" />
7
+ <link href="assets/demo.css" rel="stylesheet" />
8
+ <title>Demo – overflow-slider</title>
9
+ <meta property="og:description" content="overflow-slider" />
10
+ </head>
11
+ <body>
12
+ <div class="site">
13
+ <header class="site-header">
14
+ <div class="site-header__inner">
15
+ <div class="site-branding">
16
+ <a href="https://github.com/evermade/overflow-slider" class="site-logo" aria-label="Evermade">
17
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 270 270" width="64" height="64">
18
+ <path fill="#F04D37" d="M0 0h270v270H0z"/>
19
+ <path fill="#fff" d="M134.748 73.7339c-13.949-.1043-27.398 5.1741-37.535 14.7314-10.1368 9.5573-16.1761 22.6537-16.8549 36.5487H189.235c-.715-13.9-6.779-26.9892-16.929-36.5417-10.15-9.5525-23.603-14.8318-37.558-14.7384Zm85.532 74.6601H79.2372c2.228 13.263 9.1042 25.307 19.4039 33.986 10.2999 8.679 23.3539 13.43 36.8379 13.406 9.931.362 19.754-2.16 28.276-7.26 8.521-5.099 15.375-12.557 19.729-21.467h32.848c-13.354 35.921-41.474 54.926-82.072 54.926-11.548.213-23.019-1.905-33.726-6.227-10.7059-4.321-20.4256-10.757-28.5743-18.921-8.1488-8.163-14.5585-17.886-18.8439-28.583-4.2854-10.697-6.358-22.148-6.0933-33.664 0-49.9195 35.7237-87.59 87.7255-87.59 50.588 0 87.238 37.476 87.238 85.743.118 5.275-.489 10.541-1.804 15.651"/>
20
+ </svg>
21
+ <span>@evermade/overflow-slider</span>
22
+ </a>
23
+ </div>
24
+ </div>
25
+ </header>
26
+ <script type="module" src="assets/demo.js"></script>
27
+ <main id="main" class="site-main">
28
+ <article class="entry">
29
+ <div class="entry__content">
30
+ <h1>Overflow Slider Demos</h1>
31
+
32
+ <section class="entry__section">
33
+ <h2>Basic Demos</h2>
34
+ <div class="entry__item">
35
+ <h3>Pure CSS</h3>
36
+ <p>No plugins applied. Using only CSS.</p>
37
+ <div class="overflow-slider example-container example-container-1-css">
38
+ <a href="#1" class="example-item example-item--1">1</a>
39
+ <a href="#2" class="example-item example-item--2">2</a>
40
+ <a href="#3" class="example-item example-item--3">3</a>
41
+ <a href="#4" class="example-item example-item--4">4</a>
42
+ <a href="#5" class="example-item example-item--5">5</a>
43
+ <a href="#6" class="example-item example-item--6">6</a>
44
+ <a href="#7" class="example-item example-item--7">7</a>
45
+ <a href="#8" class="example-item example-item--8">8</a>
46
+ <a href="#9" class="example-item example-item--9">9</a>
47
+ <a href="#10" class="example-item example-item--10">10</a>
48
+ <a href="#11" class="example-item example-item--11">11</a>
49
+ <a href="#12" class="example-item example-item--12">12</a>
50
+ <a href="#13" class="example-item example-item--13">13</a>
51
+ </div>
52
+ </div>
53
+
54
+ <div class="entry__item">
55
+ <h3>Mouse Dragging</h3>
56
+ <p>Drag slides with mouse. Uses DragScrollingPlugin.</p>
57
+ <div class="overflow-slider example-container example-container-1-drag-scrolling">
58
+ <a href="#1" class="example-item example-item--1">1</a>
59
+ <a href="#2" class="example-item example-item--2">2</a>
60
+ <a href="#3" class="example-item example-item--3">3</a>
61
+ <a href="#4" class="example-item example-item--4">4</a>
62
+ <a href="#5" class="example-item example-item--5">5</a>
63
+ <a href="#6" class="example-item example-item--6">6</a>
64
+ <a href="#7" class="example-item example-item--7">7</a>
65
+ <a href="#8" class="example-item example-item--8">8</a>
66
+ <a href="#9" class="example-item example-item--9">9</a>
67
+ <a href="#10" class="example-item example-item--10">10</a>
68
+ <a href="#11" class="example-item example-item--11">11</a>
69
+ <a href="#12" class="example-item example-item--12">12</a>
70
+ <a href="#13" class="example-item example-item--13">13</a>
71
+ </div>
72
+ </div>
73
+
74
+ <div class="entry__item">
75
+ <h3>Arrows</h3>
76
+ <p>Display buttons to move slides. Uses ArrowsPlugin.</p>
77
+ <div class="overflow-slider example-container example-container-1-arrows">
78
+ <a href="#1" class="example-item example-item--1">1</a>
79
+ <a href="#2" class="example-item example-item--2">2</a>
80
+ <a href="#3" class="example-item example-item--3">3</a>
81
+ <a href="#4" class="example-item example-item--4">4</a>
82
+ <a href="#5" class="example-item example-item--5">5</a>
83
+ <a href="#6" class="example-item example-item--6">6</a>
84
+ <a href="#7" class="example-item example-item--7">7</a>
85
+ <a href="#8" class="example-item example-item--8">8</a>
86
+ <a href="#9" class="example-item example-item--9">9</a>
87
+ <a href="#10" class="example-item example-item--10">10</a>
88
+ <a href="#11" class="example-item example-item--11">11</a>
89
+ <a href="#12" class="example-item example-item--12">12</a>
90
+ <a href="#13" class="example-item example-item--13">13</a>
91
+ </div>
92
+ </div>
93
+
94
+ <div class="entry__item">
95
+ <h3>Scroll Indicator</h3>
96
+ <p>Display scroll indicator. Uses ScrollIndicatorPlugin.</p>
97
+ <div class="overflow-slider example-container example-container-1-scroll-indicator">
98
+ <a href="#1" class="example-item example-item--1">1</a>
99
+ <a href="#2" class="example-item example-item--2">2</a>
100
+ <a href="#3" class="example-item example-item--3">3</a>
101
+ <a href="#4" class="example-item example-item--4">4</a>
102
+ <a href="#5" class="example-item example-item--5">5</a>
103
+ <a href="#6" class="example-item example-item--6">6</a>
104
+ <a href="#7" class="example-item example-item--7">7</a>
105
+ <a href="#8" class="example-item example-item--8">8</a>
106
+ <a href="#9" class="example-item example-item--9">9</a>
107
+ <a href="#10" class="example-item example-item--10">10</a>
108
+ </div>
109
+ </div>
110
+
111
+ <div class="entry__item">
112
+ <h3>Dots</h3>
113
+ <p>Display dots indicator. Uses DotsPlugin.</p>
114
+ <div class="overflow-slider example-container example-container-1-dots">
115
+ <a href="#1" class="example-item example-item--1">1</a>
116
+ <a href="#2" class="example-item example-item--2">2</a>
117
+ <a href="#3" class="example-item example-item--3">3</a>
118
+ <a href="#4" class="example-item example-item--4">4</a>
119
+ <a href="#5" class="example-item example-item--5">5</a>
120
+ <a href="#6" class="example-item example-item--6">6</a>
121
+ <a href="#7" class="example-item example-item--7">7</a>
122
+ <a href="#8" class="example-item example-item--8">8</a>
123
+ <a href="#9" class="example-item example-item--9">9</a>
124
+ <a href="#10" class="example-item example-item--10">10</a>
125
+ </div>
126
+ </div>
127
+
128
+
129
+ </section>
130
+
131
+ <section class="entry__section">
132
+
133
+ <h2>Slide Contents</h2>
134
+
135
+ <div class="entry__item">
136
+ <h3>Perfectly Fitting Slides</h3>
137
+ <p>Slides fill the container perfectly.</p>
138
+ <div class="overflow-slider example-container example-container-2-perfect-fit">
139
+ <a href="#1" class="example-item example-item--1">1</a>
140
+ <a href="#2" class="example-item example-item--2">2</a>
141
+ <a href="#3" class="example-item example-item--3">3</a>
142
+ <a href="#4" class="example-item example-item--4">4</a>
143
+ <a href="#5" class="example-item example-item--5">5</a>
144
+ <a href="#6" class="example-item example-item--6">6</a>
145
+ <a href="#7" class="example-item example-item--7">7</a>
146
+ <a href="#8" class="example-item example-item--8">8</a>
147
+ <a href="#9" class="example-item example-item--9">9</a>
148
+ <a href="#10" class="example-item example-item--10">10</a>
149
+ </div>
150
+ </div>
151
+
152
+ <div class="entry__item">
153
+ <h3>Varying Width Slides</h3>
154
+ <p>Slides are not equal width.</p>
155
+ <div class="overflow-slider example-container example-container-2-varying-widths">
156
+ <a href="#1" class="example-item example-item--1">1</a>
157
+ <a href="#2" class="example-item example-item--2">2</a>
158
+ <a href="#3" class="example-item example-item--3">3</a>
159
+ <a href="#4" class="example-item example-item--4">4</a>
160
+ <a href="#5" class="example-item example-item--5">5</a>
161
+ <a href="#6" class="example-item example-item--6">6</a>
162
+ <a href="#7" class="example-item example-item--7">7</a>
163
+ <a href="#8" class="example-item example-item--8">8</a>
164
+ </div>
165
+ </div>
166
+
167
+ <div class="entry__item">
168
+ <h3>Only Few Slides</h3>
169
+ <p>Not enough slides to have a slider (on mobile has though).</p>
170
+ <div class="overflow-slider example-container example-container-2-only-few-slides">
171
+ <a href="#1" class="example-item example-item--1">1</a>
172
+ <a href="#2" class="example-item example-item--2">2</a>
173
+ <a href="#3" class="example-item example-item--3">3</a>
174
+ </div>
175
+ </div>
176
+
177
+ </section>
178
+
179
+ <section class="entry__section">
180
+
181
+ <h2>CSS Trickery</h2>
182
+
183
+ <div class="entry__item">
184
+ <h3>Scroll Snapping (mandatory)</h3>
185
+ <p>Force scrolling to snap to slide always.</p>
186
+ <div class="overflow-slider example-container example-container-3-scroll-snapping-mandatory">
187
+ <a href="#1" class="example-item example-item--1">1</a>
188
+ <a href="#2" class="example-item example-item--2">2</a>
189
+ <a href="#3" class="example-item example-item--3">3</a>
190
+ <a href="#4" class="example-item example-item--4">4</a>
191
+ <a href="#5" class="example-item example-item--5">5</a>
192
+ <a href="#6" class="example-item example-item--6">6</a>
193
+ <a href="#7" class="example-item example-item--7">7</a>
194
+ <a href="#8" class="example-item example-item--8">8</a>
195
+ <a href="#9" class="example-item example-item--9">9</a>
196
+ <a href="#10" class="example-item example-item--10">10</a>
197
+ </div>
198
+ </div>
199
+
200
+ <div class="entry__item">
201
+ <h3>Scroll Snapping (proximity)</h3>
202
+ <p>Force scrolling to snap to slide not so strictly.</p>
203
+ <div class="overflow-slider example-container example-container-3-scroll-snapping-proximity">
204
+ <a href="#1" class="example-item example-item--1">1</a>
205
+ <a href="#2" class="example-item example-item--2">2</a>
206
+ <a href="#3" class="example-item example-item--3">3</a>
207
+ <a href="#4" class="example-item example-item--4">4</a>
208
+ <a href="#5" class="example-item example-item--5">5</a>
209
+ <a href="#6" class="example-item example-item--6">6</a>
210
+ <a href="#7" class="example-item example-item--7">7</a>
211
+ <a href="#8" class="example-item example-item--8">8</a>
212
+ <a href="#9" class="example-item example-item--9">9</a>
213
+ <a href="#10" class="example-item example-item--10">10</a>
214
+ </div>
215
+ </div>
216
+
217
+ </section>
218
+
219
+ </div>
220
+ </article>
221
+ </main>
222
+ <div class="site-footer">
223
+ <p>
224
+ This is demo for the
225
+ <a href="https://github.com/evermade/overflow-slider">@evermade/overflow-slider</a>.
226
+ </p>
227
+ </div>
228
+ </div>
229
+ </body>
230
+ </html>
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "@evermade/overflow-slider",
3
+ "version": "1.0.0",
4
+ "description": "Accessible slider tha works with overflow: auto.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "module": "dist/index.esm.js",
8
+ "types": "dist/index.d.ts",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/evermade/overflow-slider.git"
12
+ },
13
+ "keywords": [
14
+ "overflow-slider"
15
+ ],
16
+ "scripts": {
17
+ "build": "rollup -c",
18
+ "compress": "gzip -9 -fkc dist/index.min.js > dist/index.min.js.gz",
19
+ "show": "ls -lh dist/index.min.js.gz | awk '{print \"Gzipped script size:\", $5\"B\"}'",
20
+ "size": "npm run build -- --silent && npm run compress --silent && npm run show && rm dist/index.min.js.gz",
21
+ "watch": "rollup -c -w",
22
+ "test": "echo \"No tests specified\" && exit 0"
23
+ },
24
+ "author": "Teemu Suoranta, Evermade",
25
+ "license": "MIT",
26
+ "bugs": {
27
+ "url": "https://github.com/evermade/overflow-slider/issues"
28
+ },
29
+ "devDependencies": {
30
+ "@rollup/plugin-commonjs": "^24.0.0",
31
+ "@rollup/plugin-node-resolve": "^15.0.0",
32
+ "@rollup/plugin-typescript": "^11.1.2",
33
+ "@tsconfig/recommended": "^1.0.2",
34
+ "@wordpress/eslint-plugin": "^14.3.0",
35
+ "@wordpress/prettier-config": "^2.13.0",
36
+ "autoprefixer": "^10.4.17",
37
+ "cssnano": "^6.0.3",
38
+ "eslint": "^8.37.0",
39
+ "rollup": "^2.79.1",
40
+ "rollup-plugin-copy": "^3.5.0",
41
+ "rollup-plugin-postcss": "^4.0.2",
42
+ "rollup-plugin-terser": "^7.0.2",
43
+ "sass": "^1.70.0",
44
+ "typescript": "^5.1.6"
45
+ },
46
+ "homepage": "https://github.com/evermade/overflow-slider#readme",
47
+ "publishConfig": {
48
+ "access": "public"
49
+ },
50
+ "dependencies": {
51
+ "resize-observer-polyfill": "^1.5.1",
52
+ "scrollbooster": "^3.0.2",
53
+ "simplebar": "^6.2.5"
54
+ }
55
+ }
@@ -0,0 +1,45 @@
1
+ import { terser } from 'rollup-plugin-terser';
2
+ import pkg from './package.json';
3
+ import { nodeResolve } from '@rollup/plugin-node-resolve';
4
+ import commonjs from '@rollup/plugin-commonjs';
5
+ import typescript from '@rollup/plugin-typescript';
6
+ import postcss from 'rollup-plugin-postcss';
7
+ import copy from 'rollup-plugin-copy';
8
+
9
+ const umd = { format: 'umd', name: 'OverflowSlider', exports: 'named' };
10
+ const es = { format: 'es' };
11
+ const minify = {
12
+ plugins: [terser()],
13
+ banner: () => `/*! ${pkg.name} ${pkg.version} */`,
14
+ };
15
+
16
+ export default {
17
+ input: 'src/index.ts',
18
+ output: [
19
+ // Main files
20
+ { file: 'dist/index.js', ...umd },
21
+ { file: 'dist/index.esm.js', ...es },
22
+ { file: 'docs/dist/overflow-slider.esm.js', ...es },
23
+ // Minified versions
24
+ { file: 'dist/index.min.js', ...umd, ...minify },
25
+ { file: 'dist/index.esm.min.js', ...es, ...minify },
26
+ ],
27
+ plugins: [
28
+ typescript(),
29
+ nodeResolve(),
30
+ commonjs({ include: 'node_modules/**' }),
31
+ postcss({
32
+ extract: true,
33
+ extract: 'overflow-slider.css',
34
+ plugins: [
35
+ require('autoprefixer'),
36
+ require('cssnano')({
37
+ preset: 'default',
38
+ }),
39
+ ],
40
+ minimize: true,
41
+ sourceMap: false,
42
+ extensions: ['.scss', '.css'],
43
+ }),
44
+ ],
45
+ };
@@ -0,0 +1,43 @@
1
+ import { Slider, SliderDetails } from './types';
2
+
3
+ export default function details( slider: Slider) {
4
+
5
+ let instance: SliderDetails;
6
+
7
+ let hasOverflow = false;
8
+ let slideCount = 0;
9
+ let containerWidth = 0;
10
+ let scrollableAreaWidth = 0;
11
+ let amountOfPages = 0;
12
+ let currentPage = 1;
13
+
14
+ if (slider.container.scrollWidth > slider.container.clientWidth) {
15
+ hasOverflow = true;
16
+ }
17
+
18
+ slideCount = Array.from(slider.container.querySelectorAll(':scope > *')).length;
19
+
20
+ containerWidth = slider.container.offsetWidth;
21
+
22
+ scrollableAreaWidth = slider.container.scrollWidth;
23
+
24
+ amountOfPages = Math.ceil(scrollableAreaWidth / containerWidth);
25
+
26
+ if (slider.container.scrollLeft >= 0) {
27
+ currentPage = Math.floor(slider.container.scrollLeft / containerWidth);
28
+ // consider as last page if the scrollLeft + containerWidth is equal to scrollWidth
29
+ if (slider.container.scrollLeft + containerWidth === scrollableAreaWidth) {
30
+ currentPage = amountOfPages - 1;
31
+ }
32
+ }
33
+
34
+ instance = {
35
+ hasOverflow,
36
+ slideCount,
37
+ containerWidth,
38
+ scrollableAreaWidth,
39
+ amountOfPages,
40
+ currentPage,
41
+ };
42
+ return instance;
43
+ };
@@ -0,0 +1,234 @@
1
+ import { Slider, SliderOptions, SliderPlugin } from './types';
2
+ import details from './details';
3
+ import { generateId, objectsAreEqual } from './utils';
4
+
5
+ export default function Slider( container: HTMLElement, options : SliderOptions, plugins? : SliderPlugin[] ) {
6
+ let slider: Slider;
7
+ let subs: { [key: string]: any[] } = {};
8
+
9
+ function init() {
10
+ slider.container = container;
11
+ // ensure container has id
12
+ let containerId = container.getAttribute( 'id' );
13
+ if ( containerId === null ) {
14
+ containerId = generateId( 'overflow-slider' );
15
+ container.setAttribute( 'id', containerId );
16
+ }
17
+ setDetails(true);
18
+ slider.on('contentsChanged', () => setDetails());
19
+ slider.on('containerSizeChanged', () => setDetails());
20
+
21
+ let requestId = 0;
22
+ const setDetailsDebounce = () => {
23
+ if ( requestId ) {
24
+ window.cancelAnimationFrame( requestId );
25
+ }
26
+ requestId = window.requestAnimationFrame(() => {
27
+ setDetails();
28
+ });
29
+ };
30
+ slider.on('scroll', setDetailsDebounce);
31
+
32
+ addEventListeners();
33
+ setDataAttributes();
34
+ setCSSVariables();
35
+ if (plugins) {
36
+ for (const plugin of plugins) {
37
+ plugin(slider);
38
+ }
39
+ }
40
+ slider.on('detailsChanged', () => {
41
+ setDataAttributes();
42
+ setCSSVariables();
43
+ });
44
+ slider.emit('created');
45
+ };
46
+
47
+ function setDetails( isInit = false ) {
48
+ const oldDetails = slider.details;
49
+ const newDetails = details( slider );
50
+ slider.details = newDetails;
51
+ if ( !isInit && !objectsAreEqual( oldDetails, newDetails ) ) {
52
+ slider.emit('detailsChanged');
53
+ } else if ( isInit ) {
54
+ slider.emit('detailsChanged');
55
+ }
56
+ };
57
+
58
+ function addEventListeners() {
59
+
60
+ // changes to DOM
61
+ const observer = new MutationObserver( () => slider.emit('contentsChanged') );
62
+ observer.observe( slider.container, { childList: true } );
63
+
64
+ // container size changes
65
+ const resizeObserver = new ResizeObserver( () => slider.emit('containerSizeChanged') );
66
+ resizeObserver.observe( slider.container );
67
+
68
+ // scroll event with debouncing
69
+ slider.container.addEventListener('scroll', () => slider.emit('scroll'));
70
+
71
+ // Listen for mouse down and touch start events on the document
72
+ // This handles both mouse clicks and touch interactions
73
+ let wasInteractedWith = false;
74
+ slider.container.addEventListener('mousedown', () => {
75
+ wasInteractedWith = true;
76
+ });
77
+ slider.container.addEventListener('touchstart', () => {
78
+ wasInteractedWith = true;
79
+ }, { passive: true });
80
+ slider.container.addEventListener('focusin', (e) => {
81
+ // move target parents as long as they are not the container
82
+ // but only if focus didn't start from mouse or touch
83
+ if (!wasInteractedWith) {
84
+ let target = e.target as HTMLElement;
85
+ while (target.parentElement !== slider.container) {
86
+ if (target.parentElement) {
87
+ target = target.parentElement;
88
+ } else {
89
+ break;
90
+ }
91
+ }
92
+ ensureSlideIsInView(target);
93
+ }
94
+ wasInteractedWith = false;
95
+ });
96
+
97
+
98
+ };
99
+
100
+ function setCSSVariables() {
101
+ slider.container.style.setProperty('--slider-container-width', `${slider.details.containerWidth}px`);
102
+ slider.container.style.setProperty('--slider-scrollable-width', `${slider.details.scrollableAreaWidth}px`);
103
+ slider.container.style.setProperty('--slider-slides-count', `${slider.details.slideCount}`);
104
+ }
105
+
106
+ function setDataAttributes() {
107
+ slider.container.setAttribute('data-has-overflow', slider.details.hasOverflow ? 'true' : 'false');
108
+ }
109
+
110
+ function ensureSlideIsInView( slide: HTMLElement ) {
111
+ const slideRect = slide.getBoundingClientRect();
112
+ const sliderRect = slider.container.getBoundingClientRect();
113
+ const containerWidth = slider.container.offsetWidth;
114
+ const scrollLeft = slider.container.scrollLeft;
115
+ const slideStart = slideRect.left - sliderRect.left + scrollLeft;
116
+ const slideEnd = slideStart + slideRect.width;
117
+ let scrollTarget = null;
118
+ if (slideStart < scrollLeft) {
119
+ scrollTarget = slideStart;
120
+ } else if (slideEnd > scrollLeft + containerWidth) {
121
+ scrollTarget = slideEnd - containerWidth;
122
+ }
123
+ if (scrollTarget) {
124
+ slider.container.style.scrollSnapType = 'none';
125
+ slider.container.scrollLeft = scrollTarget;
126
+ // @todo resume scroll snapping but at least proximity gives a lot of trouble
127
+ // and it's not really needed for this use case but it would be nice to have
128
+ // it back in case it's needed. We need to calculate scrollLeft some other way
129
+ }
130
+ };
131
+
132
+ function moveToDirection(direction = "prev") {
133
+ const scrollStrategy = slider.options.scrollStrategy;
134
+ const scrollLeft = slider.container.scrollLeft;
135
+ const sliderRect = slider.container.getBoundingClientRect();
136
+ const containerWidth = slider.container.offsetWidth;
137
+ let targetScrollPosition = scrollLeft;
138
+ if (direction === 'prev') {
139
+ targetScrollPosition = Math.max(0, scrollLeft - slider.container.offsetWidth);
140
+ } else if (direction === 'next') {
141
+ targetScrollPosition = Math.min(slider.container.scrollWidth, scrollLeft + slider.container.offsetWidth);
142
+ }
143
+ if (scrollStrategy === 'fullSlide') {
144
+ let fullSldeTargetScrollPosition = null;
145
+ const slides = Array.from(slider.container.querySelectorAll(':scope > *'));
146
+
147
+ let gapSize = 0;
148
+
149
+ if (slides.length > 1) {
150
+ const firstSlideRect = slides[0].getBoundingClientRect();
151
+ const secondSlideRect = slides[1].getBoundingClientRect();
152
+ gapSize = secondSlideRect.left - firstSlideRect.right;
153
+ }
154
+
155
+ // extend targetScrollPosition to include gap
156
+ if (direction === 'prev') {
157
+ fullSldeTargetScrollPosition = Math.max(0, targetScrollPosition - gapSize);
158
+ } else {
159
+ fullSldeTargetScrollPosition = Math.min(slider.container.scrollWidth, targetScrollPosition + gapSize);
160
+ }
161
+
162
+ if (direction === 'next') {
163
+ let partialSlideFound = false;
164
+ for (let slide of slides) {
165
+ const slideRect = slide.getBoundingClientRect();
166
+ const slideStart = slideRect.left - sliderRect.left + scrollLeft;
167
+ const slideEnd = slideStart + slideRect.width;
168
+ if (slideStart < targetScrollPosition && slideEnd > targetScrollPosition) {
169
+ fullSldeTargetScrollPosition = slideStart;
170
+ partialSlideFound = true;
171
+ break;
172
+ }
173
+ }
174
+ if (!partialSlideFound) {
175
+ fullSldeTargetScrollPosition = Math.min(targetScrollPosition, slider.container.scrollWidth - slider.container.offsetWidth);
176
+ }
177
+ if (fullSldeTargetScrollPosition && fullSldeTargetScrollPosition > scrollLeft) {
178
+ targetScrollPosition = fullSldeTargetScrollPosition;
179
+ }
180
+ } else {
181
+ let partialSlideFound = false;
182
+ for (let slide of slides) {
183
+ const slideRect = slide.getBoundingClientRect();
184
+ const slideStart = slideRect.left - sliderRect.left + scrollLeft;
185
+ const slideEnd = slideStart + slideRect.width;
186
+ if (slideStart < scrollLeft && slideEnd > scrollLeft) {
187
+ fullSldeTargetScrollPosition = slideEnd - containerWidth;
188
+ partialSlideFound = true;
189
+ break;
190
+ }
191
+ }
192
+ if (!partialSlideFound) {
193
+ fullSldeTargetScrollPosition = Math.max(0, scrollLeft - containerWidth);
194
+ }
195
+ if (fullSldeTargetScrollPosition && fullSldeTargetScrollPosition < scrollLeft) {
196
+ targetScrollPosition = fullSldeTargetScrollPosition;
197
+ }
198
+ }
199
+ }
200
+ slider.container.style.scrollBehavior = 'smooth';
201
+ slider.container.scrollLeft = targetScrollPosition;
202
+ setTimeout(() => slider.container.style.scrollBehavior = '', 50);
203
+ }
204
+
205
+ function on(name: string, cb: any) {
206
+ if (!subs[name]) {
207
+ subs[name] = [];
208
+ }
209
+ subs[name].push(cb);
210
+ }
211
+
212
+ function emit(name: string) {
213
+ if (subs && subs[name]) {
214
+ subs[name].forEach(cb => {
215
+ cb(slider);
216
+ });
217
+ }
218
+ const optionCallBack = slider?.options?.[name];
219
+ if (typeof optionCallBack === 'function') {
220
+ optionCallBack(slider);
221
+ }
222
+ }
223
+
224
+ slider = <Slider>{
225
+ emit,
226
+ moveToDirection,
227
+ on,
228
+ options,
229
+ };
230
+
231
+ init();
232
+
233
+ return slider;
234
+ }
@@ -0,0 +1,41 @@
1
+ export type Slider<O = {}, C = {}, H extends string = string> = {
2
+ container: HTMLElement
3
+ emit: (name: H | SliderHooks) => void
4
+ moveToDirection: (
5
+ direction: 'prev' | 'next'
6
+ ) => void
7
+ on: (
8
+ name: H | SliderHooks,
9
+ cb: (props: Slider<O, C, H>) => void
10
+ ) => void
11
+ options: SliderOptions,
12
+ details: SliderDetails,
13
+ } & C;
14
+
15
+ export type SliderOptions = {
16
+ scrollBehavior: string;
17
+ scrollStrategy: string;
18
+ [key: string]: any;
19
+ }
20
+
21
+ export type SliderDetails = {
22
+ hasOverflow: boolean;
23
+ slideCount: number;
24
+ containerWidth: number;
25
+ scrollableAreaWidth: number;
26
+ amountOfPages: number;
27
+ currentPage: number;
28
+ }
29
+
30
+ export type SliderHooks =
31
+ | HOOK_CREATED
32
+ | HOOK_CONTENTS_CHANGED
33
+ | HOOK_DETAILS_CHANGED
34
+ | HOOK_CONTAINER_SIZE_CHANGED;
35
+
36
+ export type HOOK_CREATED = 'created';
37
+ export type HOOK_DETAILS_CHANGED = 'detailsChanged';
38
+ export type HOOK_CONTENTS_CHANGED = 'contentsChanged';
39
+ export type HOOK_CONTAINER_SIZE_CHANGED = 'containerSizeChanged';
40
+
41
+ export type SliderPlugin = (slider: Slider) => void;
@@ -0,0 +1,24 @@
1
+
2
+ function generateId( prefix : string, i = 1 ): string {
3
+ const id = `${prefix}-${i}`;
4
+ if ( document.getElementById( id ) ) {
5
+ return generateId( prefix, i + 1 );
6
+ }
7
+ return id;
8
+ }
9
+
10
+ function objectsAreEqual( obj1: any, obj2: any ) {
11
+ const keys1 = Object.keys(obj1);
12
+ const keys2 = Object.keys(obj2);
13
+ if (keys1.length !== keys2.length) {
14
+ return false;
15
+ }
16
+ for (let key of keys1) {
17
+ if (obj2.hasOwnProperty(key) === false || obj1[key] !== obj2[key]) {
18
+ return false;
19
+ }
20
+ }
21
+ return true;
22
+ }
23
+
24
+ export { generateId, objectsAreEqual };