Skip to content

MotionRailExtension

Extension plugin interface.

Type Definition

ts
type MotionRailExtension = {
  name: string;
  onInit?: (motionRail: MotionRail, state: MotionRailState) => void;
  onUpdate?: (motionRail: MotionRail, state: MotionRailState) => void;
  onDestroy?: (motionRail: MotionRail, state: MotionRailState) => void;
}

Properties

name

Unique identifier for the extension.

  • Type: string
  • Required: Yes
ts
const customExtension: MotionRailExtension = {
  name: 'custom-logger'
};

onInit

Called once when the carousel is initialized.

  • Type: (motionRail: MotionRail, state: MotionRailState) => void
  • Optional: Yes

Parameters:

ts
const customExtension: MotionRailExtension = {
  name: 'init-tracker',
  onInit: (motionRail, state) => {
    console.log('Carousel initialized with', state.totalItems, 'items');
  }
};

onUpdate

Called whenever the carousel state updates (scroll, resize, content changes).

  • Type: (motionRail: MotionRail, state: MotionRailState) => void
  • Optional: Yes

Parameters:

ts
const customExtension: MotionRailExtension = {
  name: 'scroll-tracker',
  onUpdate: (motionRail, state) => {
    console.log('Visible items:', state.visibleItemIndexes);
  }
};

onDestroy

Called when the carousel is destroyed for cleanup.

  • Type: (motionRail: MotionRail, state: MotionRailState) => void
  • Optional: Yes

Parameters:

ts
const customExtension: MotionRailExtension = {
  name: 'cleanup-tracker',
  onDestroy: (motionRail, state) => {
    console.log('Carousel destroyed');
  }
};

Lifecycle Order

Extensions follow this lifecycle:

  1. onInit - Carousel initialization (called once)
  2. onUpdate - State changes (called multiple times)
  3. onDestroy - Carousel cleanup (called once)
ts
const lifecycleExtension: MotionRailExtension = {
  name: 'lifecycle-demo',
  onInit: (motionRail, state) => {
    console.log('1. Init');
  },
  onUpdate: (motionRail, state) => {
    console.log('2. Update (called on scroll, resize, etc.)');
  },
  onDestroy: (motionRail, state) => {
    console.log('3. Destroy');
  }
};

Usage

Adding Extensions

ts
import { MotionRail } from 'motionrail';

const carousel = new MotionRail(element, {
  extensions: [
    customExtension1,
    customExtension2
  ]
});

Built-in Extensions

ts
import { Arrows } from 'motionrail/extensions/arrows';
import { Dots } from 'motionrail/extensions/dots';
import { Thumbnails } from 'motionrail/extensions/thumbnails';
import { Logger } from 'motionrail/extensions/logger';

const carousel = new MotionRail(element, {
  extensions: [
    Arrows(),
    Dots({ dotSize: 48 }),
    Thumbnails(),
    Logger()
  ]
});

See Extensions Overview for built-in extensions.

Examples

Simple Logger

ts
const logger: MotionRailExtension = {
  name: 'simple-logger',
  onInit: (motionRail, state) => {
    console.log('Initialized');
  },
  onUpdate: (motionRail, state) => {
    console.log('State updated:', state);
  },
  onDestroy: (motionRail, state) => {
    console.log('Destroyed');
  }
};

Counter Display

ts
const counter: MotionRailExtension = {
  name: 'counter',
  onUpdate: (motionRail, state) => {
    const counterEl = document.querySelector('.counter');
    if (counterEl) {
      const current = state.visibleItemIndexes[0] + 1;
      counterEl.textContent = `${current} / ${state.totalItems}`;
    }
  }
};

Custom Controls

ts
const customControls: MotionRailExtension = {
  name: 'custom-controls',
  onInit: (motionRail, state) => {
    const prevBtn = document.querySelector('.my-prev');
    const nextBtn = document.querySelector('.my-next');
    
    prevBtn?.addEventListener('click', () => motionRail.prev());
    nextBtn?.addEventListener('click', () => motionRail.next());
  },
  onUpdate: (motionRail, state) => {
    const prevBtn = document.querySelector('.my-prev') as HTMLButtonElement;
    const nextBtn = document.querySelector('.my-next') as HTMLButtonElement;
    
    if (prevBtn) prevBtn.disabled = state.isFirstItemVisible;
    if (nextBtn) nextBtn.disabled = state.isLastItemVisible;
  }
};

Progress Bar

ts
const progressBar: MotionRailExtension = {
  name: 'progress-bar',
  onInit: (motionRail, state) => {
    const container = motionRail.getOptions();
    const progressEl = document.createElement('div');
    progressEl.className = 'progress-bar';
    progressEl.style.cssText = 'height: 4px; background: #ccc; position: relative;';
    
    const bar = document.createElement('div');
    bar.className = 'progress-bar-fill';
    bar.style.cssText = 'height: 100%; background: #007bff; width: 0; transition: width 0.3s;';
    
    progressEl.appendChild(bar);
    // Insert progress bar before carousel
  },
  onUpdate: (motionRail, state) => {
    const bar = document.querySelector('.progress-bar-fill') as HTMLElement;
    if (bar && state.totalItems > 0) {
      const progress = ((state.visibleItemIndexes[0] + 1) / state.totalItems) * 100;
      bar.style.width = `${progress}%`;
    }
  }
};

Keyboard Navigation

ts
const keyboardNav: MotionRailExtension = {
  name: 'keyboard-navigation',
  onInit: (motionRail, state) => {
    const handleKeydown = (e: KeyboardEvent) => {
      if (e.key === 'ArrowLeft') motionRail.prev();
      if (e.key === 'ArrowRight') motionRail.next();
    };
    
    document.addEventListener('keydown', handleKeydown);
    
    // Store for cleanup
    (motionRail as any)._keyboardHandler = handleKeydown;
  },
  onDestroy: (motionRail, state) => {
    const handler = (motionRail as any)._keyboardHandler;
    if (handler) {
      document.removeEventListener('keydown', handler);
    }
  }
};

External State Sync

ts
const stateSync: MotionRailExtension = {
  name: 'state-sync',
  onUpdate: (motionRail, state) => {
    // Sync to external state management (Redux, Vuex, etc.)
    window.dispatchEvent(new CustomEvent('carousel-state-change', {
      detail: state
    }));
  }
};

TypeScript Support

Full TypeScript support with type checking:

ts
import { MotionRail } from 'motionrail';
import type { MotionRailExtension, MotionRailState } from 'motionrail';

const typedExtension: MotionRailExtension = {
  name: 'typed-extension',
  onInit: (motionRail: MotionRail, state: MotionRailState) => {
    // Full type checking and autocomplete
    console.log(state.totalItems);
    motionRail.play();
  }
};

Next Steps