diff --git a/src/blocks/carousel/__tests__/edit.test.tsx b/src/blocks/carousel/__tests__/edit.test.tsx
index 716238b..c73926f 100644
--- a/src/blocks/carousel/__tests__/edit.test.tsx
+++ b/src/blocks/carousel/__tests__/edit.test.tsx
@@ -257,6 +257,7 @@ describe( 'Carousel Edit setup flow', () => {
expect( selectLabels ).not.toContain( 'Contain Scroll' );
expect( toggleLabels ).not.toContain( 'Free Drag' );
expect( toggleLabels ).not.toContain( 'Scroll Auto' );
+ expect( toggleLabels ).toContain( 'Enable Auto Scroll' );
expect( ( RangeControl as unknown as jest.Mock ).mock.calls.some(
( [ props ] ) => props.label === 'Slides to Scroll',
) ).toBe( false );
@@ -282,6 +283,7 @@ describe( 'Carousel Edit setup flow', () => {
expect( selectLabels ).toContain( 'Contain Scroll' );
expect( toggleLabels ).toContain( 'Free Drag' );
expect( toggleLabels ).toContain( 'Scroll Auto' );
+ expect( toggleLabels ).toContain( 'Enable Auto Scroll' );
} );
it( 'calls setAttributes with the selected transition', () => {
@@ -302,4 +304,43 @@ describe( 'Carousel Edit setup flow', () => {
expect( setAttributes ).toHaveBeenCalledWith( { transition: 'fade' } );
} );
+
+ it( 'disables the transition dropdown with notice when autoScroll is enabled', () => {
+ render(
+ ,
+ );
+
+ const transitionCall = ( SelectControl as unknown as jest.Mock ).mock.calls.find(
+ ( [ props ] ) => props.label === 'Transition',
+ );
+
+ expect( transitionCall[ 0 ].disabled ).toBe( true );
+ expect( transitionCall[ 0 ].help ).toBe( 'Auto Scroll does not support transitions.' );
+ } );
+
+ it( 'switches transition to slide when autoScroll is enabled', () => {
+ const setAttributes = jest.fn();
+ render(
+ ,
+ );
+
+ const autoScrollToggle = ( ToggleControl as unknown as jest.Mock ).mock.calls.find(
+ ( [ props ] ) => props.label === 'Enable Auto Scroll',
+ );
+
+ autoScrollToggle[ 0 ].onChange( true );
+
+ expect( setAttributes ).toHaveBeenCalledWith( expect.objectContaining( {
+ autoScroll: true,
+ transition: 'slide',
+ } ) );
+ } );
} );
diff --git a/src/blocks/carousel/__tests__/view.test.ts b/src/blocks/carousel/__tests__/view.test.ts
index 0daab79..b886e29 100644
--- a/src/blocks/carousel/__tests__/view.test.ts
+++ b/src/blocks/carousel/__tests__/view.test.ts
@@ -16,6 +16,7 @@ import { store, getContext, getElement } from '@wordpress/interactivity';
import EmblaCarousel, { type EmblaCarouselType } from 'embla-carousel';
import Fade from 'embla-carousel-fade';
+import AutoScroll from 'embla-carousel-auto-scroll';
// Symbol key used by the view.ts for Embla instances
const EMBLA_KEY = Symbol.for( 'rt-carousel.carousel' );
@@ -174,6 +175,29 @@ describe( 'Carousel View Module', () => {
expect( result ).toBe( false );
} );
+
+ it( 'should return true when autoScroll is enabled', () => {
+ const mockContext = createMockContext( {
+ canScrollPrev: false,
+ autoScroll: {
+ speed: 2,
+ direction: 'forward',
+ startDelay: 1000,
+ stopOnInteraction: true,
+ stopOnMouseEnter: false,
+ stopOnFocusIn: true,
+ },
+ } );
+ ( getContext as jest.Mock ).mockReturnValue( mockContext );
+
+ const result =
+ Object.getOwnPropertyDescriptor(
+ storeConfig.state,
+ 'canScrollPrev',
+ )?.get?.() ?? false;
+
+ expect( result ).toBe( true );
+ } );
} );
describe( 'canScrollNext', () => {
@@ -202,6 +226,29 @@ describe( 'Carousel View Module', () => {
expect( result ).toBe( false );
} );
+
+ it( 'should return true when autoScroll is enabled', () => {
+ const mockContext = createMockContext( {
+ canScrollNext: false,
+ autoScroll: {
+ speed: 2,
+ direction: 'forward',
+ startDelay: 1000,
+ stopOnInteraction: true,
+ stopOnMouseEnter: false,
+ stopOnFocusIn: true,
+ },
+ } );
+ ( getContext as jest.Mock ).mockReturnValue( mockContext );
+
+ const result =
+ Object.getOwnPropertyDescriptor(
+ storeConfig.state,
+ 'canScrollNext',
+ )?.get?.() ?? false;
+
+ expect( result ).toBe( true );
+ } );
} );
} );
@@ -913,6 +960,56 @@ describe( 'Carousel View Module', () => {
originalIntersectionObserver;
}
} );
+
+ it( 'includes the AutoScroll plugin when transition is fade', () => {
+ const mockContext = createMockContext( {
+ transition: 'fade',
+ autoScroll: {
+ speed: 2,
+ direction: 'forward',
+ startDelay: 1000,
+ stopOnInteraction: true,
+ stopOnMouseEnter: false,
+ stopOnFocusIn: true,
+ },
+ } );
+ const { wrapper, viewport } = createMockCarouselDOM();
+ const originalIntersectionObserver = window.IntersectionObserver;
+
+ viewport.getBoundingClientRect = jest.fn( () => ( {
+ width: 100,
+ height: 0,
+ top: 0,
+ right: 0,
+ bottom: 0,
+ left: 0,
+ x: 0,
+ y: 0,
+ toJSON: () => ( {} ),
+ } ) );
+
+ ( getContext as jest.Mock ).mockReturnValue( mockContext );
+ ( getElement as jest.Mock ).mockReturnValue( { ref: wrapper } );
+ ( EmblaCarousel as unknown as jest.Mock ).mockReturnValue(
+ createMockEmblaInstance( {
+ scrollProgress: jest.fn( () => 0 ),
+ slideNodes: jest.fn( () => [] ),
+ } ),
+ );
+ delete ( window as Window & { IntersectionObserver?: typeof IntersectionObserver } ).IntersectionObserver;
+
+ try {
+ storeConfig.callbacks.initCarousel();
+
+ const lastCall = ( EmblaCarousel as unknown as jest.Mock ).mock.calls.at( -1 );
+ expect( lastCall?.[ 2 ] ).toContainEqual( ( Fade as unknown as jest.Mock ).mock.results.at( -1 )?.value );
+ const autoScrollMockResult = ( AutoScroll as unknown as jest.Mock ).mock.results.at( -1 )?.value;
+ expect( lastCall?.[ 2 ] ).toContainEqual( autoScrollMockResult );
+ } finally {
+ ( window as Window & { IntersectionObserver?: typeof IntersectionObserver } ).IntersectionObserver =
+ originalIntersectionObserver;
+ }
+ } );
} );
} );
} );
diff --git a/src/blocks/carousel/controls/edit.tsx b/src/blocks/carousel/controls/edit.tsx
index da15df7..c02d159 100644
--- a/src/blocks/carousel/controls/edit.tsx
+++ b/src/blocks/carousel/controls/edit.tsx
@@ -13,6 +13,7 @@ export default function Edit() {
emblaApi: contextApi,
canScrollPrev,
canScrollNext,
+ carouselOptions,
} = useContext( EditorCarouselContext );
const ref = useRef( null );
@@ -58,7 +59,7 @@ export default function Edit() {
handleScroll( 'prev' );
} }
type="button"
- disabled={ ! canScrollPrev }
+ disabled={ ! carouselOptions?.autoScroll && ! canScrollPrev }
aria-label={ __( 'Previous Slide', 'rt-carousel' ) }
>
@@ -70,7 +71,7 @@ export default function Edit() {
handleScroll( 'next' );
} }
type="button"
- disabled={ ! canScrollNext }
+ disabled={ ! carouselOptions?.autoScroll && ! canScrollNext }
aria-label={ __( 'Next Slide', 'rt-carousel' ) }
>
diff --git a/src/blocks/carousel/edit.tsx b/src/blocks/carousel/edit.tsx
index 0f2ca83..c4a31c3 100644
--- a/src/blocks/carousel/edit.tsx
+++ b/src/blocks/carousel/edit.tsx
@@ -322,6 +322,7 @@ export default function Edit( {
setAttributes( { transition: value as CarouselAttributes[ 'transition' ] } )
}
- help={ __(
- 'Choose how slides transition: sliding horizontally or cross-fading.',
- 'rt-carousel',
- ) }
+ help={
+ autoScroll
+ ? __( 'Auto Scroll does not support transitions.', 'rt-carousel' )
+ : __(
+ 'Choose how slides transition: sliding horizontally or cross-fading.',
+ 'rt-carousel',
+ )
+ }
/>
setAttributes( {
- autoScroll: value,
- autoplay: value ? false : autoplay,
- loop: ( value && autoScrollDirection === 'backward' ) ? true : loop,
- } ) }
+ onChange={ ( value ) => {
+ const nextAttributes: Partial< CarouselAttributes > = {
+ autoScroll: value,
+ autoplay: value ? false : autoplay,
+ loop: ( value && autoScrollDirection === 'backward' ) ? true : loop,
+ };
+ if ( value ) {
+ nextAttributes.transition = 'slide';
+ }
+ setAttributes( nextAttributes );
+ } }
/>
{ autoScroll && ( <>
();
- return context.canScrollPrev;
+ return context.autoScroll ? true : context.canScrollPrev;
},
get canScrollNext() {
const context = getContext();
- return context.canScrollNext;
+ return context.autoScroll ? true : context.canScrollNext;
},
},
actions: {
diff --git a/tests/js/setup.ts b/tests/js/setup.ts
index e2b7da9..21ba522 100644
--- a/tests/js/setup.ts
+++ b/tests/js/setup.ts
@@ -61,6 +61,22 @@ jest.mock( 'embla-carousel-fade', () => ( {
} ) ),
} ) );
+/**
+ * Mock embla-carousel-auto-scroll module.
+ */
+jest.mock( 'embla-carousel-auto-scroll', () => ( {
+ __esModule: true,
+ default: jest.fn( () => ( {
+ name: 'autoScroll',
+ options: {},
+ init: jest.fn(),
+ destroy: jest.fn(),
+ play: jest.fn(),
+ stop: jest.fn(),
+ isPlaying: jest.fn( () => false ),
+ } ) ),
+} ) );
+
/**
* Mock WordPress block editor components.
*/