@@ -7,6 +7,7 @@ import { defineCustomElement } from '@components/scripts/utils'
77import {
88 createAnimationController ,
99 type AnimationControllerHandle ,
10+ type AnimationPlayState ,
1011} from '@components/scripts/store'
1112import type { WebComponentModule } from '@components/scripts/@types/webComponentModule'
1213
@@ -23,6 +24,7 @@ const AUTOPLAY_OPTIONS = {
2324 delay : 4000 ,
2425 stopOnInteraction : true ,
2526 stopOnMouseEnter : true ,
27+ playOnInit : false ,
2628}
2729
2830type DebugEmblaElement = HTMLElement & { __emblaApi__ ?: EmblaCarouselType }
@@ -37,6 +39,11 @@ export class CarouselElement extends HTMLElement {
3739 private nextBtn : HTMLButtonElement | null = null
3840 private initialized = false
3941 private animationController : AnimationControllerHandle | undefined
42+ private pendingAutoplayState : AnimationPlayState | null = null
43+ private hasAutoplaySupport = false
44+ private autoplayReady = false
45+ private autoplayReadyScheduled = false
46+ private autoplayReadyTimer : ReturnType < typeof setTimeout > | null = null
4047 private readonly animationInstanceId : string
4148 private readonly domReadyHandler = ( ) => {
4249 document . removeEventListener ( 'DOMContentLoaded' , this . domReadyHandler )
@@ -93,24 +100,37 @@ export class CarouselElement extends HTMLElement {
93100 throw new ClientScriptError ( 'CarouselElement: Missing required DOM parts for initialization.' )
94101 }
95102
96- this . autoplayPlugin = Autoplay ( { ...AUTOPLAY_OPTIONS } )
97- this . emblaApi = EmblaCarousel ( this . viewport , EMBLA_OPTIONS , [ this . autoplayPlugin ] )
103+ const slideCount = this . querySelectorAll ( '[data-carousel-slide]' ) . length
104+ const requestedAutoplay = slideCount > 1 ? Autoplay ( { ...AUTOPLAY_OPTIONS } ) : null
105+ this . autoplayReady = false
106+ this . autoplayReadyScheduled = false
107+ const plugins = requestedAutoplay ? [ requestedAutoplay ] : [ ]
108+ this . emblaApi = EmblaCarousel ( this . viewport , EMBLA_OPTIONS , plugins )
98109 this . emblaRoot . __emblaApi__ = this . emblaApi
110+ const emblaSnapCount = this . emblaApi . scrollSnapList ( ) . length
111+ const supportsAutoplay = slideCount > 1 && emblaSnapCount > 1
112+ this . hasAutoplaySupport = supportsAutoplay
113+ this . autoplayPlugin = supportsAutoplay ? requestedAutoplay : null
99114 this . setAutoplayState ( 'paused' )
100115
101- const emblaWithEvents = this . emblaApi as EmblaCarouselType & {
102- on : ( _event : string , _handler : ( ) => void ) => EmblaCarouselType
103- }
116+ if ( this . autoplayPlugin ) {
117+ const emblaWithEvents = this . emblaApi as EmblaCarouselType & {
118+ on : ( _event : string , _handler : ( ) => void ) => EmblaCarouselType
119+ }
104120
105- emblaWithEvents . on ( 'autoplay:play' , this . autoplayPlayHandler )
106- emblaWithEvents . on ( 'autoplay:stop' , this . autoplayStopHandler )
121+ emblaWithEvents . on ( 'autoplay:play' , this . autoplayPlayHandler )
122+ emblaWithEvents . on ( 'autoplay:stop' , this . autoplayStopHandler )
123+ }
107124
108125 this . setupNavigationButtons ( )
109126 this . setupDotsNavigation ( )
110127
111128 this . initialized = true
112129 this . setAttribute ( 'data-carousel-ready' , 'true' )
113- this . registerAnimationLifecycle ( )
130+ if ( this . hasAutoplaySupport ) {
131+ this . registerAnimationLifecycle ( )
132+ this . scheduleAutoplayReady ( )
133+ }
114134 } catch ( error ) {
115135 this . teardown ( )
116136 handleScriptError ( error , context )
@@ -123,8 +143,10 @@ export class CarouselElement extends HTMLElement {
123143 off : ( _event : string , _handler : ( ) => void ) => EmblaCarouselType
124144 }
125145
126- emblaWithEvents . off ( 'autoplay:play' , this . autoplayPlayHandler )
127- emblaWithEvents . off ( 'autoplay:stop' , this . autoplayStopHandler )
146+ if ( this . autoplayPlugin ) {
147+ emblaWithEvents . off ( 'autoplay:play' , this . autoplayPlayHandler )
148+ emblaWithEvents . off ( 'autoplay:stop' , this . autoplayStopHandler )
149+ }
128150 this . emblaApi . destroy ( )
129151 }
130152 if ( this . emblaRoot && '__emblaApi__' in this . emblaRoot ) {
@@ -138,6 +160,14 @@ export class CarouselElement extends HTMLElement {
138160 this . removeAttribute ( 'data-carousel-autoplay' )
139161 this . animationController ?. destroy ( )
140162 this . animationController = undefined
163+ this . pendingAutoplayState = null
164+ this . hasAutoplaySupport = false
165+ if ( this . autoplayReadyTimer ) {
166+ clearTimeout ( this . autoplayReadyTimer )
167+ this . autoplayReadyTimer = null
168+ }
169+ this . autoplayReadyScheduled = false
170+ this . autoplayReady = false
141171
142172 if ( this . dotsContainer ) {
143173 this . dotsContainer . innerHTML = ''
@@ -249,17 +279,15 @@ export class CarouselElement extends HTMLElement {
249279
250280 pause ( ) : void {
251281 try {
252- this . autoplayPlugin ?. stop ( )
253- this . setAutoplayState ( 'paused' )
282+ this . updateAutoplayState ( 'paused' )
254283 } catch ( error ) {
255284 handleScriptError ( error , { scriptName : SCRIPT_NAME , operation : 'pause' } )
256285 }
257286 }
258287
259288 resume ( ) : void {
260289 try {
261- this . autoplayPlugin ?. play ( )
262- this . setAutoplayState ( 'playing' )
290+ this . updateAutoplayState ( 'playing' )
263291 } catch ( error ) {
264292 handleScriptError ( error , { scriptName : SCRIPT_NAME , operation : 'resume' } )
265293 }
@@ -269,8 +297,42 @@ export class CarouselElement extends HTMLElement {
269297 this . setAttribute ( 'data-carousel-autoplay' , state )
270298 }
271299
300+ private updateAutoplayState ( state : AnimationPlayState ) : void {
301+ if ( ! this . hasAutoplaySupport || ! this . autoplayPlugin || ! this . emblaApi || ! this . initialized || ! this . autoplayReady ) {
302+ if ( this . hasAutoplaySupport ) {
303+ this . pendingAutoplayState = state
304+ this . setAutoplayState ( state )
305+ } else {
306+ this . pendingAutoplayState = null
307+ this . setAutoplayState ( 'paused' )
308+ }
309+ return
310+ }
311+
312+ this . pendingAutoplayState = null
313+ if ( state === 'playing' ) {
314+ this . autoplayPlugin . play ( )
315+ } else {
316+ this . autoplayPlugin . stop ( )
317+ }
318+ this . setAutoplayState ( state )
319+ }
320+
321+ private flushPendingAutoplayState ( ) : void {
322+ if ( ! this . pendingAutoplayState || ! this . autoplayReady || ! this . autoplayPlugin ) return
323+
324+ const pendingState = this . pendingAutoplayState
325+ this . pendingAutoplayState = null
326+
327+ try {
328+ this . updateAutoplayState ( pendingState )
329+ } catch ( error ) {
330+ handleScriptError ( error , { scriptName : SCRIPT_NAME , operation : 'flushAutoplay' } )
331+ }
332+ }
333+
272334 private registerAnimationLifecycle ( ) : void {
273- if ( this . animationController ) return
335+ if ( this . animationController || ! this . hasAutoplaySupport ) return
274336 this . animationController = createAnimationController ( {
275337 animationId : 'carousel' ,
276338 instanceId : this . animationInstanceId ,
@@ -283,6 +345,26 @@ export class CarouselElement extends HTMLElement {
283345 } ,
284346 } )
285347 }
348+
349+ private scheduleAutoplayReady ( ) : void {
350+ if ( this . autoplayReadyScheduled || ! this . autoplayPlugin ) return
351+ this . autoplayReadyScheduled = true
352+
353+ const markReady = ( ) => {
354+ this . autoplayReadyScheduled = false
355+ this . autoplayReadyTimer = null
356+ if ( ! this . initialized || ! this . autoplayPlugin ) return
357+ this . autoplayReady = true
358+ this . flushPendingAutoplayState ( )
359+ }
360+
361+ if ( typeof window === 'undefined' ) {
362+ markReady ( )
363+ return
364+ }
365+
366+ this . autoplayReadyTimer = window . setTimeout ( markReady , 0 )
367+ }
286368}
287369
288370declare global {
0 commit comments