omni-carousel
Version:
A lightweight carousel to enhance scrollable areas
148 lines (144 loc) • 4.06 kB
TypeScript
import { Emitter, Unsubscribe } from 'nanoevents';
type ScrollAlign = 'start' | 'center';
type ScrollSteps = 'one' | 'auto';
interface OmniAPI {
init: () => void;
setup: () => void;
destroy: () => void;
goTo: (index: number) => void;
next: () => void;
prev: () => void;
on: <K extends keyof OmniEvents>(event: K, callback: OmniEvents[K]) => Unsubscribe;
}
interface Options {
selectors?: Partial<Selectors>;
scrollSteps?: ScrollSteps;
scrollAlign?: ScrollAlign;
hasEqualWidths?: boolean;
hasCenterMode?: boolean;
indicatorNumbers?: boolean;
transitionHelpers?: boolean;
preloadAdjacentImages?: boolean;
}
interface Selectors {
track: string;
slide: string;
prevButton: string;
nextButton: string;
startButton: string;
endButton: string;
indicatorArea: string;
indicator: string;
}
/**
* Types for custom Omni events
*/
interface OmniEvents {
'omni:visibility:change': (data: {
state: State;
slide: HTMLElement;
fullIntersecting: boolean;
partIntersecting: boolean;
wasFullIntersecting: boolean;
wasPartIntersecting: boolean;
startBoundaryChanged: boolean;
endBoundaryChanged: boolean;
}) => void;
'omni:dimensions:change': (data: {
width: number;
scrollWidth: number;
}) => void;
'omni:nav:prev': () => void;
'omni:nav:next': () => void;
'omni:nav:index': (data: {
index: number;
}) => void;
'omni:init': () => void;
'omni:setup': () => void;
'omni:destroy': (data?: {
mode?: 'full' | 'partial';
}) => void;
}
interface OmniElements {
root: HTMLElement;
track: HTMLElement;
slides: HTMLElement[];
prevButton?: HTMLButtonElement;
nextButton?: HTMLButtonElement;
startButton?: HTMLButtonElement;
endButton?: HTMLButtonElement;
indicatorArea?: HTMLElement;
invisibleAnchor?: HTMLElement;
}
interface Config {
scrollAlign: ScrollAlign;
scrollSteps: ScrollSteps;
selectors: Selectors;
hasEqualWidths: boolean;
hasCenterMode: boolean;
indicatorNumbers: boolean;
transitionHelpers: boolean;
preloadAdjacentImages: boolean;
}
/**
* Dynamic state that changes during carousel operation
*/
interface State {
width: number;
scrollWidth: number;
containerLeft?: number;
itemSpacing?: number;
itemWidth?: number;
itemWidthMap: Map<number, number>;
indicatorSpacing?: number;
indicatorWidth?: number;
fullItems: HTMLElement[];
partItems: HTMLElement[];
startItemFullIntersecting: boolean;
endItemFullIntersecting: boolean;
hasIndicators: boolean;
indicatorOverflow: boolean;
centeredItemIndex?: number;
previousCenteredItemIndex?: number;
detectedBlinkEngine?: boolean;
hasOldInvisibleAnchors?: boolean;
addedTrackCSSPosition?: boolean;
centeredGroupItems: number[];
previousCenteredGroupItems: number[];
debouncedCenterIndicators?: ((context: Context) => void);
slideIndexMap: Map<HTMLElement, number>;
}
/**
* Combined context containing all carousel data
*/
interface Context {
config: Config;
elements: OmniElements & {
indicators: HTMLButtonElement[];
};
state: State;
utils: Utils;
eventEmitter: Emitter<OmniEvents>;
}
/**
* Utility functions for working with carousel state
*/
interface Utils {
getElementRect: (element: HTMLElement, property?: 'width' | 'left') => {
width: number;
left: number;
} | number;
getItemIndex: (slide: HTMLElement | undefined) => number;
getItemWidth: (index: number) => number;
getContainerLeft: () => number;
}
/**
* Adds basic carousel functionality to a scrollable area
*
* @param element - The carousel element
* @param options - Configuration options
*
* @returns A public API object with a few methods for controlling the carousel
*/
declare const createOmniCarousel: (root: HTMLElement, options?: Options) => OmniAPI;
export { createOmniCarousel };