This block uses the WordPress Interactivity API to manage state and logic. You can consume this state in your own custom blocks to build advanced features like progress bars, slide counters, or synchronized sliders.
rt-carousel/carousel
rtCarousel exposes JavaScript filters immediately before calling EmblaCarousel( viewport, options, plugins ), allowing runtime customization without changing saved block markup.
import { addFilter } from '@wordpress/hooks';
import AutoHeight from 'embla-carousel-auto-height';
addFilter(
'rtcamp.rtCarousel.emblaOptions',
'my-plugin/custom-options',
( options ) => ( { ...options, duration: 40 } )
);
addFilter(
'rtcamp.rtCarousel.emblaPlugins',
'my-plugin/auto-height',
( plugins ) => [ ...plugins, AutoHeight() ]
);Both filter callbacks receive the filtered value as their first argument and the filter context object as their second argument. rtcamp.rtCarousel.emblaPlugins also receives the filtered options on the options property of this object.
rtCarousel also exposes an action after Embla has initialized so integrations can call Embla methods or subscribe to Embla events:
import { addAction } from '@wordpress/hooks';
addAction(
'rtcamp.rtCarousel.emblaInit',
'my-plugin/custom-events',
( embla, { root } ) => {
embla.on( 'select', () => {
root.dataset.selectedSlide = embla.selectedScrollSnap().toString();
} );
}
);| Property | Type | Description |
|---|---|---|
context |
CarouselContext |
Interactivity API context for the carousel. |
root |
HTMLElement |
Root .rt-carousel element. |
viewport |
HTMLElement |
Embla viewport element. |
dynamicListContainer |
HTMLElement | null |
Query Loop or Terms Query template container when present. |
options |
EmblaOptionsType |
Passed to rtcamp.rtCarousel.emblaPlugins and rtcamp.rtCarousel.emblaInit; contains filtered options. |
plugins |
EmblaPluginType[] |
Only passed to rtcamp.rtCarousel.emblaInit; contains filtered plugins. |
The following properties are exposed in the Interactivity API context:
| Property | Type | Description |
|---|---|---|
isPlaying |
boolean |
true if Autoplay is currently running. |
timerIterationId |
number |
Increments every time the Autoplay timer resets (slide change). Bind to key to restart animations. |
autoplay |
boolean or { delay, ... } |
Autoplay config or false if disabled. |
canScrollPrev |
boolean |
true if there are previous slides (or looping). |
canScrollNext |
boolean |
true if there are next slides (or looping). |
selectedIndex |
number |
The zero-based index of the current slide. |
scrollSnaps |
{ index: number }[] |
List of snap points (used by Dots block). |
Create a progress bar that animates while the carousel is auto-playing.
save.tsx
<div
data-wp-interactive="rt-carousel/carousel"
data-wp-bind--key="context.timerIterationId"
data-wp-bind--style="state.barDuration"
data-wp-class--is-playing="context.isPlaying"
className="core-progress-bar"
/>view.ts
store('rt-carousel/carousel', {
state: {
get barDuration() {
const { autoplay } = getContext();
return `--duration: ${autoplay?.delay || 4000}ms;`;
}
}
});CSS
.core-progress-bar.is-playing {
animation: grow var(--duration) linear forwards;
}
@keyframes grow { from { width: 0%; } to { width: 100%; } }Highlight slide content when active using the callbacks.isSlideActive helper.
<div
data-wp-interactive="rt-carousel/carousel"
data-wp-class--is-active="callbacks.isSlideActive"
className="my-card"
>
<h2 data-wp-class--highlight="callbacks.isSlideActive">Card Title</h2>
</div>Gutenberg's InnerBlocks can isolate React Contexts, causing state sync issues between parent and deeply nested children in the editor. To guarantee reliable interactivity:
- Attach: The Viewport component attaches the vanilla Embla instance directly to its DOM node using a Symbol key:
element[Symbol.for("rt-carousel.carousel")] = embla. - Find: Child components (Controls/Dots) attempt to find the API via Context first. If missing, they traverse the DOM up to the common wrapper (
.rt-carousel) and then search for the sibling.emblaviewport. - Bind: A retry mechanism (
setTimeout+useEffect) ensures the Viewport has finished initializing before binding listeners.
The Carousel Dots block demonstrates a pattern for iterating over data with the Interactivity API:
- Data Source:
context.scrollSnapsis populated inview.tsby mapping Embla's snap list to objects:[{ index: 0 }, { index: 1 }, ...]. - The Loop:
The
<template data-wp-each--snap="context.scrollSnaps"> <button data-wp-class--is-active="callbacks.isDotActive" ... /> </template>
--snapsuffix names the iterator variable. - Why Objects? The Interactivity API's loop directive does not expose a separate
indexvariable. To check if a dot is active (selectedIndex === currentDotIndex), we include the index explicitly inside the data object (context.snap.index).