@evermade/overflow-slider 4.0.0 → 4.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 (58) hide show
  1. package/README.md +50 -5
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.esm.js +39 -3
  4. package/dist/index.esm.js.map +1 -1
  5. package/dist/index.min.js +1 -1
  6. package/dist/index.min.js.map +1 -1
  7. package/dist/plugins/arrows/index.d.ts +1 -1
  8. package/dist/plugins/autoplay/index.d.ts +1 -1
  9. package/dist/plugins/classnames/index.d.ts +14 -0
  10. package/dist/plugins/classnames/index.esm.js +107 -0
  11. package/dist/plugins/classnames/index.min.js +1 -0
  12. package/dist/plugins/core/index.d.ts +10 -62
  13. package/dist/plugins/core/index.d2.ts +63 -10
  14. package/dist/plugins/dots/index.d.ts +2 -1
  15. package/dist/plugins/dots/index.esm.js +31 -19
  16. package/dist/plugins/dots/index.min.js +1 -1
  17. package/dist/plugins/drag-scrolling/index.d.ts +1 -1
  18. package/dist/plugins/fade/index.d.ts +1 -1
  19. package/dist/plugins/full-width/index.d.ts +2 -2
  20. package/dist/plugins/full-width/index.esm.js +36 -19
  21. package/dist/plugins/full-width/index.min.js +1 -1
  22. package/dist/plugins/scroll-indicator/index.d.ts +1 -1
  23. package/dist/plugins/skip-links/index.d.ts +1 -1
  24. package/dist/plugins/thumbnails/index.d.ts +1 -1
  25. package/docs/assets/demo.css +42 -0
  26. package/docs/assets/demo.js +62 -28
  27. package/docs/dist/index.d.ts +1 -1
  28. package/docs/dist/index.esm.js +39 -3
  29. package/docs/dist/index.esm.js.map +1 -1
  30. package/docs/dist/index.min.js +1 -1
  31. package/docs/dist/index.min.js.map +1 -1
  32. package/docs/dist/plugins/arrows/index.d.ts +1 -1
  33. package/docs/dist/plugins/autoplay/index.d.ts +1 -1
  34. package/docs/dist/plugins/classnames/index.d.ts +14 -0
  35. package/docs/dist/plugins/classnames/index.esm.js +107 -0
  36. package/docs/dist/plugins/classnames/index.min.js +1 -0
  37. package/docs/dist/plugins/core/index.d.ts +63 -10
  38. package/docs/dist/plugins/core/index.d2.ts +10 -62
  39. package/docs/dist/plugins/dots/index.d.ts +2 -1
  40. package/docs/dist/plugins/dots/index.esm.js +31 -19
  41. package/docs/dist/plugins/dots/index.min.js +1 -1
  42. package/docs/dist/plugins/drag-scrolling/index.d.ts +1 -1
  43. package/docs/dist/plugins/fade/index.d.ts +1 -1
  44. package/docs/dist/plugins/full-width/index.d.ts +2 -2
  45. package/docs/dist/plugins/full-width/index.esm.js +36 -19
  46. package/docs/dist/plugins/full-width/index.min.js +1 -1
  47. package/docs/dist/plugins/infinite-scroll/index.d.ts +1 -1
  48. package/docs/dist/plugins/scroll-indicator/index.d.ts +1 -1
  49. package/docs/dist/plugins/skip-links/index.d.ts +1 -1
  50. package/docs/dist/plugins/thumbnails/index.d.ts +1 -1
  51. package/docs/index.html +39 -17
  52. package/package.json +6 -6
  53. package/src/core/slider.ts +42 -4
  54. package/src/core/types.ts +1 -0
  55. package/src/plugins/classnames/index.ts +145 -0
  56. package/src/plugins/dots/index.ts +28 -16
  57. package/src/plugins/full-width/index.ts +41 -21
  58. package/src/plugins/infinite-scroll/index.ts +0 -109
package/docs/index.html CHANGED
@@ -158,7 +158,7 @@
158
158
 
159
159
  <div class="entry__item">
160
160
  <h3>Dots</h3>
161
- <p>Display dots indicator. Uses DotsPlugin.</p>
161
+ <p>Display dots indicator (by slide). Uses DotsPlugin.</p>
162
162
  <div class="overflow-slider example-container example-container-1-dots">
163
163
  <a href="#1" class="example-item example-item--1">1</a>
164
164
  <a href="#2" class="example-item example-item--2">2</a>
@@ -167,6 +167,19 @@
167
167
  <a href="#5" class="example-item example-item--5">5</a>
168
168
  <a href="#6" class="example-item example-item--6">6</a>
169
169
  </div>
170
+ <p style="margin-top:1.5rem;">Display dots indicator (by view).</p>
171
+ <div class="overflow-slider example-container example-container-1-dots-view">
172
+ <a href="#1" class="example-item example-item--1">1</a>
173
+ <a href="#2" class="example-item example-item--2">2</a>
174
+ <a href="#3" class="example-item example-item--3">3</a>
175
+ <a href="#4" class="example-item example-item--4">4</a>
176
+ <a href="#5" class="example-item example-item--5">5</a>
177
+ <a href="#6" class="example-item example-item--6">6</a>
178
+ <a href="#7" class="example-item example-item--7">7</a>
179
+ <a href="#8" class="example-item example-item--8">8</a>
180
+ <a href="#9" class="example-item example-item--9">9</a>
181
+ <a href="#10" class="example-item example-item--10">10</a>
182
+ </div>
170
183
  </div>
171
184
 
172
185
  <div class="entry__item">
@@ -191,10 +204,23 @@
191
204
  </div>
192
205
 
193
206
  <div class="entry__item">
194
- <h3>Autoplay (slide)</h3>
195
- <p>Use autoplay and set delay. If user prefers reduced motion, autoplay does not execute.</p>
196
- <div class="example-container-1-autoplay-wrap-slide">
197
- <div class="overflow-slider example-container example-container-1-autoplay-slide">
207
+ <h3>Classnames</h3>
208
+ <p>Toggle classes when slides are visible, partly visible or hidden. Uses ClassNamesPlugin.</p>
209
+ <div class="overflow-slider example-container example-container-1-classname-opacity">
210
+ <a href="#1" class="example-item example-item--1">1</a>
211
+ <a href="#2" class="example-item example-item--2">2</a>
212
+ <a href="#3" class="example-item example-item--3">3</a>
213
+ <a href="#4" class="example-item example-item--4">4</a>
214
+ <a href="#5" class="example-item example-item--5">5</a>
215
+ <a href="#6" class="example-item example-item--6">6</a>
216
+ <a href="#7" class="example-item example-item--7">7</a>
217
+ <a href="#8" class="example-item example-item--8">8</a>
218
+ <a href="#9" class="example-item example-item--9">9</a>
219
+ <a href="#10" class="example-item example-item--10">10</a>
220
+ </div>
221
+ <p style="margin-top:1.5rem;">Display separate effects when slides are partly visible. freezeStateOnVisible: true which means we don't hide once visible slides</p>
222
+ <div class="example-container-1-classname-partly-wrapper">
223
+ <div class="overflow-slider example-container example-container-1-classname-partly">
198
224
  <a href="#1" class="example-item example-item--1">1</a>
199
225
  <a href="#2" class="example-item example-item--2">2</a>
200
226
  <a href="#3" class="example-item example-item--3">3</a>
@@ -210,10 +236,10 @@
210
236
  </div>
211
237
 
212
238
  <div class="entry__item">
213
- <h3>Autoplay (view)</h3>
239
+ <h3>Autoplay (slide)</h3>
214
240
  <p>Use autoplay and set delay. If user prefers reduced motion, autoplay does not execute.</p>
215
- <div class="example-container-1-autoplay-wrap-view">
216
- <div class="overflow-slider example-container example-container-1-autoplay-view">
241
+ <div class="example-container-1-autoplay-wrap-slide">
242
+ <div class="overflow-slider example-container example-container-1-autoplay-slide">
217
243
  <a href="#1" class="example-item example-item--1">1</a>
218
244
  <a href="#2" class="example-item example-item--2">2</a>
219
245
  <a href="#3" class="example-item example-item--3">3</a>
@@ -227,12 +253,12 @@
227
253
  </div>
228
254
  </div>
229
255
  </div>
230
- <!--
256
+
231
257
  <div class="entry__item">
232
- <h3>Infinite scroll</h3>
233
- <p>This is a bad idea. Please don't use.</p>
234
- <div class="example-container-1-infinite-wrap">
235
- <div class="overflow-slider example-container example-container-1-infinite">
258
+ <h3>Autoplay (view)</h3>
259
+ <p>Use autoplay and set delay. If user prefers reduced motion, autoplay does not execute.</p>
260
+ <div class="example-container-1-autoplay-wrap-view">
261
+ <div class="overflow-slider example-container example-container-1-autoplay-view">
236
262
  <a href="#1" class="example-item example-item--1">1</a>
237
263
  <a href="#2" class="example-item example-item--2">2</a>
238
264
  <a href="#3" class="example-item example-item--3">3</a>
@@ -243,13 +269,9 @@
243
269
  <a href="#8" class="example-item example-item--8">8</a>
244
270
  <a href="#9" class="example-item example-item--9">9</a>
245
271
  <a href="#10" class="example-item example-item--10">10</a>
246
- <a href="#11" class="example-item example-item--11">11</a>
247
- <a href="#12" class="example-item example-item--12">12</a>
248
- <a href="#13" class="example-item example-item--13">13</a>
249
272
  </div>
250
273
  </div>
251
274
  </div>
252
- -->
253
275
 
254
276
  </section>
255
277
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evermade/overflow-slider",
3
- "version": "4.0.0",
3
+ "version": "4.2.0",
4
4
  "description": "Accessible slider that is powered by overflow: auto.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -37,6 +37,11 @@
37
37
  "require": "./dist/plugins/autoplay/index.min.js",
38
38
  "types": "./dist/plugins/autoplay/index.d.ts"
39
39
  },
40
+ "./plugins/classnames": {
41
+ "import": "./dist/plugins/classnames/index.esm.js",
42
+ "require": "./dist/plugins/classnames/index.min.js",
43
+ "types": "./dist/plugins/classnames/index.d.ts"
44
+ },
40
45
  "./plugins/full-width": {
41
46
  "import": "./dist/plugins/full-width/index.esm.js",
42
47
  "require": "./dist/plugins/full-width/index.min.js",
@@ -57,11 +62,6 @@
57
62
  "require": "./dist/plugins/fade/index.min.js",
58
63
  "types": "./dist/plugins/fade/index.d.ts"
59
64
  },
60
- "./plugins/infinite-scroll": {
61
- "import": "./dist/plugins/infinite-scroll/index.esm.js",
62
- "require": "./dist/plugins/infinite-scroll/index.min.js",
63
- "types": "./dist/plugins/infinite-scroll/index.d.ts"
64
- },
65
65
  "./style.css": "./dist/overflow-slider.css",
66
66
  "./style": "./dist/overflow-slider.css",
67
67
  "./mixins.scss": "./dist/mixins.scss",
@@ -6,6 +6,18 @@ export default function Slider( container: HTMLElement, options : SliderOptionAr
6
6
  let slider: Slider;
7
7
  let subs: { [key: string]: SliderCallback[] } = {};
8
8
 
9
+ const overrideTransitions = () => {
10
+ slider.slides.forEach( ( slide ) => {
11
+ slide.style.transition = 'none';
12
+ });
13
+ };
14
+
15
+ const restoreTransitions = () => {
16
+ slider.slides.forEach( ( slide ) => {
17
+ slide.style.removeProperty('transition');
18
+ });
19
+ };
20
+
9
21
  function init() {
10
22
  slider.container = container;
11
23
  // ensure container has id
@@ -15,6 +27,8 @@ export default function Slider( container: HTMLElement, options : SliderOptionAr
15
27
  container.setAttribute( 'id', containerId );
16
28
  }
17
29
  setSlides();
30
+ // CSS transitions can cause delays for calculations
31
+ overrideTransitions();
18
32
  setDetails(true);
19
33
  setActiveSlideIdx();
20
34
  slider.on('contentsChanged', () => {
@@ -44,12 +58,20 @@ export default function Slider( container: HTMLElement, options : SliderOptionAr
44
58
  for (const plugin of plugins) {
45
59
  plugin(slider);
46
60
  }
61
+ // plugins may mutate layout: refresh details and derived data after they run
62
+ // setTimeout( () => {
63
+ setDetails();
64
+ setActiveSlideIdx();
65
+ setCSSVariables();
66
+ slider.emit('pluginsLoaded');
67
+ // }, 250 );
47
68
  }
48
69
  slider.on('detailsChanged', () => {
49
70
  setDataAttributes();
50
71
  setCSSVariables();
51
72
  });
52
73
  slider.emit('created');
74
+ restoreTransitions();
53
75
  slider.container.setAttribute('data-ready', 'true');
54
76
  };
55
77
 
@@ -491,19 +513,35 @@ export default function Slider( container: HTMLElement, options : SliderOptionAr
491
513
  const containerRect = container.getBoundingClientRect();
492
514
  const factor = rtl ? -1 : 1;
493
515
 
516
+ // Calculate target area offset if targetWidth is defined
517
+ let targetAreaOffset = 0;
518
+ if (typeof options.targetWidth === 'function') {
519
+ try {
520
+ const targetWidth = options.targetWidth(slider);
521
+ const containerWidth = containerRect.width;
522
+ if (Number.isFinite(targetWidth) && targetWidth > 0 && targetWidth < containerWidth) {
523
+ targetAreaOffset = (containerWidth - targetWidth) / 2;
524
+ }
525
+ } catch (error) {
526
+ // ignore errors, use default offset of 0
527
+ }
528
+ }
529
+
494
530
  // Build slide metadata
495
531
  const slideData = [...slides].map(slide => {
496
532
  const { width } = slide.getBoundingClientRect();
497
533
  const slideRect = slide.getBoundingClientRect();
498
534
 
499
- // position relative to containers left edge
535
+ // position relative to container's left edge
500
536
  const relativeStart = (slideRect.left - containerRect.left) + scrollPos;
537
+ // Adjust trigger point to align with target area start instead of container edge
538
+ const alignmentPoint = relativeStart - targetAreaOffset;
501
539
  const triggerPoint = Math.min(
502
- relativeStart + width / 2,
503
- relativeStart + emulateScrollSnapMaxThreshold
540
+ alignmentPoint + width / 2,
541
+ alignmentPoint + emulateScrollSnapMaxThreshold
504
542
  );
505
543
 
506
- return { start: relativeStart, trigger: triggerPoint };
544
+ return { start: relativeStart - targetAreaOffset, trigger: triggerPoint };
507
545
  });
508
546
 
509
547
  // Pick the target start based on drag direction
package/src/core/types.ts CHANGED
@@ -65,6 +65,7 @@ export type SliderOptionArgs = {
65
65
  emulateScrollSnapMaxThreshold?: number;
66
66
  cssVariableContainer?: HTMLElement;
67
67
  rtl?: boolean;
68
+ targetWidth?: ( slider: Slider ) => number;
68
69
  [key: string]: unknown;
69
70
  }
70
71
 
@@ -0,0 +1,145 @@
1
+ import { Slider, DeepPartial } from '../../core/types';
2
+
3
+ export type ClassnameOptions = {
4
+ classNames: {
5
+ visible: string;
6
+ partlyVisible: string;
7
+ hidden: string;
8
+ },
9
+ freezeStateOnVisible: boolean;
10
+ };
11
+
12
+ const DEFAULT_CLASS_NAMES: ClassnameOptions['classNames'] = {
13
+ visible: 'is-visible',
14
+ partlyVisible: 'is-partly-visible',
15
+ hidden: 'is-hidden',
16
+ }
17
+
18
+ type VisibilityState = keyof ClassnameOptions['classNames'];
19
+
20
+ export default function ClassNamesPlugin( args?: DeepPartial<ClassnameOptions> ) {
21
+ return ( slider: Slider ) => {
22
+
23
+ const providedClassNames = args?.classNames ?? (args as { classnames?: DeepPartial<ClassnameOptions['classNames']> })?.classnames;
24
+
25
+ const options = <ClassnameOptions>{
26
+ classNames: {
27
+ ...DEFAULT_CLASS_NAMES,
28
+ ...providedClassNames ?? {},
29
+ },
30
+ freezeStateOnVisible: args?.freezeStateOnVisible ?? false,
31
+ };
32
+
33
+ const slideStates = new WeakMap<HTMLElement, VisibilityState>();
34
+ const uniqueClassNames = Array.from(
35
+ new Set(
36
+ Object.values( options.classNames ).filter( ( className ): className is string => Boolean( className ) )
37
+ )
38
+ );
39
+
40
+ const getTargetBounds = () => {
41
+ const sliderRect = slider.container.getBoundingClientRect();
42
+ const sliderWidth = sliderRect.width;
43
+ if (!sliderWidth) {
44
+ return { targetStart: sliderRect.left, targetEnd: sliderRect.right };
45
+ }
46
+
47
+ let targetWidth = 0;
48
+ if ( typeof slider.options.targetWidth === 'function' ) {
49
+ try {
50
+ targetWidth = slider.options.targetWidth( slider );
51
+ } catch ( error ) {
52
+ targetWidth = 0;
53
+ }
54
+ }
55
+
56
+ if ( !Number.isFinite( targetWidth ) || targetWidth <= 0 ) {
57
+ targetWidth = sliderWidth;
58
+ }
59
+
60
+ const effectiveTargetWidth = Math.min( targetWidth, sliderWidth );
61
+ const offset = ( sliderWidth - effectiveTargetWidth ) / 2;
62
+ const clampedOffset = Math.max( offset, 0 );
63
+
64
+ return {
65
+ targetStart: sliderRect.left + clampedOffset,
66
+ targetEnd: sliderRect.right - clampedOffset,
67
+ };
68
+ };
69
+
70
+ const update = () => {
71
+ const { targetStart, targetEnd } = getTargetBounds();
72
+
73
+ slider.slides.forEach( ( slide ) => {
74
+ const slideRect = slide.getBoundingClientRect();
75
+ const slideLeft = slideRect.left;
76
+ const slideRight = slideRect.right;
77
+
78
+ const tolerance = 2;
79
+ const overlapsTarget = (slideRight - tolerance) > targetStart && (slideLeft + tolerance) < targetEnd;
80
+ const fullyInsideTarget = (slideLeft + tolerance) >= targetStart && (slideRight - tolerance) <= targetEnd;
81
+
82
+ let nextState: VisibilityState = 'hidden';
83
+ if ( overlapsTarget ) {
84
+ nextState = fullyInsideTarget ? 'visible' : 'partlyVisible';
85
+ }
86
+
87
+ const prevState = slideStates.get( slide );
88
+
89
+ // If freezeStateOnVisible is enabled and slide was previously visible, keep it frozen
90
+ if ( options.freezeStateOnVisible && prevState === 'visible' ) {
91
+ return;
92
+ }
93
+
94
+ if ( prevState === nextState ) {
95
+ return;
96
+ }
97
+
98
+ const nextClass = options.classNames[ nextState ];
99
+ if ( prevState ) {
100
+ const prevClass = options.classNames[ prevState ];
101
+ if ( prevClass !== nextClass && prevClass ) {
102
+ slide.classList.remove( prevClass );
103
+ }
104
+ } else {
105
+ uniqueClassNames.forEach( ( className ) => {
106
+ if ( className !== nextClass ) {
107
+ slide.classList.remove( className );
108
+ }
109
+ } );
110
+ }
111
+
112
+ if ( nextClass && !slide.classList.contains( nextClass ) ) {
113
+ slide.classList.add( nextClass );
114
+ }
115
+
116
+ slideStates.set( slide, nextState );
117
+ });
118
+ };
119
+
120
+ slider.on( 'created', update );
121
+ slider.on( 'pluginsLoaded', update );
122
+ slider.on( 'fullWidthPluginUpdate', update );
123
+ slider.on( 'contentsChanged', update );
124
+ slider.on( 'containerSizeChanged', update );
125
+ slider.on( 'detailsChanged', update );
126
+ slider.on( 'scrollEnd', update );
127
+ slider.on( 'scrollStart', update );
128
+
129
+ requestAnimationFrame(() => {
130
+ requestAnimationFrame(() => update());
131
+ });
132
+
133
+ let requestId = 0;
134
+ const debouncedUpdate = () => {
135
+ if ( requestId ) {
136
+ window.cancelAnimationFrame( requestId );
137
+ }
138
+ requestId = window.requestAnimationFrame(() => {
139
+ update();
140
+ });
141
+ };
142
+ slider.on('scroll', debouncedUpdate);
143
+
144
+ };
145
+ }
@@ -1,6 +1,7 @@
1
1
  import { Slider, DeepPartial } from '../../core/types';
2
2
 
3
3
  export type DotsOptions = {
4
+ type: 'view' | 'slide';
4
5
  texts: {
5
6
  dotDescription: string;
6
7
  },
@@ -23,6 +24,7 @@ const DEFAULT_CLASS_NAMES = {
23
24
  export default function DotsPlugin( args?: DeepPartial<DotsOptions> ) {
24
25
  return ( slider: Slider ) => {
25
26
  const options = <DotsOptions>{
27
+ type: args?.type ?? 'slide',
26
28
  texts: {
27
29
  ...DEFAULT_TEXTS,
28
30
  ...args?.texts || []
@@ -45,20 +47,20 @@ export default function DotsPlugin( args?: DeepPartial<DotsOptions> ) {
45
47
 
46
48
  const dotsList = document.createElement( 'ul' );
47
49
 
48
- const pages = slider.details.slideCount;
49
- const currentItem = slider.activeSlideIdx;
50
+ const count = options.type === 'view' ? slider.details.amountOfPages : slider.details.slideCount;
51
+ const currentIndex = options.type === 'view' ? slider.details.currentPage : slider.activeSlideIdx;
50
52
 
51
- if ( pages <= 1 ) {
53
+ if ( count <= 1 ) {
52
54
  return;
53
55
  }
54
56
 
55
- for ( let i = 0; i < pages; i++ ) {
57
+ for ( let i = 0; i < count; i++ ) {
56
58
  const dotListItem = document.createElement( 'li' );
57
59
  const dot = document.createElement( 'button' );
58
60
  dot.setAttribute( 'type', 'button' );
59
61
  dot.setAttribute( 'class', options.classNames.dotsItem );
60
- dot.setAttribute( 'aria-label', options.texts.dotDescription.replace( '%d', ( i + 1 ).toString() ).replace( '%d', pages.toString() ) );
61
- dot.setAttribute( 'aria-pressed', ( i === currentItem ).toString() );
62
+ dot.setAttribute( 'aria-label', options.texts.dotDescription.replace( '%d', ( i + 1 ).toString() ).replace( '%d', count.toString() ) );
63
+ dot.setAttribute( 'aria-pressed', ( i === currentIndex ).toString() );
62
64
  dot.setAttribute( 'data-item', ( i + 1 ).toString() );
63
65
  dotListItem.appendChild( dot );
64
66
  dotsList.appendChild( dotListItem );
@@ -71,23 +73,23 @@ export default function DotsPlugin( args?: DeepPartial<DotsOptions> ) {
71
73
  }
72
74
  const currentItem = parseInt( currentItemItem.getAttribute( 'data-item' ) ?? '1' );
73
75
  if ( e.key === 'ArrowLeft' ) {
74
- const previousPage = currentItem - 1;
75
- if ( previousPage > 0 ) {
76
- const matchingDot = dots.querySelector( `[data-item="${previousPage}"]` );
76
+ const previousIndex = currentItem - 1;
77
+ if ( previousIndex > 0 ) {
78
+ const matchingDot = dots.querySelector( `[data-item="${previousIndex}"]` );
77
79
  if ( matchingDot ) {
78
80
  ( <HTMLElement>matchingDot ).focus();
79
81
  }
80
- activateDot( previousPage );
82
+ activateDot( previousIndex );
81
83
  }
82
84
  }
83
85
  if ( e.key === 'ArrowRight' ) {
84
- const nextPage = currentItem + 1;
85
- if ( nextPage <= pages ) {
86
- const matchingDot = dots.querySelector( `[data-item="${nextPage}"]` );
86
+ const nextIndex = currentItem + 1;
87
+ if ( nextIndex <= count ) {
88
+ const matchingDot = dots.querySelector( `[data-item="${nextIndex}"]` );
87
89
  if ( matchingDot ) {
88
90
  ( <HTMLElement>matchingDot ).focus();
89
91
  }
90
- activateDot( nextPage );
92
+ activateDot( nextIndex );
91
93
  }
92
94
  }
93
95
  } );
@@ -104,8 +106,17 @@ export default function DotsPlugin( args?: DeepPartial<DotsOptions> ) {
104
106
  }
105
107
  };
106
108
 
107
- const activateDot = ( item: number ) => {
108
- slider.moveToSlide( item - 1 );
109
+ const activateDot = ( index: number ) => {
110
+ if ( options.type === 'view' ) {
111
+ const targetPosition = slider.details.containerWidth * ( index - 1 );
112
+ const scrollLeft = slider.options.rtl ? -targetPosition : targetPosition;
113
+ slider.container.scrollTo({
114
+ left: scrollLeft,
115
+ behavior: slider.options.scrollBehavior as ScrollBehavior
116
+ });
117
+ } else {
118
+ slider.moveToSlide( index - 1 );
119
+ }
109
120
  };
110
121
 
111
122
  buildDots();
@@ -119,5 +130,6 @@ export default function DotsPlugin( args?: DeepPartial<DotsOptions> ) {
119
130
  slider.on( 'scrollEnd', buildDots );
120
131
  slider.on( 'contentsChanged', buildDots );
121
132
  slider.on( 'containerSizeChanged', buildDots );
133
+ slider.on( 'detailsChanged', buildDots );
122
134
  };
123
135
  };
@@ -3,7 +3,7 @@ import { Slider, DeepPartial } from '../../core/types';
3
3
  const DEFAULT_TARGET_WIDTH = ( slider: Slider ) => slider.container.parentElement?.offsetWidth ?? window.innerWidth;
4
4
 
5
5
  export type FullWidthOptions = {
6
- targetWidth: ( slider: Slider ) => number,
6
+ targetWidth?: ( slider: Slider ) => number,
7
7
  addMarginBefore: boolean,
8
8
  addMarginAfter: boolean,
9
9
  };
@@ -12,11 +12,22 @@ export default function FullWidthPlugin( args?: DeepPartial<FullWidthOptions> )
12
12
  return ( slider: Slider ) => {
13
13
 
14
14
  const options = <FullWidthOptions>{
15
- targetWidth: args?.targetWidth ?? null,
15
+ targetWidth: args?.targetWidth ?? undefined,
16
16
  addMarginBefore: args?.addMarginBefore ?? true,
17
17
  addMarginAfter: args?.addMarginAfter ?? true,
18
18
  };
19
19
 
20
+ if ( typeof slider.options.targetWidth !== 'function' ) {
21
+ slider.options.targetWidth = options.targetWidth ?? DEFAULT_TARGET_WIDTH;
22
+ }
23
+
24
+ const resolveTargetWidth = () => {
25
+ if ( typeof slider.options.targetWidth === 'function' ) {
26
+ return slider.options.targetWidth;
27
+ }
28
+ return options.targetWidth ?? DEFAULT_TARGET_WIDTH;
29
+ };
30
+
20
31
  const update = () => {
21
32
  const slides = slider.container.querySelectorAll( slider.options.slidesSelector );
22
33
 
@@ -24,34 +35,43 @@ export default function FullWidthPlugin( args?: DeepPartial<FullWidthOptions> )
24
35
  return;
25
36
  }
26
37
 
38
+ const targetWidthFn = resolveTargetWidth();
39
+ const rawMargin = ( window.innerWidth - targetWidthFn( slider ) ) / 2;
40
+ const marginAmount = Math.max( 0, Math.floor( rawMargin ) );
41
+ const marginValue = marginAmount ? `${marginAmount}px` : '';
42
+
43
+ slides.forEach( ( slide ) => {
44
+ const element = slide as HTMLElement;
45
+ element.style.marginInlineStart = '';
46
+ element.style.marginInlineEnd = '';
47
+ } );
48
+
27
49
  const firstSlide = slides[0] as HTMLElement;
28
50
  const lastSlide = slides[slides.length - 1] as HTMLElement;
29
51
 
30
- const marginAmount = Math.floor((window.innerWidth - getTargetWidth()) / 2);
31
52
  if ( options.addMarginBefore ) {
32
- firstSlide.style.marginInlineStart = `${marginAmount}px`;
53
+ firstSlide.style.marginInlineStart = marginValue;
54
+ slider.container.style.setProperty( 'scroll-padding-inline-start', marginValue || '0px' );
33
55
  }
34
- if ( options.addMarginAfter ) {
35
- lastSlide.style.marginInlineEnd = `${marginAmount}px`;
56
+ else {
57
+ slider.container.style.removeProperty( 'scroll-padding-inline-start' );
36
58
  }
37
- slider.container.setAttribute( 'data-full-width-offset', marginAmount.toString() );
38
- setCSS();
39
- };
40
-
41
- const getTargetWidth = () => {
42
- if ( typeof options.targetWidth === 'function' ) {
43
- return options.targetWidth(slider);
59
+ if ( options.addMarginAfter ) {
60
+ lastSlide.style.marginInlineEnd = marginValue;
61
+ slider.container.style.setProperty( 'scroll-padding-inline-end', marginValue || '0px' );
44
62
  }
45
- if ( typeof slider.options.targetWidth === 'function' ) {
46
- return slider.options.targetWidth(slider);
63
+ else {
64
+ slider.container.style.removeProperty( 'scroll-padding-inline-end' );
47
65
  }
48
- return DEFAULT_TARGET_WIDTH(slider);
49
- }
50
66
 
51
- const setCSS = () => {
52
- if ( typeof slider.options.targetWidth === 'function' ) {
53
- slider.options.cssVariableContainer.style.setProperty('--slider-container-target-width', `${getTargetWidth()}px`);
54
- }
67
+ slider.container.setAttribute( 'data-full-width-offset', `${marginAmount}` );
68
+ setCSS( targetWidthFn );
69
+ slider.emit( 'fullWidthPluginUpdate' );
70
+ };
71
+
72
+ const setCSS = ( targetWidthFn: ( slider: Slider ) => number ) => {
73
+ const width = targetWidthFn( slider );
74
+ slider.options.cssVariableContainer.style.setProperty('--slider-container-target-width', `${width}px`);
55
75
  };
56
76
 
57
77
  update();