@evermade/overflow-slider 4.2.1 → 4.2.3

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/.github/workflows/npm-publish.yml +40 -22
  2. package/.github/workflows/publish.yml +35 -0
  3. package/CHANGELOG.md +116 -0
  4. package/README.md +55 -107
  5. package/RELEASE.md +44 -0
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.esm.js +107 -15
  8. package/dist/index.esm.js.map +1 -1
  9. package/dist/index.min.js +1 -1
  10. package/dist/index.min.js.map +1 -1
  11. package/dist/overflow-slider.css +1 -1
  12. package/dist/plugins/arrows/index.d.ts +1 -1
  13. package/dist/plugins/autoplay/index.d.ts +1 -1
  14. package/dist/plugins/classnames/index.d.ts +1 -1
  15. package/dist/plugins/core/index.d.ts +10 -63
  16. package/dist/plugins/core/index.d2.ts +64 -10
  17. package/dist/plugins/dots/index.d.ts +1 -1
  18. package/dist/plugins/drag-scrolling/index.d.ts +1 -1
  19. package/dist/plugins/fade/index.d.ts +1 -1
  20. package/dist/plugins/full-width/index.d.ts +1 -1
  21. package/dist/plugins/scroll-indicator/index.d.ts +1 -1
  22. package/dist/plugins/skip-links/index.d.ts +1 -1
  23. package/dist/plugins/thumbnails/index.d.ts +3 -3
  24. package/dist/plugins/thumbnails/index.esm.js +4 -4
  25. package/dist/plugins/thumbnails/index.min.js +1 -1
  26. package/docs/assets/demo.css +5 -0
  27. package/docs/assets/demo.js +11 -0
  28. package/docs/dist/index.d.ts +1 -1
  29. package/docs/dist/index.esm.js +107 -15
  30. package/docs/dist/index.esm.js.map +1 -1
  31. package/docs/dist/index.min.js +1 -1
  32. package/docs/dist/index.min.js.map +1 -1
  33. package/docs/dist/overflow-slider.css +1 -1
  34. package/docs/dist/plugins/arrows/index.d.ts +1 -1
  35. package/docs/dist/plugins/autoplay/index.d.ts +1 -1
  36. package/docs/dist/plugins/classnames/index.d.ts +1 -1
  37. package/docs/dist/plugins/core/index.d.ts +10 -63
  38. package/docs/dist/plugins/core/index.d2.ts +64 -10
  39. package/docs/dist/plugins/dots/index.d.ts +1 -1
  40. package/docs/dist/plugins/drag-scrolling/index.d.ts +1 -1
  41. package/docs/dist/plugins/fade/index.d.ts +1 -1
  42. package/docs/dist/plugins/full-width/index.d.ts +1 -1
  43. package/docs/dist/plugins/scroll-indicator/index.d.ts +1 -1
  44. package/docs/dist/plugins/skip-links/index.d.ts +1 -1
  45. package/docs/dist/plugins/thumbnails/index.d.ts +3 -3
  46. package/docs/dist/plugins/thumbnails/index.esm.js +4 -4
  47. package/docs/dist/plugins/thumbnails/index.min.js +1 -1
  48. package/docs/index.html +29 -0
  49. package/package.json +1 -1
  50. package/src/core/slider.ts +111 -12
  51. package/src/core/types.ts +4 -1
  52. package/src/overflow-slider.scss +1 -0
  53. package/src/plugins/thumbnails/index.ts +4 -4
@@ -1,4 +1,4 @@
1
- import { DeepPartial, Slider } from '../core/index.js';
1
+ import { DeepPartial, Slider } from '../core/index.d2.ts';
2
2
 
3
3
  type ArrowsMovementTypes = 'view' | 'slide';
4
4
  type ArrowsOptions = {
@@ -1,4 +1,4 @@
1
- import { DeepPartial, Slider } from '../core/index.js';
1
+ import { DeepPartial, Slider } from '../core/index.d2.ts';
2
2
 
3
3
  type AutoplayMovementTypes = 'view' | 'slide';
4
4
  type AutoplayPluginOptions = {
@@ -1,4 +1,4 @@
1
- import { DeepPartial, Slider } from '../core/index.js';
1
+ import { DeepPartial, Slider } from '../core/index.d2.ts';
2
2
 
3
3
  type ClassnameOptions = {
4
4
  classNames: {
@@ -1,10 +1,12 @@
1
- type Slider<O = {}, C = {}, H extends string = string> = {
1
+ import { SliderOptionArgs, SliderPlugin, SliderCallback, SliderOptions, SliderDetails } from './index.d2.ts';
2
+
3
+ declare function OverflowSlider(container: HTMLElement, options?: SliderOptionArgs, plugins?: SliderPlugin[]): {
2
4
  container: HTMLElement;
3
5
  slides: HTMLElement[];
4
- emit: (name: H | SliderHooks) => void;
5
- moveToDirection: (direction: 'prev' | 'next') => void;
6
- moveToSlideInDirection: (direction: 'prev' | 'next') => void;
7
- snapToClosestSlide: (direction: 'prev' | 'next') => void;
6
+ emit: (name: string) => void;
7
+ moveToDirection: (direction: "prev" | "next") => void;
8
+ moveToSlideInDirection: (direction: "prev" | "next") => void;
9
+ snapToClosestSlide: (direction: "prev" | "next") => void;
8
10
  moveToSlide: (index: number) => void;
9
11
  canMoveToSlide: (index: number) => boolean;
10
12
  getInclusiveScrollWidth: () => number;
@@ -14,65 +16,10 @@ type Slider<O = {}, C = {}, H extends string = string> = {
14
16
  getScrollLeft: () => number;
15
17
  setScrollLeft: (value: number) => void;
16
18
  setActiveSlideIdx: () => void;
17
- on: (name: H | SliderHooks, cb: SliderCallback) => void;
19
+ on: (name: string, cb: SliderCallback) => void;
18
20
  options: SliderOptions;
19
21
  details: SliderDetails;
20
22
  activeSlideIdx: number;
21
- } & C;
22
- type SliderCallback<O = {}, C = {}, H extends string = string> = (props: Slider<O, C, H>) => void;
23
- /**
24
- * Recursively makes all properties of T optional.
25
- * @see https://www.typescriptlang.org/docs/handbook/utility-types.html#mapped-types
26
- */
27
- type DeepPartial<T> = {
28
- [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
29
- };
30
- type SliderOptions = {
31
- scrollBehavior: string;
32
- scrollStrategy: string;
33
- slidesSelector: string;
34
- emulateScrollSnap: boolean;
35
- emulateScrollSnapMaxThreshold?: number;
36
- cssVariableContainer: HTMLElement;
37
- rtl: boolean;
38
- targetWidth?: (slider: Slider) => number;
39
- [key: string]: unknown;
40
- };
41
- type SliderOptionArgs = {
42
- scrollBehavior?: 'smooth' | 'auto';
43
- scrollStrategy?: 'fullSlide' | 'partialSlide';
44
- slidesSelector?: string;
45
- emulateScrollSnap?: boolean;
46
- emulateScrollSnapMaxThreshold?: number;
47
- cssVariableContainer?: HTMLElement;
48
- rtl?: boolean;
49
- targetWidth?: (slider: Slider) => number;
50
- [key: string]: unknown;
51
- };
52
- type SliderDetails = {
53
- hasOverflow: boolean;
54
- slideCount: number;
55
- containerWidth: number;
56
- containerHeight: number;
57
- scrollableAreaWidth: number;
58
- amountOfPages: number;
59
- currentPage: number;
60
- };
61
- type SliderHooks = HOOK_CREATED | HOOK_CONTENTS_CHANGED | HOOK_DETAILS_CHANGED | HOOK_CONTAINER_SIZE_CHANGED | HOOK_ACTIVE_SLIDE_CHANGED | HOOK_SCROLL_START | HOOK_SCROLL | HOOK_SCROLL_END | HOOK_NATIVE_SCROLL_START | HOOK_NATIVE_SCROLL | HOOK_NATIVE_SCROLL_END | HOOK_PROGRAMMATIC_SCROLL_START | HOOK_PROGRAMMATIC_SCROLL | HOOK_PROGRAMMATIC_SCROLL_END;
62
- type HOOK_CREATED = 'created';
63
- type HOOK_DETAILS_CHANGED = 'detailsChanged';
64
- type HOOK_CONTENTS_CHANGED = 'contentsChanged';
65
- type HOOK_CONTAINER_SIZE_CHANGED = 'containerSizeChanged';
66
- type HOOK_ACTIVE_SLIDE_CHANGED = 'activeSlideChanged';
67
- type HOOK_SCROLL_START = 'scrollStart';
68
- type HOOK_SCROLL = 'scroll';
69
- type HOOK_SCROLL_END = 'scrollEnd';
70
- type HOOK_NATIVE_SCROLL_START = 'nativeScrollStart';
71
- type HOOK_NATIVE_SCROLL = 'nativeScroll';
72
- type HOOK_NATIVE_SCROLL_END = 'nativeScrollEnd';
73
- type HOOK_PROGRAMMATIC_SCROLL_START = 'programmaticScrollStart';
74
- type HOOK_PROGRAMMATIC_SCROLL = 'programmaticScroll';
75
- type HOOK_PROGRAMMATIC_SCROLL_END = 'programmaticScrollEnd';
76
- type SliderPlugin = (slider: Slider) => void;
23
+ } | undefined;
77
24
 
78
- export type { DeepPartial, HOOK_ACTIVE_SLIDE_CHANGED, HOOK_CONTAINER_SIZE_CHANGED, HOOK_CONTENTS_CHANGED, HOOK_CREATED, HOOK_DETAILS_CHANGED, HOOK_NATIVE_SCROLL, HOOK_NATIVE_SCROLL_END, HOOK_NATIVE_SCROLL_START, HOOK_PROGRAMMATIC_SCROLL, HOOK_PROGRAMMATIC_SCROLL_END, HOOK_PROGRAMMATIC_SCROLL_START, HOOK_SCROLL, HOOK_SCROLL_END, HOOK_SCROLL_START, Slider, SliderCallback, SliderDetails, SliderHooks, SliderOptionArgs, SliderOptions, SliderPlugin };
25
+ export { OverflowSlider as default };
@@ -1,12 +1,10 @@
1
- import { SliderOptionArgs, SliderPlugin, SliderCallback, SliderOptions, SliderDetails } from './index.js';
2
-
3
- declare function OverflowSlider(container: HTMLElement, options?: SliderOptionArgs, plugins?: SliderPlugin[]): {
1
+ type Slider<O = {}, C = {}, H extends string = string> = {
4
2
  container: HTMLElement;
5
3
  slides: HTMLElement[];
6
- emit: (name: string) => void;
7
- moveToDirection: (direction: "prev" | "next") => void;
8
- moveToSlideInDirection: (direction: "prev" | "next") => void;
9
- snapToClosestSlide: (direction: "prev" | "next") => void;
4
+ emit: (name: H | SliderHooks) => void;
5
+ moveToDirection: (direction: 'prev' | 'next') => void;
6
+ moveToSlideInDirection: (direction: 'prev' | 'next') => void;
7
+ snapToClosestSlide: (direction: 'prev' | 'next') => void;
10
8
  moveToSlide: (index: number) => void;
11
9
  canMoveToSlide: (index: number) => boolean;
12
10
  getInclusiveScrollWidth: () => number;
@@ -16,10 +14,66 @@ declare function OverflowSlider(container: HTMLElement, options?: SliderOptionAr
16
14
  getScrollLeft: () => number;
17
15
  setScrollLeft: (value: number) => void;
18
16
  setActiveSlideIdx: () => void;
19
- on: (name: string, cb: SliderCallback) => void;
17
+ on: (name: H | SliderHooks, cb: SliderCallback) => void;
20
18
  options: SliderOptions;
21
19
  details: SliderDetails;
22
20
  activeSlideIdx: number;
23
- } | undefined;
21
+ } & C;
22
+ type SliderCallback<O = {}, C = {}, H extends string = string> = (props: Slider<O, C, H>) => void;
23
+ /**
24
+ * Recursively makes all properties of T optional.
25
+ * @see https://www.typescriptlang.org/docs/handbook/utility-types.html#mapped-types
26
+ */
27
+ type DeepPartial<T> = {
28
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
29
+ };
30
+ type SliderOptions = {
31
+ scrollBehavior: string;
32
+ scrollStrategy: string;
33
+ slidesSelector: string;
34
+ emulateScrollSnap: boolean;
35
+ emulateScrollSnapMaxThreshold?: number;
36
+ cssVariableContainer: HTMLElement;
37
+ rtl: boolean;
38
+ targetWidth?: (slider: Slider) => number;
39
+ [key: string]: unknown;
40
+ };
41
+ type SliderOptionArgs = {
42
+ scrollBehavior?: 'smooth' | 'auto';
43
+ scrollStrategy?: 'fullSlide' | 'partialSlide';
44
+ slidesSelector?: string;
45
+ emulateScrollSnap?: boolean;
46
+ emulateScrollSnapMaxThreshold?: number;
47
+ cssVariableContainer?: HTMLElement;
48
+ rtl?: boolean;
49
+ targetWidth?: (slider: Slider) => number;
50
+ [key: string]: unknown;
51
+ };
52
+ type SliderDetails = {
53
+ hasOverflow: boolean;
54
+ slideCount: number;
55
+ containerWidth: number;
56
+ containerHeight: number;
57
+ scrollableAreaWidth: number;
58
+ amountOfPages: number;
59
+ currentPage: number;
60
+ };
61
+ type SliderHooks = HOOK_CREATED | HOOK_CONTENTS_CHANGED | HOOK_DETAILS_CHANGED | HOOK_CONTAINER_SIZE_CHANGED | HOOK_ACTIVE_SLIDE_CHANGED | HOOK_SCROLL_START | HOOK_SCROLL | HOOK_SCROLL_END | HOOK_NATIVE_SCROLL_START | HOOK_NATIVE_SCROLL | HOOK_NATIVE_SCROLL_END | HOOK_PROGRAMMATIC_SCROLL_START | HOOK_PROGRAMMATIC_SCROLL | HOOK_PROGRAMMATIC_SCROLL_END | HOOK_FOCUS_SCROLL;
62
+ type HOOK_CREATED = 'created';
63
+ type HOOK_DETAILS_CHANGED = 'detailsChanged';
64
+ type HOOK_CONTENTS_CHANGED = 'contentsChanged';
65
+ type HOOK_CONTAINER_SIZE_CHANGED = 'containerSizeChanged';
66
+ type HOOK_ACTIVE_SLIDE_CHANGED = 'activeSlideChanged';
67
+ type HOOK_SCROLL_START = 'scrollStart';
68
+ type HOOK_SCROLL = 'scroll';
69
+ type HOOK_SCROLL_END = 'scrollEnd';
70
+ type HOOK_NATIVE_SCROLL_START = 'nativeScrollStart';
71
+ type HOOK_NATIVE_SCROLL = 'nativeScroll';
72
+ type HOOK_NATIVE_SCROLL_END = 'nativeScrollEnd';
73
+ type HOOK_PROGRAMMATIC_SCROLL_START = 'programmaticScrollStart';
74
+ type HOOK_PROGRAMMATIC_SCROLL = 'programmaticScroll';
75
+ type HOOK_PROGRAMMATIC_SCROLL_END = 'programmaticScrollEnd';
76
+ type HOOK_FOCUS_SCROLL = 'focusScroll';
77
+ type SliderPlugin = (slider: Slider) => void;
24
78
 
25
- export { OverflowSlider as default };
79
+ export type { DeepPartial, HOOK_ACTIVE_SLIDE_CHANGED, HOOK_CONTAINER_SIZE_CHANGED, HOOK_CONTENTS_CHANGED, HOOK_CREATED, HOOK_DETAILS_CHANGED, HOOK_FOCUS_SCROLL, HOOK_NATIVE_SCROLL, HOOK_NATIVE_SCROLL_END, HOOK_NATIVE_SCROLL_START, HOOK_PROGRAMMATIC_SCROLL, HOOK_PROGRAMMATIC_SCROLL_END, HOOK_PROGRAMMATIC_SCROLL_START, HOOK_SCROLL, HOOK_SCROLL_END, HOOK_SCROLL_START, Slider, SliderCallback, SliderDetails, SliderHooks, SliderOptionArgs, SliderOptions, SliderPlugin };
@@ -1,4 +1,4 @@
1
- import { DeepPartial, Slider } from '../core/index.js';
1
+ import { DeepPartial, Slider } from '../core/index.d2.ts';
2
2
 
3
3
  type DotsOptions = {
4
4
  type: 'view' | 'slide';
@@ -1,4 +1,4 @@
1
- import { DeepPartial, Slider } from '../core/index.js';
1
+ import { DeepPartial, Slider } from '../core/index.d2.ts';
2
2
 
3
3
  type DragScrollingOptions = {
4
4
  draggedDistanceThatPreventsClick: number;
@@ -1,4 +1,4 @@
1
- import { DeepPartial, Slider } from '../core/index.js';
1
+ import { DeepPartial, Slider } from '../core/index.d2.ts';
2
2
 
3
3
  type FadeOptions = {
4
4
  classNames: {
@@ -1,4 +1,4 @@
1
- import { DeepPartial, Slider } from '../core/index.js';
1
+ import { DeepPartial, Slider } from '../core/index.d2.ts';
2
2
 
3
3
  type FullWidthOptions = {
4
4
  targetWidth?: (slider: Slider) => number;
@@ -1,4 +1,4 @@
1
- import { DeepPartial, Slider } from '../core/index.js';
1
+ import { DeepPartial, Slider } from '../core/index.d2.ts';
2
2
 
3
3
  type ScrollIndicatorOptions = {
4
4
  classNames: {
@@ -1,4 +1,4 @@
1
- import { DeepPartial, Slider } from '../core/index.js';
1
+ import { DeepPartial, Slider } from '../core/index.d2.ts';
2
2
 
3
3
  type SkipLinkOptions = {
4
4
  texts: {
@@ -1,9 +1,9 @@
1
- import { DeepPartial, Slider } from '../core/index.js';
1
+ import { DeepPartial, Slider } from '../core/index.d2.ts';
2
2
 
3
3
  type ThumbnailsOptions = {
4
4
  mainSlider: Slider;
5
5
  };
6
- declare function FullWidthPlugin(args: DeepPartial<ThumbnailsOptions>): (slider: Slider) => void;
6
+ declare function ThumbnailPlugin(args: DeepPartial<ThumbnailsOptions>): (slider: Slider) => void;
7
7
 
8
- export { FullWidthPlugin as default };
8
+ export { ThumbnailPlugin as default };
9
9
  export type { ThumbnailsOptions };
@@ -1,4 +1,4 @@
1
- function FullWidthPlugin(args) {
1
+ function ThumbnailPlugin(args) {
2
2
  return (slider) => {
3
3
  const options = {
4
4
  mainSlider: args.mainSlider,
@@ -31,15 +31,15 @@ function FullWidthPlugin(args) {
31
31
  setTimeout(() => {
32
32
  const mainActiveSlideIdx = mainSlider.activeSlideIdx;
33
33
  const thumbActiveSlideIdx = slider.activeSlideIdx;
34
+ const activeThumbnail = slider.slides[mainActiveSlideIdx];
35
+ setActiveThumbnail(activeThumbnail);
34
36
  if (thumbActiveSlideIdx === mainActiveSlideIdx) {
35
37
  return;
36
38
  }
37
- const activeThumbnail = slider.slides[mainActiveSlideIdx];
38
- setActiveThumbnail(activeThumbnail);
39
39
  slider.moveToSlide(mainActiveSlideIdx);
40
40
  }, 50);
41
41
  });
42
42
  };
43
43
  }
44
44
 
45
- export { FullWidthPlugin as default };
45
+ export { ThumbnailPlugin as default };
@@ -1 +1 @@
1
- function e(e){return i=>{const t={mainSlider:e.mainSlider}.mainSlider,l=(e=null)=>{null===e&&i.slides.length>0&&(e=i.slides[0]),null!==e&&(i.slides.forEach(e=>{e.setAttribute("aria-current","false")}),e.setAttribute("aria-current","true"))};l(),i.slides.forEach((e,i)=>{e.addEventListener("click",()=>{t.moveToSlide(i),l(e)})}),t.on("scrollEnd",()=>{setTimeout(()=>{const e=t.activeSlideIdx;if(i.activeSlideIdx===e)return;const r=i.slides[e];l(r),i.moveToSlide(e)},50)})}}export{e as default};
1
+ function e(e){return i=>{const l={mainSlider:e.mainSlider}.mainSlider,t=(e=null)=>{null===e&&i.slides.length>0&&(e=i.slides[0]),null!==e&&(i.slides.forEach(e=>{e.setAttribute("aria-current","false")}),e.setAttribute("aria-current","true"))};t(),i.slides.forEach((e,i)=>{e.addEventListener("click",()=>{l.moveToSlide(i),t(e)})}),l.on("scrollEnd",()=>{setTimeout(()=>{const e=l.activeSlideIdx,r=i.activeSlideIdx,s=i.slides[e];t(s),r!==e&&i.moveToSlide(e)},50)})}}export{e as default};
@@ -846,3 +846,8 @@ h3:before {
846
846
  --overflow-slider-dot-inactive-color: rgba(0, 0, 0, 0.2);
847
847
  --overflow-slider-dot-active-color: rgba(0, 0, 0, .8);
848
848
  }
849
+
850
+ .example-container-1-focus .example-item {
851
+ aspect-ratio: unset;
852
+ height: 200px;
853
+ }
@@ -241,6 +241,17 @@ import ClassNamesPlugin from '../dist/plugins/classnames/index.esm.js';
241
241
  );
242
242
  console.log( '1-autoplay-view', example1AutoplayView );
243
243
 
244
+ const example1Focus = new OverflowSlider(
245
+ document.querySelector( '.example-container-1-focus' ),
246
+ {
247
+ rtl: document.documentElement.dir === 'rtl',
248
+ },
249
+ [
250
+ DragScrollingPlugin(),
251
+ ]
252
+ );
253
+ console.log( '1-focus', example1Focus );
254
+
244
255
  const example2PerfectFit = new OverflowSlider(
245
256
  document.querySelector( '.example-container-2-perfect-fit' ),
246
257
  {
@@ -1 +1 @@
1
- export { default as OverflowSlider } from './plugins/core/index.d2.ts';
1
+ export { default as OverflowSlider } from './plugins/core/index.js';
@@ -21,8 +21,7 @@ function details(slider) {
21
21
  // Consider as last page if we're within tolerance of the maximum scroll position
22
22
  // When FullWidthPlugin is active, account for the margin offset
23
23
  const maxScroll = scrollableAreaWidth - containerWidth - (2 * slider.getLeftOffset());
24
- const currentScroll = slider.getScrollLeft();
25
- if (currentScroll >= maxScroll - 1) {
24
+ if (slider.getScrollLeft() >= maxScroll - 1) {
26
25
  currentPage = amountOfPages - 1;
27
26
  }
28
27
  }
@@ -253,21 +252,38 @@ function Slider(container, options, plugins) {
253
252
  wasInteractedWith = true;
254
253
  }, { passive: true });
255
254
  slider.container.addEventListener('focusin', (e) => {
256
- // move target parents as long as they are not the container
257
- // but only if focus didn't start from mouse or touch
258
- if (!wasInteractedWith) {
259
- let target = e.target;
260
- while (target.parentElement !== slider.container) {
261
- if (target.parentElement) {
262
- target = target.parentElement;
263
- }
264
- else {
265
- break;
266
- }
267
- }
268
- ensureSlideIsInView(target, 'auto');
255
+ // Only handle keyboard-initiated focus (not mouse or touch)
256
+ if (wasInteractedWith) {
257
+ wasInteractedWith = false;
258
+ return;
269
259
  }
270
260
  wasInteractedWith = false;
261
+ // No scrolling needed if there is no overflow
262
+ if (!slider.details.hasOverflow) {
263
+ return;
264
+ }
265
+ const focusedElement = e.target;
266
+ // Walk up from the focused element to find the direct child (slide) of the container
267
+ let slide = focusedElement;
268
+ while (slide.parentElement !== slider.container) {
269
+ if (slide.parentElement) {
270
+ slide = slide.parentElement;
271
+ }
272
+ else {
273
+ // Focused element is not inside the slider container
274
+ return;
275
+ }
276
+ }
277
+ // Emit programmaticScrollStart immediately so the browser's native focus
278
+ // scroll events are classified as programmatic (not native). This prevents
279
+ // nativeScrollStart from restoring scrollSnapType and fighting our correction.
280
+ slider.emit('programmaticScrollStart');
281
+ // Use setTimeout to let the browser's native focus scroll complete,
282
+ // then override with our WCAG-compliant scroll positioning
283
+ setTimeout(() => {
284
+ scrollFocusedSlideIntoView(slide, focusedElement);
285
+ slider.emit('focusScroll');
286
+ }, 50);
271
287
  });
272
288
  }
273
289
  function setCSSVariables() {
@@ -314,6 +330,82 @@ function Slider(container, options, plugins) {
314
330
  }, 50, scrollTarget);
315
331
  }
316
332
  }
333
+ /**
334
+ * Scrolls a focused slide (or child element) into view for WCAG AA compliance.
335
+ * Priority:
336
+ * 1. Show the full slide if it fits in the container
337
+ * 2. If the slide is wider than the container, show the focused element
338
+ * 3. If neither fits, align the leading edge (left for LTR, right for RTL)
339
+ */
340
+ function scrollFocusedSlideIntoView(slide, focusedElement) {
341
+ const isRtl = slider.options.rtl;
342
+ const containerRect = slider.container.getBoundingClientRect();
343
+ const containerWidth = slider.container.offsetWidth;
344
+ const slideRect = slide.getBoundingClientRect();
345
+ const scrollLeft = slider.container.scrollLeft;
346
+ // Calculate visual offsets relative to the container viewport
347
+ const slideLeftOffset = slideRect.left - containerRect.left;
348
+ const slideRightOffset = slideRect.right - containerRect.right;
349
+ // Check if slide is already fully visible (1px tolerance for sub-pixel rounding)
350
+ if (slideLeftOffset >= -1 && slideRightOffset <= 1) {
351
+ slider.container.style.scrollSnapType = '';
352
+ slider.emit('programmaticScrollEnd');
353
+ return;
354
+ }
355
+ let scrollTarget;
356
+ if (slideRect.width <= containerWidth) {
357
+ // Slide fits in container — align its leading edge to show it fully
358
+ if (isRtl) {
359
+ // RTL: align slide's right edge with container's right edge
360
+ scrollTarget = scrollLeft + slideRightOffset;
361
+ }
362
+ else {
363
+ // LTR: align slide's left edge with container's left edge
364
+ scrollTarget = scrollLeft + slideLeftOffset;
365
+ }
366
+ }
367
+ else if (focusedElement !== slide) {
368
+ // Slide is wider than container — try to show the focused child element
369
+ const focusRect = focusedElement.getBoundingClientRect();
370
+ const focusLeftOffset = focusRect.left - containerRect.left;
371
+ const focusRightOffset = focusRect.right - containerRect.right;
372
+ // Check if focused element is already fully visible
373
+ if (focusLeftOffset >= -1 && focusRightOffset <= 1) {
374
+ slider.container.style.scrollSnapType = '';
375
+ slider.emit('programmaticScrollEnd');
376
+ return;
377
+ }
378
+ if (focusRect.width <= containerWidth) {
379
+ // Focused element fits in container — align its leading edge
380
+ if (isRtl) {
381
+ scrollTarget = scrollLeft + focusRightOffset;
382
+ }
383
+ else {
384
+ scrollTarget = scrollLeft + focusLeftOffset;
385
+ }
386
+ }
387
+ else {
388
+ // Focused element is also wider than container — align leading edge
389
+ if (isRtl) {
390
+ scrollTarget = scrollLeft + focusRightOffset;
391
+ }
392
+ else {
393
+ scrollTarget = scrollLeft + focusLeftOffset;
394
+ }
395
+ }
396
+ }
397
+ else {
398
+ // Slide is the focused element and wider than container — align leading edge
399
+ if (isRtl) {
400
+ scrollTarget = scrollLeft + slideRightOffset;
401
+ }
402
+ else {
403
+ scrollTarget = scrollLeft + slideLeftOffset;
404
+ }
405
+ }
406
+ slider.emit('programmaticScrollStart');
407
+ slider.container.scrollTo({ left: scrollTarget, behavior: 'auto' });
408
+ }
317
409
  function setActiveSlideIdx() {
318
410
  const sliderRect = slider.container.getBoundingClientRect();
319
411
  const scrollLeft = slider.getScrollLeft();