npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@rei/carousel-engine

v1.1.0

Published

A flexible, API-driven carousel framework designed for modularity and adaptability. CarouselEngine provides an abstraction layer that allows consumers to define their own adapters for transforming data into a carousel-ready format.

Readme

Carousel Engine

Carousel Engine is a flexible, adapter-driven Vue 3 carousel framework that leverages the Adapter Pattern to separate data transformation from rendering. This is a forked and re-architected version of @rei/recommendations-slider, designed for greater modularity and reusability.

📦 Installation

$ npm i @rei/carousel-engine

🚀 Usage

Using CarouselEngine involves three key steps:

  1. Define an Adapter (Transforms raw model data into a carousel format)
  2. Provide a Model (Pass structured data to the carousel)
  3. Handle Events (Optional) (Define custom event handlers for interactions)

1️⃣ Define an Adapter

An adapter maps raw model data to a structured carousel configuration.

// adapter.ts
import type { LifestyleModel, LifestyleSlide } from '.';
import type {
  CarouselAdapter,
  CarouselConfig,
  Slide,
} from '@rei/carousel-engine';

import SlideComponent from './LifestyleSlide.vue';

export const adapter: CarouselAdapter<LifestyleSlide> = (modelData) => {
  /**
   * Extracts slides from the raw model data.
   */
  const {
    slides: slideItems = [],
    slideStyle,
    slidesVisible = 4,
  } = modelData as Partial<LifestyleModel>;

  /**
   * Determines the carousel ID.
   */
  const carouselId = 'lifestyle';

  /**
   * Transforms raw items into an array of slides for the carousel.
   *
   * @type {Slide<LifestyleSlide>[]}
   */
  const slides: Slide<LifestyleSlide>[] = Array.isArray(slideItems)
    ? slideItems.map((slide, index) => ({
        key: `lifestyle-slide-${index}`,
        props: {
          ...slide,
          slideStyle,
          lastSlide: index === slideItems.length - 1,
        },
      }))
    : [];

  /**
   * Constructs the carousel config with the resolved slides and metadata.
   *
   * @type {CarouselConfig<LifestyleSlide>}
   */
  const carouselConfig: CarouselConfig<LifestyleSlide> = {
    component: SlideComponent,
    slides,
    carouselId,
    description: 'Lifestyle carousel',
    slidesGap: parseInt(CdrSpaceThreeQuarterX, 10),
    slidesToShow: slidesVisible,
    focusSelector: ':first-child a',
  };

  return carouselConfig;
};

export default adapter;

2️⃣ Provide a Model & Render the Carousel

In a Vue component, pass the model and adapter to CarouselEngine. Your model is passed to your adapter, which transforms it into a structured carousel configuration.

<template>
  <CarouselEngine :model="model" :adapter="adapter" />
</template>

<script setup lang="ts">
import CarouselEngine from '@rei/carousel-engine';
import adapter from './adapter';

import lifestyleModelData from './mock.json';
import type { LifestyleModel } from './implementation/Lifestyle';

const model = lifestyleModelData as LifestyleModel;
</script>

3️⃣ Handle Events

Carousel Engine emits several named events:

  • arrowClick - Emitted when an arrow is clicked.
  • resize - Emitted when the carousel resize observer fires.
// handlers.ts
import type { CarouselArrowClickPayload } from '@rei/carousel-engine';
import type { LifestyleModel, LifestyleSlideClickPayload } from '.';

/**
 * Handles arrow click events in the carousel.
 * Determines scroll direction and formats analytics tracking data.
 *
 * @param {unknown} payload - The event payload containing navigation details.
 * @return {void}
 */
export function onArrowClick(payload: unknown): void {
  const { direction, event, model = {} } = payload as CarouselArrowClickPayload;
  const { slidesVisible, slideStyle } = model as Partial<LifestyleModel>;

  const scrollDirection =
    direction === 'right' ? 'forwardScroll' : 'backScroll';
  const scrollValue = `scroll-${direction}`;
  const analytics = {
    [scrollDirection]: scrollValue, // Scroll direction tracking key
    slidesVisible,
    slideStyle,
  };

  console.log('onArrowClick', { event, direction, analytics });
}

Attach the handler:

<template>
  <CarouselEngine
    :model="model"
    :adapter="adapter"
    @arrow-click="onArrowClick"
  />
</template>

<script setup lang="ts">
import CarouselEngine from '@rei/carousel-engine';

import { onSlideClick, onArrowClick } from './handlers';
</script>

Resizing

There is a default resizing strategy that you can enable for general use. Enable it by setting useDefaultResizeStrategy to true from your implementation's adapter. It is disabled by default.

// adapter.ts
...
/**
 * Constructs the carousel config with the resolved slides and metadata.
 *
 * @type {CarouselConfig<LifestyleSlide>}
 */
const carouselConfig: CarouselConfig<LifestyleSlide> = {
  component: SlideComponent,
  slides,
  carouselId,
  description: 'Lifestyle carousel',
  slidesGap: parseInt(CdrSpaceThreeQuarterX, 10),
  slidesToShow: slidesVisible,
  focusSelector: ':first-child a',
  useDefaultResizeStrategy: true, // <-- here
};

return carouselConfig;
...

If you need to customize the resize strategy, you can attach a handler to the resize event. The resize event is emitted when the carousel resize observer fires. It provides the slidesToShow and slidesToScroll references which can be used to update the carousel's internal state.

Define a handler:

/**
 * Handles window resize events and updates the carousel's
 * internal state for `slidesToShow` and `slidesToScroll` based
 * on the current window size.
 *
 * @param {unknown} payload - The event payload containing
 *   information about the carousel's config and model.
 * @return {void}
 */
export function onResize(payload: unknown): void {
  const {
    slidesToScroll,
    slidesToShow,
    model = {},
  } = payload as CarouselResizePayload;
  const { slidesVisible = 3 } = model as Partial<LifestyleModel>;

  const { clientWidth } = window.document.body;
  switch (true) {
    case clientWidth >= Number(CdrBreakpointLg):
      slidesToShow.value = slidesVisible;
      slidesToScroll.value = slidesVisible - 1;
      break;
    case clientWidth >= Number(CdrBreakpointMd):
      slidesToShow.value = 3;
      slidesToScroll.value = 2;
      break;
    default:
      slidesToShow.value = 2;
      slidesToScroll.value = 1;
  }
}

Attach the handler:

<template>
  <CarouselEngine
    :model="model"
    :adapter="adapter"
    @arrow-click="onArrowClick"
    @resize="onResize"
  />
</template>

<script setup lang="ts">
import CarouselEngine from '@rei/carousel-engine';

import { onSlideClick, onArrowClick, onResize } from './handlers';
</script>

Custom Events (Provide/Inject)

Carousel Engine supports custom events by using Provide/Inject. If you need to bubble an event to a parent component, CarouselEngine.vue provides the emitEvent function. In your components, you can Inject the emitEvent function and use it to emit custom events.

<template>
  <button class="slide__button" @click.once="onSlideClick"></button>
</template>
<script setup lang="ts">
import type { CarouselEventEmitter } from '@rei/carousel-engine';
import { CarouselEventKey } from '@rei/carousel-engine';

const emitEvent = inject(CarouselEventKey) as CarouselEventEmitter;

/**
 * Handles the click event on a slide, emitting a 'slideClick' event with the event details and the slide item.
 *
 * @param {Event} event - The click event that triggered this function.
 * @return {void}
 */
const onSlideClick = (event: Event) => {
  emitEvent?.('slideClick', {
    event,
    item: props,
  } as LifestyleSlideClickPayload);
};
</script>

Now, define a handler for the slideClick event:

import type { CarouselArrowClickPayload } from '@rei/carousel-engine';
import type { LifestyleModel, LifestyleSlideClickPayload } from '.';

/**
 * Handles slide click events in the carousel and logs analytics data.
 *
 * @param {unknown} payload - The event payload containing details about the clicked slide.
 * @return {void}
 */
export function onSlideClick(payload: unknown): void {
  const { event, item } = payload as LifestyleSlideClickPayload;

  const analytics = {
    target: item.cta.target,
    text: item.cta.text,
  };

  console.log('onSlideClick', { event, item, analytics });
}

Attach the handler:

<template>
  <CarouselEngine
    :model="lifestyleModelData"
    :adapter="LifestyleAdapter"
    @slide-click="onSlideClick"
    @arrow-click="onArrowClick"
    @resize="onResize"
  />
</template>

<script setup lang="ts">
import CarouselEngine from 'carousel-engine';
import LifestyleAdapter from './adapter';
import lifestyleModelData from './mock.json';
import { onSlideClick, onArrowClick, onResize } from './handlers';
</script>

🏗 Architectural Overview

Carousel Engine is built with a layered architecture that separates concerns into four key layers:

1️⃣ Engine Layer (Core Carousel Logic)

  • The Carousel Engine component acts as the orchestrator, managing carousel state, event handling, and rendering.
  • Handles carousel navigation, focus management, and slide transitions, while remaining agnostic to specific implementations.

2️⃣ Adapter Layer (Model Transformation & Customization)

  • Adapters define how raw model data is transformed into a standardized carousel structure.
  • Implements the Adapter Pattern, allowing different data formats to be consumed without modifying the core engine.

3️⃣ Handler Layer (Event Processing & Consumer Interactions)

  • Handlers allow consumers to define custom event logic for slides and navigation without modifying the core engine.
  • Arbitrary events can be emitted with Provide/Inject, enabling per-instance overrides.
  • Supports tracking analytics, logging, or triggering additional UI behaviors in a fully decoupled way.

4️⃣ Model-Driven Rendering (Data as the Source of Truth)

  • The entire carousel is driven by a structured model, making it highly flexible.
  • Consumers pass a model object and an adapter, and CarouselEngine dynamically constructs the carousel.
  • Enables easy A/B testing, content variations, and dynamic configurations without modifying components.

Examples

Check out the examples folder for more detailed examples. You can also run the local development environment to see how it works in SFCs:

git clone [email protected]:rei/carousel-engine.git
cd carousel-engine
npm ci
npm run dev