Skip to content

Commit

Permalink
Implement #936.
Browse files Browse the repository at this point in the history
  • Loading branch information
S-Shingler authored and davidjerleke committed Aug 20, 2024
1 parent 6474056 commit 87e1027
Show file tree
Hide file tree
Showing 8 changed files with 72 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
7 changes: 5 additions & 2 deletions packages/embla-carousel-autoplay/src/components/Autoplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
22 changes: 19 additions & 3 deletions packages/embla-carousel-docs/src/content/pages/api/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
</Admonition>

---

### watchFocus

Type: <BrandPrimaryText>`boolean | (emblaApi: EmblaCarouselType, event: FocusEvent) => boolean | void`</BrandPrimaryText>
Default: <BrandSecondaryText>`true`</BrandSecondaryText>

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.

<Admonition type="note">
**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.
</Admonition>

---
Expand All @@ -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.
</Admonition>

---
Expand All @@ -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.
</Admonition>

---
2 changes: 1 addition & 1 deletion packages/embla-carousel/src/components/EmblaCarousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions packages/embla-carousel/src/components/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ export function Engine(
containScroll,
watchResize,
watchSlides,
watchDrag
watchDrag,
watchFocus
} = options

// Measurements
Expand Down Expand Up @@ -263,7 +264,8 @@ export function Engine(
scrollTo,
scrollBody,
eventStore,
eventHandler
eventHandler,
watchFocus
)

// Engine
Expand Down
1 change: 1 addition & 0 deletions packages/embla-carousel/src/components/EventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface EmblaEventListType {
destroy: 'destroy'
reInit: 'reInit'
resize: 'resize'
slideFocusStart: 'slideFocusStart'
slideFocus: 'slideFocus'
}

Expand Down
5 changes: 4 additions & 1 deletion packages/embla-carousel/src/components/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -36,6 +37,7 @@ export type OptionsType = CreateOptionsType<{
watchDrag: DragHandlerOptionType
watchResize: ResizeHandlerOptionType
watchSlides: SlidesHandlerOptionType
watchFocus: FocusHandlerOptionType
}>

export const defaultOptions: OptionsType = {
Expand All @@ -57,7 +59,8 @@ export const defaultOptions: OptionsType = {
active: true,
watchDrag: true,
watchResize: true,
watchSlides: true
watchSlides: true,
watchFocus: true
}

export type EmblaOptionsType = Partial<OptionsType>
53 changes: 36 additions & 17 deletions packages/embla-carousel/src/components/SlideFocus.ts
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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
}
Expand Down

0 comments on commit 87e1027

Please sign in to comment.