diff --git a/packages/embla-carousel-auto-scroll/src/components/AutoScroll.ts b/packages/embla-carousel-auto-scroll/src/components/AutoScroll.ts
index 4e49d81a2..485aef165 100644
--- a/packages/embla-carousel-auto-scroll/src/components/AutoScroll.ts
+++ b/packages/embla-carousel-auto-scroll/src/components/AutoScroll.ts
@@ -83,10 +83,7 @@ function AutoScroll(userOptions: AutoScrollOptionsType = {}): AutoScrollType {
}
if (options.stopOnFocusIn) {
- eventStore.add(container, 'focusin', () => {
- stopScroll()
- emblaApi.scrollTo(emblaApi.selectedScrollSnap(), true)
- })
+ emblaApi.on('slideFocusStart', stopScroll)
if (!options.stopOnInteraction) {
eventStore.add(container, 'focusout', startScroll)
@@ -100,6 +97,7 @@ function AutoScroll(userOptions: AutoScrollOptionsType = {}): AutoScrollType {
emblaApi
.off('pointerDown', stopScroll)
.off('pointerUp', startScrollOnSettle)
+ .off('slideFocusStart', stopScroll)
.off('settle', onSettle)
stopScroll()
destroyed = true
diff --git a/packages/embla-carousel-autoplay/src/components/Autoplay.ts b/packages/embla-carousel-autoplay/src/components/Autoplay.ts
index 1baa6a887..86e9fff17 100644
--- a/packages/embla-carousel-autoplay/src/components/Autoplay.ts
+++ b/packages/embla-carousel-autoplay/src/components/Autoplay.ts
@@ -79,7 +79,7 @@ function Autoplay(userOptions: AutoplayOptionsType = {}): AutoplayType {
}
if (options.stopOnFocusIn) {
- eventStore.add(container, 'focusin', stopTimer)
+ emblaApi.on('slideFocusStart', stopTimer)
if (!options.stopOnInteraction) {
eventStore.add(container, 'focusout', startTimer)
@@ -92,7 +92,10 @@ function Autoplay(userOptions: AutoplayOptionsType = {}): AutoplayType {
}
function destroy(): void {
- emblaApi.off('pointerDown', stopTimer).off('pointerUp', startTimer)
+ emblaApi
+ .off('pointerDown', stopTimer)
+ .off('pointerUp', startTimer)
+ .off('slideFocusStart', stopTimer)
stopTimer()
destroyed = true
playing = false
diff --git a/packages/embla-carousel-docs/src/content/pages/api/options.mdx b/packages/embla-carousel-docs/src/content/pages/api/options.mdx
index fd0e3ffd9..c6c627c45 100644
--- a/packages/embla-carousel-docs/src/content/pages/api/options.mdx
+++ b/packages/embla-carousel-docs/src/content/pages/api/options.mdx
@@ -604,7 +604,23 @@ Enables for scrolling the carousel with mouse and touch interactions. Set this t
**Note:** When passing a custom callback it will run **before** the default
Embla drag behaviour. Return `true` in your callback if you want Embla to run
its default drag behaviour after your callback, or return `false` if you want
- to disable it.
+ to skip it.
+
+
+---
+
+### watchFocus
+
+Type: `boolean | (emblaApi: EmblaCarouselType, event: FocusEvent) => boolean | void`
+Default: `true`
+
+Embla automatically watches the [slides](api/options/#slides) for focus events. The default callback fires the [slideFocus](/api/events/#slidefocus/) event and [scrolls](/api/methods/#scrollto/) to the focused element. Set this to `false` to disable this behaviour or pass a custom callback to add your own focus logic.
+
+
+ **Note:** When passing a custom callback it will run **before** the default
+ Embla focus behaviour. Return `true` in your callback if you want Embla to run
+ its default focus behaviour after your callback, or return `false` if you want
+ to skip it.
---
@@ -620,7 +636,7 @@ Embla automatically watches the [container](/api/methods/#containernode/) and [s
**Note:** When passing a custom callback it will run **before** the default
Embla resize behaviour. Return `true` in your callback if you want Embla to
run its default resize behaviour after your callback, or return `false` if you
- want to disable it.
+ want to skip it.
---
@@ -636,7 +652,7 @@ Embla automatically watches the [container](/api/methods/#containernode/) for **
**Note:** When passing a custom callback it will run **before** the default
Embla mutation behaviour. Return `true` in your callback if you want Embla to
run its default mutation behaviour after your callback, or return `false` if
- you want to disable it.
+ you want to skip it.
---
diff --git a/packages/embla-carousel/src/components/EmblaCarousel.ts b/packages/embla-carousel/src/components/EmblaCarousel.ts
index 9b8148737..b620cb814 100644
--- a/packages/embla-carousel/src/components/EmblaCarousel.ts
+++ b/packages/embla-carousel/src/components/EmblaCarousel.ts
@@ -112,7 +112,7 @@ function EmblaCarousel(
engine.translate.to(engine.location.get())
engine.animation.init()
engine.slidesInView.init()
- engine.slideFocus.init()
+ engine.slideFocus.init(self)
engine.eventHandler.init(self)
engine.resizeHandler.init(self)
engine.slidesHandler.init(self)
diff --git a/packages/embla-carousel/src/components/Engine.ts b/packages/embla-carousel/src/components/Engine.ts
index f2a7d3626..c00e2d53f 100644
--- a/packages/embla-carousel/src/components/Engine.ts
+++ b/packages/embla-carousel/src/components/Engine.ts
@@ -99,7 +99,8 @@ export function Engine(
containScroll,
watchResize,
watchSlides,
- watchDrag
+ watchDrag,
+ watchFocus
} = options
// Measurements
@@ -263,7 +264,8 @@ export function Engine(
scrollTo,
scrollBody,
eventStore,
- eventHandler
+ eventHandler,
+ watchFocus
)
// Engine
diff --git a/packages/embla-carousel/src/components/EventHandler.ts b/packages/embla-carousel/src/components/EventHandler.ts
index aa911d748..cc2c1e942 100644
--- a/packages/embla-carousel/src/components/EventHandler.ts
+++ b/packages/embla-carousel/src/components/EventHandler.ts
@@ -17,6 +17,7 @@ export interface EmblaEventListType {
destroy: 'destroy'
reInit: 'reInit'
resize: 'resize'
+ slideFocusStart: 'slideFocusStart'
slideFocus: 'slideFocus'
}
diff --git a/packages/embla-carousel/src/components/Options.ts b/packages/embla-carousel/src/components/Options.ts
index cfee3164e..b42583f69 100644
--- a/packages/embla-carousel/src/components/Options.ts
+++ b/packages/embla-carousel/src/components/Options.ts
@@ -6,6 +6,7 @@ import { DragHandlerOptionType } from './DragHandler'
import { ResizeHandlerOptionType } from './ResizeHandler'
import { SlidesHandlerOptionType } from './SlidesHandler'
import { SlidesInViewOptionsType } from './SlidesInView'
+import { FocusHandlerOptionType } from './SlideFocus'
export type LooseOptionsType = {
[key: string]: unknown
@@ -36,6 +37,7 @@ export type OptionsType = CreateOptionsType<{
watchDrag: DragHandlerOptionType
watchResize: ResizeHandlerOptionType
watchSlides: SlidesHandlerOptionType
+ watchFocus: FocusHandlerOptionType
}>
export const defaultOptions: OptionsType = {
@@ -57,7 +59,8 @@ export const defaultOptions: OptionsType = {
active: true,
watchDrag: true,
watchResize: true,
- watchSlides: true
+ watchSlides: true,
+ watchFocus: true
}
export type EmblaOptionsType = Partial
diff --git a/packages/embla-carousel/src/components/SlideFocus.ts b/packages/embla-carousel/src/components/SlideFocus.ts
index a101e6413..521b97a3c 100644
--- a/packages/embla-carousel/src/components/SlideFocus.ts
+++ b/packages/embla-carousel/src/components/SlideFocus.ts
@@ -1,12 +1,20 @@
+import { EmblaCarouselType } from './EmblaCarousel'
import { EventHandlerType } from './EventHandler'
import { EventStoreType } from './EventStore'
import { ScrollBodyType } from './ScrollBody'
import { ScrollToType } from './ScrollTo'
import { SlideRegistryType } from './SlideRegistry'
-import { isNumber } from './utils'
+import { isBoolean, isNumber } from './utils'
+
+type FocusHandlerCallbackType = (
+ emblaApi: EmblaCarouselType,
+ evt: FocusEvent
+) => boolean | void
+
+export type FocusHandlerOptionType = boolean | FocusHandlerCallbackType
export type SlideFocusType = {
- init: () => void
+ init: (emblaApi: EmblaCarouselType) => void
}
export function SlideFocus(
@@ -16,43 +24,54 @@ export function SlideFocus(
scrollTo: ScrollToType,
scrollBody: ScrollBodyType,
eventStore: EventStoreType,
- eventHandler: EventHandlerType
+ eventHandler: EventHandlerType,
+ watchFocus: FocusHandlerOptionType
): SlideFocusType {
+ const focusListenerOptions = { passive: true, capture: true }
let lastTabPressTime = 0
- function init(): void {
- eventStore.add(document, 'keydown', registerTabPress, false)
- slides.forEach(addSlideFocusEvent)
- }
-
- function registerTabPress(event: KeyboardEvent): void {
- if (event.code === 'Tab') lastTabPressTime = new Date().getTime()
- }
+ function init(emblaApi: EmblaCarouselType): void {
+ if (!watchFocus) return
- function addSlideFocusEvent(slide: HTMLElement): void {
- const focus = (): void => {
+ function defaultCallback(index: number): void {
const nowTime = new Date().getTime()
const diffTime = nowTime - lastTabPressTime
if (diffTime > 10) return
+ eventHandler.emit('slideFocusStart')
root.scrollLeft = 0
- const index = slides.indexOf(slide)
+
const group = slideRegistry.findIndex((group) => group.includes(index))
if (!isNumber(group)) return
scrollBody.useDuration(0)
scrollTo.index(group, 0)
+
eventHandler.emit('slideFocus')
}
- eventStore.add(slide, 'focus', focus, {
- passive: true,
- capture: true
+ eventStore.add(document, 'keydown', registerTabPress, false)
+
+ slides.forEach((slide, slideIndex) => {
+ eventStore.add(
+ slide,
+ 'focus',
+ (evt: FocusEvent) => {
+ if (isBoolean(watchFocus) || watchFocus(emblaApi, evt)) {
+ defaultCallback(slideIndex)
+ }
+ },
+ focusListenerOptions
+ )
})
}
+ function registerTabPress(event: KeyboardEvent): void {
+ if (event.code === 'Tab') lastTabPressTime = new Date().getTime()
+ }
+
const self: SlideFocusType = {
init
}