Skip to content

Commit

Permalink
Merge pull request #549 from davidjerleke/feature/#456
Browse files Browse the repository at this point in the history
Support using tab(focus) to select and navigate carousel
  • Loading branch information
davidjerleke authored Aug 3, 2023
2 parents 2ad7ca6 + 03e5ec4 commit 7b3fac6
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 48 deletions.
6 changes: 3 additions & 3 deletions packages/embla-carousel/src/__tests__/PluginsHandler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,21 @@ afterEach(() => {
describe('PluginsHandler', () => {
describe('Init', () => {
test('Initializes the plugins passed', () => {
pluginsHandler.init(plugins, emblaApi)
pluginsHandler.init(emblaApi, plugins)

expect(autoplay.init).toHaveBeenCalledTimes(1)
expect(classNames.init).toHaveBeenCalledTimes(1)
})

test('Returns a object with plugin API:s', () => {
const pluginApis = pluginsHandler.init(plugins, emblaApi)
const pluginApis = pluginsHandler.init(emblaApi, plugins)
expect(pluginApis).toEqual({ autoplay, classNames })
})
})

describe('Destroy', () => {
test('Destroys the plugins', () => {
pluginsHandler.init(plugins, emblaApi)
pluginsHandler.init(emblaApi, plugins)
pluginsHandler.destroy()

expect(autoplay.destroy).toHaveBeenCalledTimes(1)
Expand Down
10 changes: 4 additions & 6 deletions packages/embla-carousel/src/components/DragHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type DragHandlerCallbackType = (
export type DragHandlerOptionType = boolean | DragHandlerCallbackType

export type DragHandlerType = {
init: (emblaApi: EmblaCarouselType, watchDrag: DragHandlerOptionType) => void
init: (emblaApi: EmblaCarouselType) => void
destroy: () => void
pointerDown: () => boolean
}
Expand All @@ -54,7 +54,8 @@ export function DragHandler(
dragFree: boolean,
dragThreshold: number,
skipSnaps: boolean,
baseFriction: number
baseFriction: number,
watchDrag: DragHandlerOptionType
): DragHandlerType {
const { cross: crossAxis } = axis
const focusNodes = ['INPUT', 'SELECT', 'TEXTAREA']
Expand All @@ -74,10 +75,7 @@ export function DragHandler(
let preventClick = false
let isMouse = false

function init(
emblaApi: EmblaCarouselType,
watchDrag: DragHandlerOptionType
): void {
function init(emblaApi: EmblaCarouselType): void {
if (!watchDrag) return

function downIfAllowed(evt: PointerEventType): void {
Expand Down
12 changes: 5 additions & 7 deletions packages/embla-carousel/src/components/EmblaCarousel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,21 +122,19 @@ function EmblaCarousel(

engine.translate.to(engine.location.get())
engine.slidesInView.init()
engine.slideFocus.init()
engine.eventHandler.init(self)
engine.resizeHandler.init(self, options.watchResize)
engine.slidesHandler.init(self, options.watchSlides)
engine.resizeHandler.init(self)
engine.slidesHandler.init(self)

documentVisibleHandler.add(ownerDocument, 'visibilitychange', () => {
if (ownerDocument.hidden) animations.reset()
})

if (engine.options.loop) engine.slideLooper.loop()
if (container.offsetParent && slides.length) engine.dragHandler.init(self)

if (container.offsetParent && slides.length) {
engine.dragHandler.init(self, options.watchDrag)
}

pluginApis = pluginsHandler.init(pluginList, self)
pluginApis = pluginsHandler.init(self, pluginList)
}

function reActivate(
Expand Down
31 changes: 24 additions & 7 deletions packages/embla-carousel/src/components/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ScrollSnaps } from './ScrollSnaps'
import { SlideRegistry, SlideRegistryType } from './SlideRegistry'
import { ScrollTarget, ScrollTargetType } from './ScrollTarget'
import { ScrollTo, ScrollToType } from './ScrollTo'
import { SlideFocus, SlideFocusType } from './SlideFocus'
import { SlideLooper, SlideLooperType } from './SlideLooper'
import { SlidesHandler, SlidesHandlerType } from './SlidesHandler'
import { SlidesInView, SlidesInViewType } from './SlidesInView'
Expand Down Expand Up @@ -67,6 +68,7 @@ export type EngineType = {
scrollSnapList: number[]
scrollSnaps: number[]
slideIndexes: number[]
slideFocus: SlideFocusType
slideRegistry: SlideRegistryType['slideRegistry']
containerRect: DOMRect
slideRects: DOMRect[]
Expand Down Expand Up @@ -94,7 +96,10 @@ export function Engine(
dragThreshold,
slidesToScroll: groupSlides,
skipSnaps,
containScroll
containScroll,
watchResize,
watchSlides,
watchDrag
} = options

// Measurements
Expand Down Expand Up @@ -225,15 +230,24 @@ export function Engine(
eventHandler
)
const scrollProgress = ScrollProgress(limit)
const eventStore = EventStore()
const slidesInView = SlidesInView(slides, eventHandler)
const { slideRegistry } = SlideRegistry(
viewSize,
contentSize,
containSnaps,
scrollContainLimit,
containScroll,
slidesToScroll,
slideIndexes
)
const slidesInView = SlidesInView(slides, eventHandler)
const slideFocus = SlideFocus(
root,
slides,
slideRegistry,
scrollTo,
scrollBody,
eventStore
)

// Engine
const engine: EngineType = {
Expand Down Expand Up @@ -264,9 +278,10 @@ export function Engine(
dragFree,
dragThreshold,
skipSnaps,
friction
friction,
watchDrag
),
eventStore: EventStore(),
eventStore,
percentOfView,
index,
indexPrevious,
Expand All @@ -279,7 +294,8 @@ export function Engine(
eventHandler,
ownerWindow,
slides,
axis
axis,
watchResize
),
scrollBody,
scrollBounds: ScrollBounds(
Expand Down Expand Up @@ -311,7 +327,8 @@ export function Engine(
offsetLocation,
slides
),
slidesHandler: SlidesHandler(container, eventHandler),
slideFocus,
slidesHandler: SlidesHandler(container, eventHandler, watchSlides),
slidesInView,
slideIndexes,
slideRegistry,
Expand Down
8 changes: 4 additions & 4 deletions packages/embla-carousel/src/components/PluginsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { EmblaPluginsType, EmblaPluginType } from './Plugins'

export type PluginsHandlerType = {
init: (
plugins: EmblaPluginType[],
embla: EmblaCarouselType
emblaApi: EmblaCarouselType,
plugins: EmblaPluginType[]
) => EmblaPluginsType
destroy: () => void
}
Expand All @@ -16,8 +16,8 @@ export function PluginsHandler(
let activePlugins: EmblaPluginType[] = []

function init(
plugins: EmblaPluginType[],
emblaApi: EmblaCarouselType
emblaApi: EmblaCarouselType,
plugins: EmblaPluginType[]
): EmblaPluginsType {
activePlugins = plugins.filter(
({ options }) => optionsHandler.optionsAtMedia(options).active !== false
Expand Down
13 changes: 4 additions & 9 deletions packages/embla-carousel/src/components/ResizeHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@ type ResizeHandlerCallbackType = (
export type ResizeHandlerOptionType = boolean | ResizeHandlerCallbackType

export type ResizeHandlerType = {
init: (
emblaApi: EmblaCarouselType,
watchResize: ResizeHandlerOptionType
) => void
init: (emblaApi: EmblaCarouselType) => void
destroy: () => void
}

Expand All @@ -23,7 +20,8 @@ export function ResizeHandler(
eventHandler: EventHandlerType,
ownerWindow: WindowType,
slides: HTMLElement[],
axis: AxisType
axis: AxisType,
watchResize: ResizeHandlerOptionType
): ResizeHandlerType {
let resizeObserver: ResizeObserver
let containerSize: number
Expand All @@ -34,10 +32,7 @@ export function ResizeHandler(
return axis.measureSize(node.getBoundingClientRect())
}

function init(
emblaApi: EmblaCarouselType,
watchResize: ResizeHandlerOptionType
): void {
function init(emblaApi: EmblaCarouselType): void {
if (!watchResize) return

containerSize = readSize(container)
Expand Down
57 changes: 57 additions & 0 deletions packages/embla-carousel/src/components/SlideFocus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { EventStoreType } from './EventStore'
import { ScrollBodyType } from './ScrollBody'
import { ScrollToType } from './ScrollTo'
import { SlideRegistryType } from './SlideRegistry'
import { isNumber } from './utils'

export type SlideFocusType = {
init: () => void
}

export function SlideFocus(
root: HTMLElement,
slides: HTMLElement[],
slideRegistry: SlideRegistryType['slideRegistry'],
scrollTo: ScrollToType,
scrollBody: ScrollBodyType,
eventStore: EventStoreType
): SlideFocusType {
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 addSlideFocusEvent(slide: HTMLElement): void {
const focus = (): void => {
const nowTime = new Date().getTime()
const diffTime = nowTime - lastTabPressTime

if (diffTime > 10) return

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)
}

eventStore.add(slide, 'focus', focus, {
passive: true,
capture: true
})
}

const self: SlideFocusType = {
init
}
return self
}
5 changes: 2 additions & 3 deletions packages/embla-carousel/src/components/SlideRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { LimitType } from './Limit'
import { ScrollContainOptionType } from './ScrollContain'
import { SlidesToScrollType } from './SlidesToScroll'
import { arrayFromNumber, arrayLast, arrayLastIndex } from './utils'

Expand All @@ -10,8 +9,8 @@ export type SlideRegistryType = {
export function SlideRegistry(
viewSize: number,
contentSize: number,
containSnaps: boolean,
scrollContainLimit: LimitType,
containScroll: ScrollContainOptionType,
slidesToScroll: SlidesToScrollType,
slideIndexes: number[]
): SlideRegistryType {
Expand All @@ -21,7 +20,7 @@ export function SlideRegistry(

function createSlideRegistry(): number[][] {
const groupedSlideIndexes = groupSlides(slideIndexes)
if (!containScroll || contentSize <= viewSize) return groupedSlideIndexes
if (!containSnaps || contentSize <= viewSize) return groupedSlideIndexes

return groupedSlideIndexes.slice(min, max).map((group, index, groups) => {
const indexIsFirst = !index
Expand Down
13 changes: 4 additions & 9 deletions packages/embla-carousel/src/components/SlidesHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,19 @@ type SlidesHandlerCallbackType = (
export type SlidesHandlerOptionType = boolean | SlidesHandlerCallbackType

export type SlidesHandlerType = {
init: (
emblaApi: EmblaCarouselType,
watchSlides: SlidesHandlerOptionType
) => void
init: (emblaApi: EmblaCarouselType) => void
destroy: () => void
}

export function SlidesHandler(
container: HTMLElement,
eventHandler: EventHandlerType
eventHandler: EventHandlerType,
watchSlides: SlidesHandlerOptionType
): SlidesHandlerType {
let mutationObserver: MutationObserver
let destroyed = false

function init(
emblaApi: EmblaCarouselType,
watchSlides: SlidesHandlerOptionType
): void {
function init(emblaApi: EmblaCarouselType): void {
if (!watchSlides) return

function defaultCallback(mutations: MutationRecord[]): void {
Expand Down

0 comments on commit 7b3fac6

Please sign in to comment.