diff --git a/src/blocks/carousel/__tests__/edit.test.tsx b/src/blocks/carousel/__tests__/edit.test.tsx
index fc90528..80ebf80 100644
--- a/src/blocks/carousel/__tests__/edit.test.tsx
+++ b/src/blocks/carousel/__tests__/edit.test.tsx
@@ -407,7 +407,9 @@ describe( 'useTabs toggle', () => {
toggleCall[ 0 ].onChange( true );
- expect( setAttributes ).toHaveBeenCalledWith( expect.objectContaining( { useTabs: true } ) );
+ expect( setAttributes ).toHaveBeenCalledWith(
+ expect.objectContaining( { useTabs: true, transition: 'slide', autoScroll: false } ),
+ );
expect( mockInsertBlock ).toHaveBeenCalledWith(
expect.objectContaining( { name: 'rt-carousel/carousel-tab-list' } ),
0,
@@ -436,18 +438,92 @@ describe( 'useTabs toggle', () => {
toggleCall[ 0 ].onChange( true );
- expect( setAttributes ).toHaveBeenCalledWith( expect.objectContaining( { useTabs: true } ) );
+ expect( setAttributes ).toHaveBeenCalledWith(
+ expect.objectContaining( { useTabs: true, transition: 'slide', autoScroll: false } ),
+ );
expect( mockInsertBlock ).not.toHaveBeenCalled();
} );
- it( 'removes all tab list blocks (including nested ones) when toggling Use as Tabs OFF', () => {
+ it( 'removes tab list blocks and re-inserts full nav group container when toggling Use as Tabs OFF and no nav blocks exist', () => {
mockBlocks = [
{ name: 'rt-carousel/carousel-tab-list', clientId: 'top-tab-list', innerBlocks: [] },
+ { name: 'rt-carousel/carousel-viewport', clientId: 'viewport-1', innerBlocks: [] },
+ ];
+ const setAttributes = jest.fn();
+
+ render(
+ ,
+ );
+
+ const toggleCall = ( ToggleControl as unknown as jest.Mock ).mock.calls.find(
+ ( [ props ] ) => props.label === 'Use as Tabs',
+ );
+
+ toggleCall[ 0 ].onChange( false );
+
+ expect( setAttributes ).toHaveBeenCalledWith( { useTabs: false } );
+ expect( mockRemoveBlocks ).toHaveBeenCalledWith( [ 'top-tab-list' ] );
+ expect( mockInsertBlock ).toHaveBeenCalledWith(
+ expect.objectContaining( { name: 'core/group' } ),
+ undefined,
+ 'test-client-id',
+ );
+ } );
+
+ it( 'removes navigation group row containers and navigation blocks when toggling Use as Tabs ON', () => {
+ mockBlocks = [
+ { name: 'rt-carousel/carousel-viewport', clientId: 'viewport-1', innerBlocks: [] },
{
name: 'core/group',
clientId: 'group-1',
innerBlocks: [
- { name: 'rt-carousel/carousel-tab-list', clientId: 'nested-tab-list', innerBlocks: [] },
+ { name: 'rt-carousel/carousel-controls', clientId: 'nested-controls', innerBlocks: [] },
+ { name: 'rt-carousel/carousel-counter', clientId: 'nested-counter', innerBlocks: [] },
+ { name: 'rt-carousel/carousel-dots', clientId: 'nested-dots', innerBlocks: [] },
+ ],
+ },
+ ];
+ const setAttributes = jest.fn();
+
+ render(
+ ,
+ );
+
+ const toggleCall = ( ToggleControl as unknown as jest.Mock ).mock.calls.find(
+ ( [ props ] ) => props.label === 'Use as Tabs',
+ );
+
+ toggleCall[ 0 ].onChange( true );
+
+ expect( setAttributes ).toHaveBeenCalledWith( expect.objectContaining( { useTabs: true } ) );
+ const removedIds = mockRemoveBlocks.mock.calls[ 0 ][ 0 ];
+ expect( removedIds ).toHaveLength( 4 );
+ expect( removedIds ).toEqual(
+ expect.arrayContaining( [
+ 'group-1',
+ 'nested-controls',
+ 'nested-counter',
+ 'nested-dots',
+ ] ),
+ );
+ } );
+
+ it( 'restores missing nav blocks into the core/group that actually contains nav elements, ignoring unrelated groups', () => {
+ mockBlocks = [
+ { name: 'core/group', clientId: 'unrelated-group', innerBlocks: [] },
+ {
+ name: 'core/group',
+ clientId: 'nav-group-1',
+ innerBlocks: [
+ { name: 'rt-carousel/carousel-dots', clientId: 'existing-dots', innerBlocks: [] },
],
},
];
@@ -467,7 +543,61 @@ describe( 'useTabs toggle', () => {
toggleCall[ 0 ].onChange( false );
- expect( setAttributes ).toHaveBeenCalledWith( { useTabs: false } );
- expect( mockRemoveBlocks ).toHaveBeenCalledWith( [ 'top-tab-list', 'nested-tab-list' ] );
+ expect( mockInsertBlock ).toHaveBeenCalledWith(
+ expect.objectContaining( { name: 'rt-carousel/carousel-controls' } ),
+ undefined,
+ 'nav-group-1',
+ );
+ expect( mockInsertBlock ).toHaveBeenCalledWith(
+ expect.objectContaining( { name: 'rt-carousel/carousel-counter' } ),
+ undefined,
+ 'nav-group-1',
+ );
+ } );
+
+ it( 're-inserts only missing nav blocks (counter and dots) when controls already exist in a nav group', () => {
+ mockBlocks = [
+ {
+ name: 'core/group',
+ clientId: 'nav-group-1',
+ innerBlocks: [
+ { name: 'rt-carousel/carousel-controls', clientId: 'existing-controls', innerBlocks: [] },
+ ],
+ },
+ ];
+ const setAttributes = jest.fn();
+
+ render(
+ ,
+ );
+
+ const toggleCall = ( ToggleControl as unknown as jest.Mock ).mock.calls.find(
+ ( [ props ] ) => props.label === 'Use as Tabs',
+ );
+
+ toggleCall[ 0 ].onChange( false );
+
+ // Controls already exist — should NOT be inserted again
+ expect( mockInsertBlock ).not.toHaveBeenCalledWith(
+ expect.objectContaining( { name: 'rt-carousel/carousel-controls' } ),
+ expect.anything(),
+ expect.anything(),
+ );
+
+ // Missing counter and dots SHOULD be inserted into nav-group-1
+ expect( mockInsertBlock ).toHaveBeenCalledWith(
+ expect.objectContaining( { name: 'rt-carousel/carousel-counter' } ),
+ undefined,
+ 'nav-group-1',
+ );
+ expect( mockInsertBlock ).toHaveBeenCalledWith(
+ expect.objectContaining( { name: 'rt-carousel/carousel-dots' } ),
+ undefined,
+ 'nav-group-1',
+ );
} );
} );
diff --git a/src/blocks/carousel/edit.tsx b/src/blocks/carousel/edit.tsx
index 08a2cd6..9232051 100644
--- a/src/blocks/carousel/edit.tsx
+++ b/src/blocks/carousel/edit.tsx
@@ -342,12 +342,14 @@ export default function Edit( {
if ( value ) {
setAttributes( {
useTabs: true,
+ transition: 'slide',
loop: false,
dragFree: false,
carouselAlign: 'start',
containScroll: 'trimSnaps',
slidesToScroll: '1',
autoplay: false,
+ autoScroll: false,
axis: 'x',
} );
const tabListExists = findBlockDeep( innerBlocks, 'rt-carousel/carousel-tab-list' );
@@ -359,6 +361,29 @@ export default function Edit( {
clientId,
);
}
+ // Find nav row containers (core/group containing navigation blocks) as well as any standalone nav blocks
+ const navGroupBlocks = innerBlocks.filter(
+ ( b ) =>
+ b.name === 'core/group' &&
+ ( findBlockDeep( b.innerBlocks ?? [], 'rt-carousel/carousel-controls' ) ||
+ findBlockDeep( b.innerBlocks ?? [], 'rt-carousel/carousel-counter' ) ||
+ findBlockDeep( b.innerBlocks ?? [], 'rt-carousel/carousel-dots' ) ),
+ );
+ const looseNavBlocks = [
+ ...findAllBlocksDeep( innerBlocks, 'rt-carousel/carousel-controls' ),
+ ...findAllBlocksDeep( innerBlocks, 'rt-carousel/carousel-counter' ),
+ ...findAllBlocksDeep( innerBlocks, 'rt-carousel/carousel-dots' ),
+ ];
+
+ const idsToRemove = Array.from(
+ new Set( [
+ ...navGroupBlocks.map( ( b ) => b.clientId ),
+ ...looseNavBlocks.map( ( b ) => b.clientId ),
+ ] ),
+ );
+ if ( idsToRemove.length > 0 ) {
+ removeBlocks( idsToRemove );
+ }
} else {
setAttributes( { useTabs: false } );
const tabListBlocks = findAllBlocksDeep( innerBlocks, 'rt-carousel/carousel-tab-list' );
@@ -366,6 +391,46 @@ export default function Edit( {
if ( tabListIds.length > 0 ) {
removeBlocks( tabListIds );
}
+
+ const controlsExist = findBlockDeep( innerBlocks, 'rt-carousel/carousel-controls' );
+ const counterExist = findBlockDeep( innerBlocks, 'rt-carousel/carousel-counter' );
+ const dotsExist = findBlockDeep( innerBlocks, 'rt-carousel/carousel-dots' );
+
+ if ( ! controlsExist && ! counterExist && ! dotsExist ) {
+ // No navigation blocks exist — insert full nav group row container
+ insertBlock( createNavGroup(), undefined, clientId );
+ } else if ( ! controlsExist || ! counterExist || ! dotsExist ) {
+ const navGroup = innerBlocks.find(
+ ( b ) =>
+ b.name === 'core/group' &&
+ ( findBlockDeep( b.innerBlocks ?? [], 'rt-carousel/carousel-controls' ) ||
+ findBlockDeep( b.innerBlocks ?? [], 'rt-carousel/carousel-counter' ) ||
+ findBlockDeep( b.innerBlocks ?? [], 'rt-carousel/carousel-dots' ) ),
+ );
+ const targetParentId = navGroup ? navGroup.clientId : clientId;
+
+ if ( ! controlsExist ) {
+ insertBlock(
+ createBlock( 'rt-carousel/carousel-controls', {} ),
+ undefined,
+ targetParentId,
+ );
+ }
+ if ( ! counterExist ) {
+ insertBlock(
+ createBlock( 'rt-carousel/carousel-counter', {} ),
+ undefined,
+ targetParentId,
+ );
+ }
+ if ( ! dotsExist ) {
+ insertBlock(
+ createBlock( 'rt-carousel/carousel-dots', {} ),
+ undefined,
+ targetParentId,
+ );
+ }
+ }
}
};
@@ -373,26 +438,6 @@ export default function Edit( {
<>
-
- setAttributes( { transition: value as CarouselAttributes[ 'transition' ] } )
- }
- help={
- autoScroll
- ? __( 'Auto Scroll does not support transitions.', 'rt-carousel' )
- : __(
- 'Choose how slides transition: sliding horizontally or cross-fading.',
- 'rt-carousel',
- )
- }
- />
{ ! useTabs && (
<>
+
+ setAttributes( { transition: value as CarouselAttributes[ 'transition' ] } )
+ }
+ help={
+ autoScroll
+ ? __( 'Auto Scroll does not support transitions.', 'rt-carousel' )
+ : __(
+ 'Choose how slides transition: sliding horizontally or cross-fading.',
+ 'rt-carousel',
+ )
+ }
+ />