@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,33 +1,51 @@
1
- # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
- # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3
-
1
+ # Publish to npm using Trusted Publishers (OIDC)
4
2
  name: Publish to NPM
5
3
 
6
4
  on:
7
5
  release:
8
6
  types: [created]
7
+ workflow_dispatch:
8
+ inputs:
9
+ dry_run:
10
+ description: 'Run in dry-run mode (no actual publish)'
11
+ required: false
12
+ default: false
13
+ type: boolean
9
14
 
10
15
  jobs:
11
- build:
16
+ publish:
12
17
  runs-on: ubuntu-latest
18
+ permissions:
19
+ contents: read
20
+ id-token: write
13
21
  steps:
14
- - uses: actions/checkout@v3
15
- - uses: actions/setup-node@v3
16
- with:
17
- node-version: 16
18
- - run: npm ci
19
- - run: npm test
22
+ - name: Checkout
23
+ uses: actions/checkout@v4
20
24
 
21
- publish-npm:
22
- needs: build
23
- runs-on: ubuntu-latest
24
- steps:
25
- - uses: actions/checkout@v3
26
- - uses: actions/setup-node@v3
25
+ - name: Setup Node.js
26
+ uses: actions/setup-node@v4
27
27
  with:
28
- node-version: 16
29
- registry-url: https://registry.npmjs.org/
30
- - run: npm ci
31
- - run: npm publish
32
- env:
33
- NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
28
+ node-version: '20'
29
+ registry-url: 'https://registry.npmjs.org'
30
+
31
+ - name: Install dependencies
32
+ run: npm ci
33
+
34
+ - name: Run tests
35
+ run: npm test
36
+
37
+ - name: Debug authentication
38
+ run: |
39
+ echo "=== Environment Check ==="
40
+ echo "ACTIONS_ID_TOKEN_REQUEST_TOKEN: ${ACTIONS_ID_TOKEN_REQUEST_TOKEN:+SET}"
41
+ echo "ACTIONS_ID_TOKEN_REQUEST_URL: ${ACTIONS_ID_TOKEN_REQUEST_URL:+SET}"
42
+ echo "NPM_CONFIG_REGISTRY: $NPM_CONFIG_REGISTRY"
43
+ echo "=== npm whoami ==="
44
+ npm whoami || echo "Failed to authenticate"
45
+ echo "=== npm ping ==="
46
+ npm ping
47
+ echo "=== Check registry ==="
48
+ npm config get registry
49
+
50
+ - name: Publish to npm
51
+ run: npm publish --access public ${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }}
@@ -0,0 +1,35 @@
1
+ # Publish to npm using Trusted Publishers (OIDC)
2
+ name: Publish to NPM
3
+
4
+ permissions:
5
+ id-token: write # Required for OIDC
6
+ contents: read
7
+
8
+ on:
9
+ release:
10
+ types: [created]
11
+ workflow_dispatch:
12
+ inputs:
13
+ dry_run:
14
+ description: 'Run in dry-run mode (no actual publish)'
15
+ required: false
16
+ default: false
17
+ type: boolean
18
+
19
+ jobs:
20
+ publish:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - uses: actions/setup-node@v4
26
+ with:
27
+ node-version: '20'
28
+ registry-url: 'https://registry.npmjs.org'
29
+
30
+ - run: npm ci
31
+ - run: npm test
32
+ - run: npm whoami || echo "OIDC auth failed"
33
+ - run: npm publish ${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }}
34
+ env:
35
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CHANGELOG.md ADDED
@@ -0,0 +1,116 @@
1
+ # Changelog
2
+
3
+ ### 4.2.3
4
+
5
+ * Add: React example in README.md
6
+ * Change: Move changelog to separate file to make README.md more compact
7
+ * Fix: ThumbnailPlugin not setting first item active when navigatin back to it when thumbnails are not overflowing
8
+ * Fix: moving slider to keyboard focused item work better with slide's inner focusable elements
9
+
10
+ ### 4.2.2
11
+
12
+ * Add: `justify-content: flex-start` to overflow-slider base styles to prevent centering of slides or other unexpected behavior when slider container is wider than its content.
13
+
14
+ ### 4.2.1
15
+
16
+ * Fix: currentPage and amountOfPages not being calculated correctly with FullWidthPlugin causin last page no to activate properly.
17
+
18
+ ### 4.2.0
19
+
20
+ * Add: View mode in DotsPlugin in addition to previous "slide mode"
21
+ * Fix: Remove forgotten console.log from ClassNamesPlugin
22
+
23
+ ### 4.1.0
24
+
25
+ * Add: ClassNamesPlugin to add classes to visible/partly visible/hidden slides.
26
+ * Add: targetWidth property to core level (backwards compatible with FullWidthPlugin implementation)
27
+ * Fix: Scroll snapping for FullWidthPlugin
28
+ * Fix: Possible issues where plugin changed some details and that was not applied for first render
29
+ * Fix: Rendering issue where transition on slides could prevent calculations initially from working
30
+
31
+ ### 4.0.0
32
+
33
+ * Add: AutoPlayPlugin to allow auto-playing slides
34
+ * Add: Mixins that can be imported to SCSS projects
35
+ * Add: CSS variable: `--slider-container-height`
36
+ * Add: CSS variable: `--slider-x-offset`
37
+ * Add: Option `cssVariableContainer` to expose CSS variables for example higher container
38
+ * Add: `canMoveToSlide` method to check if slider can move to a specific slide (does it exist, is it already in view)
39
+ * Add: `targetWidth` to slider options as relying on `--slider-container-target-width` can be more solid to calculate fractional slide widths on (at least when there is only few slides)
40
+ * Fix: Export TypeScript types properly from core and plugins to be available automatically
41
+ * Fix: ScrollIndicatorPlugin click to scroll bar didn't always detect click position correctly
42
+ * Fix: snapToClosestSlide method edge cases on DragScrollingPlugin sometimes not snapping on right slide
43
+
44
+ ### 3.3.1
45
+
46
+ * Fix: FullWidthPlugin margin calculation not being run if there's too few slides for overflow and you resize screen without container width changing
47
+
48
+ ### 3.3.0
49
+
50
+ * Add: Ability to move each direction by one slide at a time via `moveToSlideInDirection` prev/next
51
+ * Add: Support for ArrowsPlugin to move by one slide at a time (default is still one view at a time)
52
+ * Fix: Remove console logs
53
+ * Refactor: Plugin build paths to match import paths. Might fix some eslint warnings. If you are not using import but directly referencing the plugin files under `dist/` you might need to update your paths.
54
+
55
+ ### 3.2.1
56
+
57
+ * Add: Documentation on plugins
58
+ * Fix: Make types more strict and remove all "any" types
59
+
60
+ ### 3.2.0
61
+
62
+ * Add: RTL support
63
+ * Add: `--slider-container-target-width` for FullWidthPlugin to allow CSS based on target size
64
+ * Add: Documentation how to set slides per view in CSS
65
+ * Fix: Attach ThumbnailsPlugin to scrollEnd which skips in-between slides when multiple slides are scrolled at once
66
+
67
+ ### 3.1.0
68
+
69
+ * Add: slider.getInclusiveScrollWidth and slider.getInclusiveScrollHeight methods to get widths including outermost childs outermost margins
70
+ * Fix: Lot of bugs related to subpixel widths
71
+ * Fix: Don't run arrow click action if there are no more slides to scroll to
72
+ * Fix: FullWidthPlugin bugs where arrows were not detecting start or end properly (because of child margins not taken into account)
73
+ * Fix: Attach ThumbnailsPlugin to activeSlideChanged which is more appropriate hook
74
+
75
+ ### 3.0.0
76
+
77
+ * Breaking: Change dot plugin to calculate dots based on slides instead of container width "pages"
78
+ * Add: FadePlugin to hint that there are more slides to scroll to
79
+ * Add: Scroll snap emulation method
80
+ * Add: Scroll snap emulation for DragScrollingPlugin
81
+ * Add: Hooks for different types of scrolling (any, native, programmatic)
82
+ * Add: Hooks for different states of scrolling (start, scroll, end) for above types
83
+ * Refactor: Scroll snapping exceptions to be handled by the core slider
84
+ * Fix: Enhance performance by hooking some plugins only when scrolling has ended
85
+ * Fix: Full width alignment to take into account the container offset
86
+
87
+ ### 2.0.2
88
+
89
+ * Fix: Import style.css from correct path
90
+
91
+ ### 2.0.1
92
+
93
+ * Fix: Smooth scrolling for moveToSlide method
94
+ * Fix: Prev arrow sometimes leaving visible although there are no more slides to scroll to
95
+
96
+ ### 2.0.0
97
+
98
+ * Breaking: Separate plugins to their own imports/files
99
+ * Add: FullWidthPlugin to allow full width sliders
100
+ * Add: ThumbnailsPlugin to show synchronized thumbnails
101
+ * Add: Slider container 'data-ready' attribute when initialized to help writing CSS
102
+ * Add: Support for optional separate containers for prev and next arrows
103
+ * Add: Slides as array to Slider instance
104
+ * Add: Active slide ID to Slider instance as activeSlideIdx and hook activeSlideChanged
105
+ * Fix: DragScrollingPlugin dragging clickable slides in Firefox
106
+ * Fix: DragScrollingPlugin dragging outside of container bugs in Firefox/Safari
107
+ * Fix: ScrollIndicatorPlugin width calculation when scrollbar and container are not same width
108
+
109
+ ### 1.1.0
110
+
111
+ * Add: Grab cursor when hovering slider that has DragScrollingPlugin
112
+ * Add: Example of using entrance and exit animations for slides
113
+ * Fix: ScrollIndicatorPlugin dragging works now with touch
114
+ * Fix: Hide native scrollbar also in Firefox + Edge
115
+ * Docs: Add more info on required markup and limitations
116
+
package/README.md CHANGED
@@ -102,6 +102,59 @@ You can use the CSS variables to override some values easily.
102
102
 
103
103
  Note that you can easily write styles from scratch if you want to. See source code from `src/overflow-slider.scss` for reference.
104
104
 
105
+ ## Using in React
106
+
107
+ Overflow Slider can be used with React. There is no separate core or plugins for React so usage is very similar to vanilla JS. Overflow Slider depends on expanding existing DOM and adding DOM elements so it needs reliable access to these elements.
108
+
109
+ In React the way to give this access is `useRef`.
110
+
111
+ ```js
112
+ import { useRef, useEffect } from 'react';
113
+ import { OverflowSlider } from '@evermade/overflow-slider';
114
+ import DragScrollingPlugin from '@evermade/overflow-slider/plugins/drag-scrolling';
115
+ import ArrowsPlugin from '@evermade/overflow-slider/plugins/arrows';
116
+
117
+ const ImageSlider = () => {
118
+ const sliderElement = useRef(null);
119
+ const sliderControls = useRef(null);
120
+
121
+ useEffect(() => {
122
+ if (!sliderElement.current || !sliderControls.current) {
123
+ return;
124
+ }
125
+
126
+ const slider = new OverflowSlider(
127
+ sliderElement.current,
128
+ {
129
+ emulateScrollSnap: true,
130
+ },
131
+ [
132
+ DragScrollingPlugin(),
133
+ ArrowsPlugin({
134
+ container: sliderControls.current
135
+ }),
136
+ ]
137
+ );
138
+ }, []);
139
+
140
+ return (
141
+ <div className="slider-container">
142
+ <div className="overflow-slider" ref={sliderElement}>
143
+ <div className="overflow-slider__slider-item"><h2>Slide 1</h2></div>
144
+ <div className="overflow-slider__slider-item"><h2>Slide 2</h2></div>
145
+ <div className="overflow-slider__slider-item"><h2>Slide 3</h2></div>
146
+ </div>
147
+ <div className="slider-controls" ref={sliderControls}>
148
+ </div>
149
+ </div>
150
+ );
151
+ };
152
+
153
+ export default ImageSlider;
154
+ ```
155
+
156
+ Note that Overflow Slider does not have destroy() function. If not having it turns out to be a big issue for React we could add it but that is something affecting all core/plugins and can increase bundle size ~25% as it can be a lot of code.
157
+
105
158
  ## Mixins
106
159
 
107
160
  If you are using SCSS, you can use these helpers.
@@ -562,108 +615,7 @@ Infinite scroll is not supported and likely never will be. It is not accessible
562
615
 
563
616
  ## Changelog
564
617
 
565
- ### 4.2.1
566
-
567
- * Fix: currentPage and amountOfPages not being calculated correctly with FullWidthPlugin causin last page no to activate properly.
568
-
569
- ### 4.2.0
570
-
571
- * Add: View mode in DotsPlugin in addition to previous "slide mode"
572
- * Fix: Remove forgotten console.log from ClassNamesPlugin
573
-
574
- ### 4.1.0
575
-
576
- * Add: ClassNamesPlugin to add classes to visible/partly visible/hidden slides.
577
- * Add: targetWidth property to core level (backwards compatible with FullWidthPlugin implementation)
578
- * Fix: Scroll snapping for FullWidthPlugin
579
- * Fix: Possible issues where plugin changed some details and that was not applied for first render
580
- * Fix: Rendering issue where transition on slides could prevent calculations initially from working
581
-
582
- ### 4.0.0
583
-
584
- * Add: AutoPlayPlugin to allow auto-playing slides
585
- * Add: Mixins that can be imported to SCSS projects
586
- * Add: CSS variable: `--slider-container-height`
587
- * Add: CSS variable: `--slider-x-offset`
588
- * Add: Option `cssVariableContainer` to expose CSS variables for example higher container
589
- * Add: `canMoveToSlide` method to check if slider can move to a specific slide (does it exist, is it already in view)
590
- * Add: `targetWidth` to slider options as relying on `--slider-container-target-width` can be more solid to calculate fractional slide widths on (at least when there is only few slides)
591
- * Fix: Export TypeScript types properly from core and plugins to be available automatically
592
- * Fix: ScrollIndicatorPlugin click to scroll bar didn't always detect click position correctly
593
- * Fix: snapToClosestSlide method edge cases on DragScrollingPlugin sometimes not snapping on right slide
594
-
595
- ### 3.3.1
596
-
597
- * Fix: FullWidthPlugin margin calculation not being run if there's too few slides for overflow and you resize screen without container width changing
598
-
599
- ### 3.3.0
600
-
601
- * Add: Ability to move each direction by one slide at a time via `moveToSlideInDirection` prev/next
602
- * Add: Support for ArrowsPlugin to move by one slide at a time (default is still one view at a time)
603
- * Fix: Remove console logs
604
- * Refactor: Plugin build paths to match import paths. Might fix some eslint warnings. If you are not using import but directly referencing the plugin files under `dist/` you might need to update your paths.
605
-
606
- ### 3.2.1
607
-
608
- * Add: Documentation on plugins
609
- * Fix: Make types more strict and remove all "any" types
610
-
611
- ### 3.2.0
612
-
613
- * Add: RTL support
614
- * Add: `--slider-container-target-width` for FullWidthPlugin to allow CSS based on target size
615
- * Add: Documentation how to set slides per view in CSS
616
- * Fix: Attach ThumbnailsPlugin to scrollEnd which skips in-between slides when multiple slides are scrolled at once
617
-
618
- ### 3.1.0
619
-
620
- * Add: slider.getInclusiveScrollWidth and slider.getInclusiveScrollHeight methods to get widths including outermost childs outermost margins
621
- * Fix: Lot of bugs related to subpixel widths
622
- * Fix: Don't run arrow click action if there are no more slides to scroll to
623
- * Fix: FullWidthPlugin bugs where arrows were not detecting start or end properly (because of child margins not taken into account)
624
- * Fix: Attach ThumbnailsPlugin to activeSlideChanged which is more appropriate hook
625
-
626
- ### 3.0.0
627
-
628
- * Breaking: Change dot plugin to calculate dots based on slides instead of container width "pages"
629
- * Add: FadePlugin to hint that there are more slides to scroll to
630
- * Add: Scroll snap emulation method
631
- * Add: Scroll snap emulation for DragScrollingPlugin
632
- * Add: Hooks for different types of scrolling (any, native, programmatic)
633
- * Add: Hooks for different states of scrolling (start, scroll, end) for above types
634
- * Refactor: Scroll snapping exceptions to be handled by the core slider
635
- * Fix: Enhance performance by hooking some plugins only when scrolling has ended
636
- * Fix: Full width alignment to take into account the container offset
637
-
638
- ### 2.0.2
639
-
640
- * Fix: Import style.css from correct path
641
-
642
- ### 2.0.1
643
-
644
- * Fix: Smooth scrolling for moveToSlide method
645
- * Fix: Prev arrow sometimes leaving visible although there are no more slides to scroll to
646
-
647
- ### 2.0.0
648
-
649
- * Breaking: Separate plugins to their own imports/files
650
- * Add: FullWidthPlugin to allow full width sliders
651
- * Add: ThumbnailsPlugin to show synchronized thumbnails
652
- * Add: Slider container 'data-ready' attribute when initialized to help writing CSS
653
- * Add: Support for optional separate containers for prev and next arrows
654
- * Add: Slides as array to Slider instance
655
- * Add: Active slide ID to Slider instance as activeSlideIdx and hook activeSlideChanged
656
- * Fix: DragScrollingPlugin dragging clickable slides in Firefox
657
- * Fix: DragScrollingPlugin dragging outside of container bugs in Firefox/Safari
658
- * Fix: ScrollIndicatorPlugin width calculation when scrollbar and container are not same width
659
-
660
- ### 1.1.0
661
-
662
- * Add: Grab cursor when hovering slider that has DragScrollingPlugin
663
- * Add: Example of using entrance and exit animations for slides
664
- * Fix: ScrollIndicatorPlugin dragging works now with touch
665
- * Fix: Hide native scrollbar also in Firefox + Edge
666
- * Docs: Add more info on required markup and limitations
618
+ See [CHANGELOG.md](./CHANGELOG.md)
667
619
 
668
620
  ## Development
669
621
 
@@ -671,8 +623,4 @@ Install tools `npm install` and build `npm run build` or develop with `npm run w
671
623
 
672
624
  Releasing new version:
673
625
 
674
- * Update version in `package.json`
675
- * Commit to master
676
- * Set tag with version number to git
677
- * Create new release in GitHub
678
- * NPM package is automatically published from GitHub
626
+ See [RELEASE.md](./RELEASE.md)
package/RELEASE.md ADDED
@@ -0,0 +1,44 @@
1
+ # Release Process
2
+
3
+ This project uses a manual release process.
4
+
5
+ ## Steps to Release
6
+
7
+ 1. **Update version in package.json**
8
+
9
+ 2. **Build the package**
10
+ ```bash
11
+ npm run build
12
+ ```
13
+
14
+ 3. **Push changes and create GitHub release**
15
+ ```bash
16
+ git push
17
+ git push --tags
18
+ ```
19
+
20
+ 4. **Create GitHub Release**
21
+ - Go to GitHub repository
22
+ - Click "Releases" → "Create a new release"
23
+ - Select the version tag
24
+ - Write release notes
25
+ - Publish release
26
+
27
+ 5. **Publish to npm**
28
+ ```bash
29
+ npm publish --access public
30
+ ```
31
+
32
+ ## Prerequisites
33
+
34
+ - Make sure you're logged into npm: `npm whoami`
35
+ - Make sure you have write access to the `@evermade/overflow-slider` package
36
+ - Make sure you're on the main branch with latest changes
37
+
38
+ ## Notes
39
+
40
+ - The `npm version` command automatically updates package.json and creates a git tag
41
+ - Use `npm publish --dry-run` first if you want to test without actually publishing
42
+ - The package is public, so `--access public` ensures it's published correctly
43
+
44
+ That's it! Simple and reliable manual process.
package/dist/index.d.ts CHANGED
@@ -1 +1 @@
1
- export { default as OverflowSlider } from './plugins/core/index.d2.ts';
1
+ export { default as OverflowSlider } from './plugins/core/index.js';
package/dist/index.esm.js CHANGED
@@ -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();