From 03e5ec49c1097084e04eb881a92bcce62f3ed546 Mon Sep 17 00:00:00 2001 From: David Jerleke Date: Thu, 3 Aug 2023 21:55:36 +0200 Subject: [PATCH] Implement #456. --- .../src/__tests__/PluginsHandler.test.ts | 6 +- .../src/components/DragHandler.ts | 10 ++-- .../src/components/EmblaCarousel.ts | 12 ++-- .../embla-carousel/src/components/Engine.ts | 31 +++++++--- .../src/components/PluginsHandler.ts | 8 +-- .../src/components/ResizeHandler.ts | 13 ++--- .../src/components/SlideFocus.ts | 57 +++++++++++++++++++ .../src/components/SlideRegistry.ts | 5 +- .../src/components/SlidesHandler.ts | 13 ++--- 9 files changed, 107 insertions(+), 48 deletions(-) create mode 100644 packages/embla-carousel/src/components/SlideFocus.ts diff --git a/packages/embla-carousel/src/__tests__/PluginsHandler.test.ts b/packages/embla-carousel/src/__tests__/PluginsHandler.test.ts index c3c61a9d7..7c200d993 100644 --- a/packages/embla-carousel/src/__tests__/PluginsHandler.test.ts +++ b/packages/embla-carousel/src/__tests__/PluginsHandler.test.ts @@ -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) diff --git a/packages/embla-carousel/src/components/DragHandler.ts b/packages/embla-carousel/src/components/DragHandler.ts index 43c2d3b13..dd4eff7b8 100644 --- a/packages/embla-carousel/src/components/DragHandler.ts +++ b/packages/embla-carousel/src/components/DragHandler.ts @@ -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 } @@ -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'] @@ -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 { diff --git a/packages/embla-carousel/src/components/EmblaCarousel.ts b/packages/embla-carousel/src/components/EmblaCarousel.ts index 3e98ed5ef..2d1b9f57e 100644 --- a/packages/embla-carousel/src/components/EmblaCarousel.ts +++ b/packages/embla-carousel/src/components/EmblaCarousel.ts @@ -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( diff --git a/packages/embla-carousel/src/components/Engine.ts b/packages/embla-carousel/src/components/Engine.ts index 7e2aeedb7..646e7fd9f 100644 --- a/packages/embla-carousel/src/components/Engine.ts +++ b/packages/embla-carousel/src/components/Engine.ts @@ -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' @@ -67,6 +68,7 @@ export type EngineType = { scrollSnapList: number[] scrollSnaps: number[] slideIndexes: number[] + slideFocus: SlideFocusType slideRegistry: SlideRegistryType['slideRegistry'] containerRect: DOMRect slideRects: DOMRect[] @@ -94,7 +96,10 @@ export function Engine( dragThreshold, slidesToScroll: groupSlides, skipSnaps, - containScroll + containScroll, + watchResize, + watchSlides, + watchDrag } = options // Measurements @@ -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 = { @@ -264,9 +278,10 @@ export function Engine( dragFree, dragThreshold, skipSnaps, - friction + friction, + watchDrag ), - eventStore: EventStore(), + eventStore, percentOfView, index, indexPrevious, @@ -279,7 +294,8 @@ export function Engine( eventHandler, ownerWindow, slides, - axis + axis, + watchResize ), scrollBody, scrollBounds: ScrollBounds( @@ -311,7 +327,8 @@ export function Engine( offsetLocation, slides ), - slidesHandler: SlidesHandler(container, eventHandler), + slideFocus, + slidesHandler: SlidesHandler(container, eventHandler, watchSlides), slidesInView, slideIndexes, slideRegistry, diff --git a/packages/embla-carousel/src/components/PluginsHandler.ts b/packages/embla-carousel/src/components/PluginsHandler.ts index 5c6a35905..c3adf0a61 100644 --- a/packages/embla-carousel/src/components/PluginsHandler.ts +++ b/packages/embla-carousel/src/components/PluginsHandler.ts @@ -4,8 +4,8 @@ import { EmblaPluginsType, EmblaPluginType } from './Plugins' export type PluginsHandlerType = { init: ( - plugins: EmblaPluginType[], - embla: EmblaCarouselType + emblaApi: EmblaCarouselType, + plugins: EmblaPluginType[] ) => EmblaPluginsType destroy: () => void } @@ -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 diff --git a/packages/embla-carousel/src/components/ResizeHandler.ts b/packages/embla-carousel/src/components/ResizeHandler.ts index 33665e924..b860bb840 100644 --- a/packages/embla-carousel/src/components/ResizeHandler.ts +++ b/packages/embla-carousel/src/components/ResizeHandler.ts @@ -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 } @@ -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 @@ -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) diff --git a/packages/embla-carousel/src/components/SlideFocus.ts b/packages/embla-carousel/src/components/SlideFocus.ts new file mode 100644 index 000000000..1df0e4f5c --- /dev/null +++ b/packages/embla-carousel/src/components/SlideFocus.ts @@ -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 +} diff --git a/packages/embla-carousel/src/components/SlideRegistry.ts b/packages/embla-carousel/src/components/SlideRegistry.ts index 7f427a5b8..74db6dc2d 100644 --- a/packages/embla-carousel/src/components/SlideRegistry.ts +++ b/packages/embla-carousel/src/components/SlideRegistry.ts @@ -1,5 +1,4 @@ import { LimitType } from './Limit' -import { ScrollContainOptionType } from './ScrollContain' import { SlidesToScrollType } from './SlidesToScroll' import { arrayFromNumber, arrayLast, arrayLastIndex } from './utils' @@ -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 { @@ -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 diff --git a/packages/embla-carousel/src/components/SlidesHandler.ts b/packages/embla-carousel/src/components/SlidesHandler.ts index b146f5c70..7cae0f55a 100644 --- a/packages/embla-carousel/src/components/SlidesHandler.ts +++ b/packages/embla-carousel/src/components/SlidesHandler.ts @@ -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 {