123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- import { EVENT_END_INDEX_CHANGED, EVENT_REFRESH, EVENT_RESIZED, EVENT_UPDATED } from '../../constants/events';
- import { MOVING, SCROLLING } from '../../constants/states';
- import { LOOP, SLIDE } from '../../constants/types';
- import { AnyFunction, BaseComponent, ComponentConstructor } from '../../types';
- import { apply, approximatelyEqual, between, clamp, floor, isString, isUndefined, min } from '@splidejs/utils';
- /**
- * The interface for the Controller component.
- *
- * @since 3.0.0
- */
- export interface ControllerComponent extends BaseComponent {
- go( control: number | string, callback?: AnyFunction ): void;
- jump( control: number | string ): void;
- scroll( destination: number, duration?: number, snap?: boolean, callback?: AnyFunction ): void;
- getNext( destination?: boolean ): number;
- getPrev( destination?: boolean ): number;
- getEnd(): number;
- setIndex( index: number ): void;
- getIndex( prev?: boolean ): number;
- toIndex( page: number ): number;
- toPage( index: number ): number;
- toDest( position: number ): number;
- hasFocus(): boolean;
- isBusy(): boolean;
- /** @internal */
- getAdjacent( prev: boolean, destination?: boolean ): number;
- }
- /**
- * The component for controlling the slider.
- *
- * @since 3.0.0
- *
- * @param Splide - A Splide instance.
- * @param Components - A collection of components.
- * @param options - Options.
- * @param event - An EventInterface instance.
- *
- * @return A Controller component object.
- */
- export const Controller: ComponentConstructor<ControllerComponent> = ( Splide, Components, options, event ) => {
- const { on, emit } = event;
- const { Move, Scroll } = Components;
- const { getPosition, getLimit, toPosition } = Move;
- const { isEnough, getLength } = Components.Slides;
- const { omitEnd } = options;
- const isLoop = Splide.is( LOOP );
- const isSlide = Splide.is( SLIDE );
- const getNext = apply( getAdjacent, false );
- const getPrev = apply( getAdjacent, true );
- /**
- * The current index.
- */
- let currIndex = options.start || 0;
- /**
- * The latest end index.
- */
- let endIndex: number;
- /**
- * The previous index.
- */
- let prevIndex = currIndex;
- /**
- * The latest number of slides.
- */
- let slideCount: number;
- /**
- * The latest `perMove` value.
- */
- let perMove: number;
- /**
- * The latest `perMove` value.
- */
- let perPage: number;
- /**
- * Called when the component is mounted.
- */
- function mount(): void {
- init();
- on( [ EVENT_UPDATED, EVENT_REFRESH, EVENT_END_INDEX_CHANGED ], init );
- on( EVENT_RESIZED, onResized );
- }
- /**
- * Initializes some parameters.
- * Needs to check the number of slides since the current index may be out of the range after refresh.
- * The process order must be Elements -> Controller -> Move.
- */
- function init(): void {
- slideCount = getLength( true );
- perMove = options.perMove;
- perPage = options.perPage;
- endIndex = getEnd();
- const end = omitEnd ? endIndex : slideCount - 1;
- const index = clamp( currIndex, 0, end );
- prevIndex = index;
- if ( index !== currIndex ) {
- currIndex = index;
- Move.reposition();
- }
- }
- /**
- * Called when the viewport width is changed.
- * The end index can change if `autoWidth` or `fixedWidth` is enabled.
- */
- function onResized(): void {
- if ( endIndex !== getEnd() ) {
- emit( EVENT_END_INDEX_CHANGED );
- }
- }
- /**
- * Moves the slider by the control pattern.
- *
- * @see `Splide#go()`
- *
- * @param control - A control pattern.
- * @param callback - Optional. A callback function invoked after transition ends.
- */
- function go( control: number | string, callback?: AnyFunction ): void {
- if ( ! isBusy() ) {
- const dest = parse( control );
- const index = loop( dest );
- if ( canGo( dest, index ) ) {
- Scroll.cancel();
- setIndex( index );
- Move.move( dest, index, prevIndex, callback );
- }
- }
- }
- /**
- * Checks if the carousel can move or not.
- * - If target and current index are same, allows going only when the carousel is not moving.
- * Otherwise, synced carousels will provoke the infinite loop.
- * - If the carousel is looping (`dest !== index`),
- * the carousel can be shifted or has been already shifted.
- *
- * @todo dest
- *
- * @param dest - A dest index.
- * @param index - An actual index.
- *
- * @return `true` if the carousel can currently move, or otherwise `false`.
- */
- function canGo( dest: number, index: number ): boolean {
- const forward = dest > prevIndex;
- return index > -1
- && ( index !== currIndex || ! isMoving() )
- && ( dest === index || Move.exceededLimit( ! forward ) || Move.canShift( forward ) );
- }
- /**
- * Immediately jumps to the specified index.
- *
- * @param control - An index where to jump.
- */
- function jump( control: number | string ): void {
- const { set } = Components.Breakpoints;
- const { speed } = options;
- set( { speed: 0 } );
- go( control );
- set( { speed } );
- }
- /**
- * Scrolls the slider to the specified destination with updating indices.
- *
- * @param destination - The position to scroll the slider to.
- * @param duration - Optional. Specifies the scroll duration.
- * @param snap - Optional. Whether to snap the slider to the closest slide or not.
- * @param callback - Optional. A callback function invoked after scroll ends.
- */
- function scroll( destination: number, duration?: number, snap?: boolean, callback?: AnyFunction ): void {
- Scroll.scroll( destination, duration, snap, () => {
- const index = loop( Move.toIndex( getPosition() ) );
- setIndex( omitEnd ? min( index, endIndex ) : index );
- callback && callback();
- } );
- }
- /**
- * Parses the control and returns a slide index.
- *
- * @param control - A control pattern to parse.
- *
- * @return A `dest` index.
- */
- function parse( control: number | string ): number {
- let index = currIndex;
- if ( isString( control ) ) {
- const [ , indicator, number ] = control.match( /([+\-<>]\|?)(\d+)?/ ) || [];
- if ( indicator === '+' || indicator === '-' ) {
- index = computeDestIndex( currIndex + +`${ indicator }${ +number || 1 }`, currIndex );
- } else if ( indicator === '>' ) {
- index = number ? toIndex( +number ) : getNext( true );
- } else if ( indicator === '<' ) {
- index = getPrev( true );
- } else if ( indicator === '>|' ) {
- index = endIndex;
- }
- } else {
- index = isLoop ? control : clamp( control, 0, endIndex );
- }
- return index;
- }
- /**
- * Returns an adjacent destination index.
- *
- * @internal
- *
- * @param prev - Determines whether to return a previous or next index.
- * @param destination - Optional. Determines whether to get a destination index or a slide one.
- *
- * @return An adjacent index if available, or otherwise `-1`.
- */
- function getAdjacent( prev: boolean, destination?: boolean ): number {
- const number = perMove || ( hasFocus() ? 1 : perPage );
- const dest = computeDestIndex( currIndex + number * ( prev ? -1 : 1 ), currIndex, ! ( perMove || hasFocus() ) );
- if ( dest === -1 && isSlide ) {
- if ( ! approximatelyEqual( getPosition(), getLimit( ! prev ), 1 ) ) {
- return prev ? 0 : endIndex;
- }
- }
- return destination ? dest : loop( dest );
- }
- /**
- * Converts the desired destination index to the valid one.
- * - If the `move` option is `true`, finds the dest index whose position is different with the current one.
- * - This may return clone indices if the editor is the loop mode,
- * or `-1` if there is no slide to go.
- * - There are still slides where the carousel can go if borders are between `from` and `dest`.
- * - If `focus` is available, needs to calculate the dest index even if there are enough number of slides.
- *
- * @param dest - The desired destination index.
- * @param from - A base index.
- * @param snapPage - Optional. Whether to snap a page or not.
- *
- * @return A converted destination index, including clones.
- */
- function computeDestIndex( dest: number, from: number, snapPage?: boolean ): number {
- if ( isEnough() || hasFocus() ) {
- const index = computeMovableDestIndex( dest );
- if ( index !== dest ) {
- from = dest;
- dest = index;
- snapPage = false;
- }
- if ( dest < 0 || dest > endIndex ) {
- if ( ! perMove && ( between( 0, dest, from, true ) || between( endIndex, from, dest, true ) ) ) {
- dest = toIndex( toPage( dest ) );
- } else {
- if ( isLoop ) {
- dest = snapPage
- ? dest < 0 ? - ( slideCount % perPage || perPage ) : slideCount
- : dest;
- } else if ( options.rewind ) {
- dest = dest < 0 ? endIndex : 0;
- } else {
- dest = -1;
- }
- }
- } else {
- if ( snapPage && dest !== from ) {
- dest = toIndex( toPage( from ) + ( dest < from ? -1 : 1 ) );
- }
- }
- } else {
- dest = -1;
- }
- return dest;
- }
- /**
- * Finds the dest index whose position is different with the current one for `trimSpace: 'move'`.
- * This can be negative or greater than `length - 1`.
- *
- * @param dest - A dest index.
- *
- * @return A dest index.
- */
- function computeMovableDestIndex( dest: number ): number {
- if ( isSlide && options.trimSpace === 'move' && dest !== currIndex ) {
- const position = getPosition();
- while ( position === toPosition( dest ) && between( dest, 0, Splide.length - 1, ! options.rewind ) ) {
- dest < currIndex ? --dest : ++dest;
- }
- }
- return dest;
- }
- /**
- * Loops the provided index only in the loop mode.
- *
- * @param index - An index to loop.
- *
- * @return A looped index.
- */
- function loop( index: number ): number {
- return isLoop ? ( index + slideCount ) % slideCount || 0 : index;
- }
- /**
- * Returns the end index where the slider can go.
- * For example, if the slider has 10 slides and the `perPage` option is 3,
- * the slider can go to the slide 8 (the index is 7).
- * If the `omitEnd` option is available, computes the index from the slide position.
- *
- * @return An end index.
- */
- function getEnd(): number {
- let end = slideCount - ( hasFocus() || ( isLoop && perMove ) ? 1 : perPage );
- while ( omitEnd && end-- > 0 ) {
- if ( toPosition( slideCount - 1 ) !== toPosition( end ) ) {
- end++;
- break;
- }
- }
- return clamp( end, 0, slideCount - 1 );
- }
- /**
- * Converts the page index to the slide index.
- *
- * @param page - A page index to convert.
- *
- * @return A slide index.
- */
- function toIndex( page: number ): number {
- return clamp( hasFocus() ? page : perPage * page, 0, endIndex );
- }
- /**
- * Converts the slide index to the page index.
- *
- * @param index - An index to convert.
- *
- * @return A page index.
- */
- function toPage( index: number ): number {
- return hasFocus()
- ? min( index, endIndex )
- : floor( ( index >= endIndex ? slideCount - 1 : index ) / perPage );
- }
- /**
- * Converts the destination position to the dest index.
- *
- * @param destination - A position to convert.
- *
- * @return A dest index.
- */
- function toDest( destination: number ): number {
- const closest = Move.toIndex( destination );
- return isSlide ? clamp( closest, 0, endIndex ) : closest;
- }
- /**
- * Sets a new index and retains old one.
- *
- * @param index - A new index to set.
- */
- function setIndex( index: number ): void {
- if ( index !== currIndex ) {
- prevIndex = currIndex;
- currIndex = index;
- }
- }
- /**
- * Returns the current/previous index.
- *
- * @param prev - Optional. Whether to return previous index or not.
- */
- function getIndex( prev?: boolean ): number {
- return prev ? prevIndex : currIndex;
- }
- /**
- * Verifies if the focus option is available or not.
- *
- * @return `true` if the slider has the focus option.
- */
- function hasFocus(): boolean {
- return ! isUndefined( options.focus ) || options.isNavigation;
- }
- /**
- * Checks if the carousel is moving now or not.
- *
- * @return `true` if the carousel is moving or scrolling, or otherwise `false`.
- */
- function isMoving(): boolean {
- return Splide.state.is( [ MOVING, SCROLLING ] );
- }
- /**
- * Checks if the slider is moving/scrolling or not.
- *
- * @return `true` if the slider can move, or otherwise `false`.
- */
- function isBusy(): boolean {
- return isMoving() && !! options.waitForTransition;
- }
- return {
- mount,
- go,
- jump,
- scroll,
- getNext,
- getPrev,
- getAdjacent,
- getEnd,
- setIndex,
- getIndex,
- toIndex,
- toPage,
- toDest,
- hasFocus,
- isBusy,
- };
- };
|