Procházet zdrojové kódy

Refactoring the scroll component.

NaotoshiFujita před 3 roky
rodič
revize
4a02052bf5

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
dist/js/splide-renderer.min.js


+ 87 - 74
dist/js/splide.js

@@ -49,13 +49,15 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
   var MOUNTED = 2;
   var IDLE = 3;
   var MOVING = 4;
-  var DRAGGING = 5;
-  var DESTROYED = 6;
+  var SCROLLING = 5;
+  var DRAGGING = 6;
+  var DESTROYED = 7;
   var STATES = {
     CREATED: CREATED,
     MOUNTED: MOUNTED,
     IDLE: IDLE,
     MOVING: MOVING,
+    SCROLLING: SCROLLING,
     DRAGGING: DRAGGING,
     DESTROYED: DESTROYED
   };
@@ -1357,7 +1359,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     }
 
     function reposition() {
-      if (!isBusy()) {
+      if (!Components2.Controller.isBusy()) {
         Components2.Scroll.cancel();
         jump(Splide2.index);
         emit(EVENT_REPOSITIONED);
@@ -1365,28 +1367,26 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     }
 
     function move(dest, index, prev, callback) {
-      if (!isBusy()) {
-        var set = Splide2.state.set;
-        var position = getPosition();
+      var set = Splide2.state.set;
+      var position = getPosition();
 
-        if (dest !== index) {
-          Transition.cancel();
-          translate(shift(position, dest > index), true);
-        }
+      if (dest !== index) {
+        Transition.cancel();
+        translate(shift(position, dest > index), true);
+      }
 
-        set(MOVING);
-        emit(EVENT_MOVE, index, prev, dest);
-        Transition.start(index, function () {
-          set(IDLE);
-          emit(EVENT_MOVED, index, prev, dest);
+      set(MOVING);
+      emit(EVENT_MOVE, index, prev, dest);
+      Transition.start(index, function () {
+        set(IDLE);
+        emit(EVENT_MOVED, index, prev, dest);
 
-          if (options.trimSpace === "move" && dest !== prev && position === getPosition()) {
-            Components2.Controller.go(dest > prev ? ">" : "<", false, callback);
-          } else {
-            callback && callback();
-          }
-        });
-      }
+        if (options.trimSpace === "move" && dest !== prev && position === getPosition()) {
+          Components2.Controller.go(dest > prev ? ">" : "<", false, callback);
+        } else {
+          callback && callback();
+        }
+      });
     }
 
     function jump(index) {
@@ -1474,10 +1474,6 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       return toPosition(max ? Components2.Controller.getEnd() : 0, !!options.trimSpace);
     }
 
-    function isBusy() {
-      return Splide2.state.is(MOVING) && options.waitForTransition;
-    }
-
     function exceededLimit(max, position) {
       position = isUndefined(position) ? getPosition() : position;
       var exceededMin = max !== true && orient(position) < orient(getLimit(false));
@@ -1496,9 +1492,9 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       toPosition: toPosition,
       getPosition: getPosition,
       getLimit: getLimit,
-      isBusy: isBusy,
       exceededLimit: exceededLimit,
-      reposition: reposition
+      reposition: reposition,
+      loop: loop
     };
   }
 
@@ -1508,7 +1504,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
 
     var Move = Components2.Move;
     var getPosition = Move.getPosition,
-        getLimit = Move.getLimit;
+        getLimit = Move.getLimit,
+        toPosition = Move.toPosition;
     var _Components2$Slides = Components2.Slides,
         isEnough = _Components2$Slides.isEnough,
         getLength = _Components2$Slides.getLength;
@@ -1540,28 +1537,33 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     }
 
     function go(control, allowSameIndex, callback) {
-      var dest = parse(control);
+      if (!isBusy()) {
+        var dest = parse(control);
 
-      if (options.useScroll) {
-        scroll(dest, true, true, options.speed, callback);
-      } else {
-        var index = loop(dest);
+        if (options.useScroll) {
+          scrollTo(dest, options.speed, callback);
+        } else {
+          var index = loop(dest);
 
-        if (index > -1 && !Move.isBusy() && (allowSameIndex || index !== currIndex)) {
-          setIndex(index);
-          Move.move(dest, index, prevIndex, callback);
+          if (index > -1 && (allowSameIndex || index !== currIndex)) {
+            setIndex(index);
+            Move.move(dest, index, prevIndex, callback);
+          }
         }
       }
     }
 
-    function scroll(destination, useIndex, snap, duration, callback) {
-      var dest = useIndex ? destination : toDest(destination);
-      Components2.Scroll.scroll(useIndex || snap ? Move.toPosition(dest, true) : destination, duration, function () {
+    function scroll(destination, duration, snap, callback) {
+      Components2.Scroll.scroll(destination, duration, snap, function () {
         setIndex(Move.toIndex(Move.getPosition()));
         callback && callback();
       });
     }
 
+    function scrollTo(index, duration, callback) {
+      scroll(toPosition(index), duration, false, callback);
+    }
+
     function parse(control) {
       var index = currIndex;
 
@@ -1676,10 +1678,15 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       return !isUndefined(options.focus) || options.isNavigation;
     }
 
+    function isBusy() {
+      return Splide2.state.is([MOVING, SCROLLING]) && options.waitForTransition;
+    }
+
     return {
       mount: mount,
       go: go,
       scroll: scroll,
+      scrollTo: scrollTo,
       getNext: getNext,
       getPrev: getPrev,
       getAdjacent: getAdjacent,
@@ -1689,7 +1696,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       toIndex: toIndex,
       toPage: toPage,
       toDest: toDest,
-      hasFocus: hasFocus
+      hasFocus: hasFocus,
+      isBusy: isBusy
     };
   }
 
@@ -1938,60 +1946,66 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
         on = _EventInterface11.on,
         emit = _EventInterface11.emit;
 
+    var set = Splide2.state.set;
     var Move = Components2.Move;
     var getPosition = Move.getPosition,
         getLimit = Move.getLimit,
-        exceededLimit = Move.exceededLimit;
+        exceededLimit = Move.exceededLimit,
+        translate = Move.translate;
     var interval;
-    var scrollCallback;
+    var callback;
+    var friction = 1;
 
     function mount() {
       on(EVENT_MOVE, clear);
       on([EVENT_UPDATED, EVENT_REFRESH], cancel);
     }
 
-    function scroll(destination, duration, callback, suppressConstraint) {
-      var start = getPosition();
-      var friction = 1;
-      duration = duration || computeDuration(abs(destination - start));
-      scrollCallback = callback;
+    function scroll(destination, duration, snap, onScrolled, noConstrain) {
+      var from = getPosition();
       clear();
-      interval = RequestInterval(duration, onScrolled, function (rate) {
-        var position = getPosition();
-        var target = start + (destination - start) * easing(rate);
-        var diff = (target - getPosition()) * friction;
-        Move.translate(position + diff);
 
-        if (Splide2.is(SLIDE) && !suppressConstraint && exceededLimit()) {
-          friction *= FRICTION_FACTOR;
+      if (snap) {
+        var size = Components2.Layout.sliderSize();
+        var offset = sign(destination) * size * floor(abs(destination) / size) || 0;
+        destination = Move.toPosition(Components2.Controller.toDest(destination % size)) + offset;
+      }
 
-          if (abs(diff) < BOUNCE_DIFF_THRESHOLD) {
-            bounce(exceededLimit(false));
-          }
-        }
-      }, 1);
+      friction = 1;
+      duration = duration || max(abs(destination - from) / BASE_VELOCITY, MIN_DURATION);
+      callback = onScrolled;
+      interval = RequestInterval(duration, onEnd, apply(update, from, destination, noConstrain), 1);
+      set(SCROLLING);
       emit(EVENT_SCROLL);
       interval.start();
     }
 
-    function bounce(backwards) {
-      scroll(getLimit(!backwards), BOUNCE_DURATION, null, true);
-    }
-
-    function onScrolled() {
+    function onEnd() {
       var position = getPosition();
       var index = Move.toIndex(position);
 
       if (!between(index, 0, Splide2.length - 1)) {
-        Move.translate(Move.shift(position, index > 0), true);
+        translate(Move.shift(position, index > 0), true);
       }
 
-      scrollCallback && scrollCallback();
+      set(IDLE);
+      callback && callback();
       emit(EVENT_SCROLLED);
     }
 
-    function computeDuration(distance) {
-      return max(distance / BASE_VELOCITY, MIN_DURATION);
+    function update(from, to, noConstrain, rate) {
+      var position = getPosition();
+      var target = from + (to - from) * easing(rate);
+      var diff = (target - position) * friction;
+      translate(position + diff);
+
+      if (Splide2.is(SLIDE) && !noConstrain && exceededLimit()) {
+        friction *= FRICTION_FACTOR;
+
+        if (abs(diff) < BOUNCE_DIFF_THRESHOLD) {
+          scroll(getLimit(exceededLimit(true)), BOUNCE_DURATION, false, void 0, true);
+        }
+      }
     }
 
     function clear() {
@@ -2003,7 +2017,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     function cancel() {
       if (interval && !interval.isPaused()) {
         clear();
-        onScrolled();
+        onEnd();
       }
     }
 
@@ -2083,9 +2097,9 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
         var isDraggable = !noDrag || !matches(e.target, noDrag);
 
         if (isDraggable && (isTouch || !e.button)) {
-          if (!Move.isBusy()) {
+          if (!Controller.isBusy()) {
             target = isTouch ? track : window;
-            dragging = state.is(MOVING);
+            dragging = state.is([MOVING, SCROLLING]);
             prevBaseEvent = null;
             bind(target, POINTER_MOVE_EVENTS, onPointerMove, SCROLL_LISTENER_OPTIONS);
             bind(target, POINTER_UP_EVENTS, onPointerUp, SCROLL_LISTENER_OPTIONS);
@@ -2159,7 +2173,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       var rewind = options.rewind && options.rewindByDrag;
 
       if (isFree) {
-        Controller.scroll(destination);
+        Controller.scrollTo(destination);
       } else if (Splide2.is(FADE)) {
         Controller.go(orient(sign(velocity)) < 0 ? rewind ? "<" : "-" : rewind ? ">" : "+");
       } else if (Splide2.is(SLIDE) && exceeded && rewind) {
@@ -2775,7 +2789,6 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     pauseOnHover: true,
     pauseOnFocus: true,
     resetProgress: true,
-    keyboard: true,
     easing: "cubic-bezier(0.25, 1, 0.5, 1)",
     drag: true,
     direction: "ltr",

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
dist/js/splide.min.js


binární
dist/js/splide.min.js.gz


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 0 - 0
dist/js/splide.min.js.map


+ 43 - 23
src/js/components/Controller/Controller.ts

@@ -1,4 +1,5 @@
 import { EVENT_REFRESH, EVENT_UPDATED } from '../../constants/events';
+import { MOVING, SCROLLING } from '../../constants/states';
 import { LOOP, SLIDE } from '../../constants/types';
 import { EventInterface } from '../../constructors';
 import { Splide } from '../../core/Splide/Splide';
@@ -13,7 +14,8 @@ import { apply, approximatelyEqual, between, clamp, floor, isString, isUndefined
  */
 export interface ControllerComponent extends BaseComponent {
   go( control: number | string, allowSameIndex?: boolean, callback?: AnyFunction ): void;
-  scroll( destination: number, useIndex?: boolean, snap?: boolean, duration?: number, callback?: AnyFunction ): void;
+  scrollTo( index: number, duration?: number, callback?: AnyFunction ): void;
+  scroll( destination: number, duration?: number, snap?: boolean, callback?: AnyFunction ): void;
   getNext( destination?: boolean ): number;
   getPrev( destination?: boolean ): number;
   getAdjacent( prev: boolean, destination?: boolean ): number;
@@ -24,6 +26,7 @@ export interface ControllerComponent extends BaseComponent {
   toPage( index: number ): number;
   toDest( position: number ): number;
   hasFocus(): boolean;
+  isBusy(): boolean;
 }
 
 /**
@@ -40,7 +43,7 @@ export interface ControllerComponent extends BaseComponent {
 export function Controller( Splide: Splide, Components: Components, options: Options ): ControllerComponent {
   const { on } = EventInterface( Splide );
   const { Move } = Components;
-  const { getPosition, getLimit } = Move;
+  const { getPosition, getLimit, toPosition } = Move;
   const { isEnough, getLength } = Components.Slides;
   const isLoop  = Splide.is( LOOP );
   const isSlide = Splide.is( SLIDE );
@@ -108,16 +111,18 @@ export function Controller( Splide: Splide, Components: Components, options: Opt
    * @param callback       - Optional. A callback function invoked after transition ends.
    */
   function go( control: number | string, allowSameIndex?: boolean, callback?: AnyFunction ): void {
-    const dest = parse( control );
+    if ( ! isBusy() ) {
+      const dest = parse( control );
 
-    if ( options.useScroll ) {
-      scroll( dest, true, true, options.speed, callback );
-    } else {
-      const index = loop( dest );
+      if ( options.useScroll ) {
+        scrollTo( dest, options.speed, callback );
+      } else {
+        const index = loop( dest );
 
-      if ( index > -1 && ! Move.isBusy() && ( allowSameIndex || index !== currIndex ) ) {
-        setIndex( index );
-        Move.move( dest, index, prevIndex, callback );
+        if ( index > -1 && ( allowSameIndex || index !== currIndex ) ) {
+          setIndex( index );
+          Move.move( dest, index, prevIndex, callback );
+        }
       }
     }
   }
@@ -125,27 +130,29 @@ export function Controller( Splide: Splide, Components: Components, options: Opt
   /**
    * Scrolls the slider to the specified destination with updating indices.
    *
-   * @param destination - A position or an index to scroll to.
-   * @param useIndex    - Optional. Whether to use an index as a destination or not.
-   * @param snap        - Optional. Whether to snap the closest slide or not.
+   * @param destination - An index 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,
-    useIndex?: boolean,
-    snap?: boolean,
-    duration?: number,
-    callback?: AnyFunction
-  ): void {
-    const dest = useIndex ? destination : toDest( destination );
-
-    Components.Scroll.scroll( useIndex || snap ? Move.toPosition( dest, true ) : destination, duration, () => {
+  function scroll( destination: number, duration?: number, snap?: boolean, callback?: AnyFunction ): void {
+    Components.Scroll.scroll( destination, duration, snap, () => {
       setIndex( Move.toIndex( Move.getPosition() ) );
       callback && callback();
     } );
   }
 
+  /**
+   * Scrolls the slider to the specified index with updating indices.
+   *
+   * @param index    - An index to scroll the slider to.
+   * @param duration - Optional. Specifies the scroll duration.
+   * @param callback - Optional. A callback function invoked after scroll ends.
+   */
+  function scrollTo( index: number, duration?: number, callback?: AnyFunction ): void {
+    scroll( toPosition( index ), duration, false, callback );
+  }
+
   /**
    * Parses the control and returns a slide index.
    *
@@ -299,6 +306,8 @@ export function Controller( Splide: Splide, Components: Components, options: Opt
   /**
    * Converts the destination position to the dest index.
    *
+   * @todo
+   *
    * @param destination - A position to convert.
    *
    * @return A dest index.
@@ -338,10 +347,20 @@ export function Controller( Splide: Splide, Components: Components, options: Opt
     return ! isUndefined( options.focus ) || options.isNavigation;
   }
 
+  /**
+   * Checks if the slider is moving/scrolling or not.
+   *
+   * @return `true` if the slider can move, or otherwise `false`.
+   */
+  function isBusy(): boolean {
+    return Splide.state.is( [ MOVING, SCROLLING ] ) && options.waitForTransition;
+  }
+
   return {
     mount,
     go,
     scroll,
+    scrollTo,
     getNext,
     getPrev,
     getAdjacent,
@@ -352,5 +371,6 @@ export function Controller( Splide: Splide, Components: Components, options: Opt
     toPage,
     toDest,
     hasFocus,
+    isBusy,
   };
 }

+ 4 - 4
src/js/components/Drag/Drag.ts

@@ -1,6 +1,6 @@
 import { EVENT_DRAG, EVENT_DRAGGED, EVENT_DRAGGING, EVENT_MOUNTED, EVENT_UPDATED } from '../../constants/events';
 import { SCROLL_LISTENER_OPTIONS } from '../../constants/listener-options';
-import { DRAGGING, IDLE, MOVING } from '../../constants/states';
+import { DRAGGING, IDLE, MOVING, SCROLLING } from '../../constants/states';
 import { FADE, LOOP, SLIDE } from '../../constants/types';
 import { EventInterface } from '../../constructors';
 import { Splide } from '../../core/Splide/Splide';
@@ -122,9 +122,9 @@ export function Drag( Splide: Splide, Components: Components, options: Options )
       const isDraggable = ! noDrag || ! matches( e.target, noDrag );
 
       if ( isDraggable && ( isTouch || ! e.button ) ) {
-        if ( ! Move.isBusy() ) {
+        if ( ! Controller.isBusy() ) {
           target        = isTouch ? track : window;
-          dragging      = state.is( MOVING );
+          dragging      = state.is( [ MOVING, SCROLLING ] );
           prevBaseEvent = null;
 
           bind( target, POINTER_MOVE_EVENTS, onPointerMove, SCROLL_LISTENER_OPTIONS );
@@ -229,7 +229,7 @@ export function Drag( Splide: Splide, Components: Components, options: Options )
     const rewind      = options.rewind && options.rewindByDrag;
 
     if ( isFree ) {
-      Controller.scroll( destination );
+      Controller.scrollTo( destination );
     } else if ( Splide.is( FADE ) ) {
       Controller.go( orient( sign( velocity ) ) < 0 ? ( rewind ? '<' : '-' ) : ( rewind ? '>' : '+' ) );
     } else if ( Splide.is( SLIDE ) && exceeded && rewind ) {

+ 22 - 31
src/js/components/Move/Move.ts

@@ -31,11 +31,11 @@ export interface MoveComponent extends BaseComponent {
   toPosition( index: number, trimming?: boolean ): number;
   getPosition(): number;
   getLimit( max: boolean ): number;
-  isBusy(): boolean;
   exceededLimit( max?: boolean | undefined, position?: number ): boolean;
 
   /** @internal */
   reposition(): void;
+  loop( position: number ): number;
 }
 
 /**
@@ -75,7 +75,7 @@ export function Move( Splide: Splide, Components: Components, options: Options )
    * - Slide components listening to the internal repositioned event to update their visibility.
    */
   function reposition(): void {
-    if ( ! isBusy() ) {
+    if ( ! Components.Controller.isBusy() ) {
       Components.Scroll.cancel();
       jump( Splide.index );
       emit( EVENT_REPOSITIONED );
@@ -91,29 +91,29 @@ export function Move( Splide: Splide, Components: Components, options: Options )
    * @param callback - Optional. A callback function invoked after transition ends.
    */
   function move( dest: number, index: number, prev: number, callback?: AnyFunction ): void {
-    if ( ! isBusy() ) {
-      const { set } = Splide.state;
-      const position = getPosition();
+    const { set } = Splide.state;
+    const position = getPosition();
 
-      if ( dest !== index ) {
-        Transition.cancel();
-        translate( shift( position, dest > index ), true );
-      }
+    // todo shift...
+    if ( dest !== index ) {
+      Transition.cancel();
+      translate( shift( position, dest > index ), true );
+    }
 
-      set( MOVING );
-      emit( EVENT_MOVE, index, prev, dest );
+    set( MOVING );
+    emit( EVENT_MOVE, index, prev, dest );
 
-      Transition.start( index, () => {
-        set( IDLE );
-        emit( EVENT_MOVED, index, prev, dest );
+    // todo
+    Transition.start( index, () => {
+      set( IDLE );
+      emit( EVENT_MOVED, index, prev, dest );
 
-        if ( options.trimSpace === 'move' && dest !== prev && position === getPosition() ) {
-          Components.Controller.go( dest > prev ? '>' : '<', false, callback );
-        } else {
-          callback && callback();
-        }
-      } );
-    }
+      if ( options.trimSpace === 'move' && dest !== prev && position === getPosition() ) {
+        Components.Controller.go( dest > prev ? '>' : '<', false, callback );
+      } else {
+        callback && callback();
+      }
+    } );
   }
 
   /**
@@ -268,15 +268,6 @@ export function Move( Splide: Splide, Components: Components, options: Options )
     return toPosition( max ? Components.Controller.getEnd() : 0, !! options.trimSpace );
   }
 
-  /**
-   * Checks if the slider can move now or not.
-   *
-   * @return `true` if the slider can move, or otherwise `false`.
-   */
-  function isBusy(): boolean {
-    return Splide.state.is( MOVING ) && options.waitForTransition;
-  }
-
   /**
    * Checks if the provided position exceeds the minimum or maximum limit or not.
    *
@@ -303,8 +294,8 @@ export function Move( Splide: Splide, Components: Components, options: Options )
     toPosition,
     getPosition,
     getLimit,
-    isBusy,
     exceededLimit,
     reposition,
+    loop,
   };
 }

+ 14 - 14
src/js/components/Move/test/general.test.ts

@@ -103,18 +103,18 @@ describe( 'Move', () => {
     splide.destroy();
   } );
 
-  test( 'can check if the slider can move or not.', () => {
-    const splide   = init( { width: 200, height: 100 } );
-    const { Move } = splide.Components;
-
-    expect( Move.isBusy() ).toBe( false );
-
-    Move.move( 1, 1, -1 );
-    expect( Move.isBusy() ).toBe( true );
-
-    fire( splide.Components.Elements.list, 'transitionend' );
-    expect( Move.isBusy() ).toBe( false );
-
-    splide.destroy();
-  } );
+  // test( 'can check if the slider can move or not.', () => {
+  //   const splide   = init( { width: 200, height: 100 } );
+  //   const { Move } = splide.Components;
+  //
+  //   expect( Move.isBusy() ).toBe( false );
+  //
+  //   Move.move( 1, 1, -1 );
+  //   expect( Move.isBusy() ).toBe( true );
+  //
+  //   fire( splide.Components.Elements.list, 'transitionend' );
+  //   expect( Move.isBusy() ).toBe( false );
+  //
+  //   splide.destroy();
+  // } );
 } );

+ 58 - 48
src/js/components/Scroll/Scroll.ts

@@ -1,9 +1,10 @@
 import { EVENT_MOVE, EVENT_REFRESH, EVENT_SCROLL, EVENT_SCROLLED, EVENT_UPDATED } from '../../constants/events';
+import { IDLE, SCROLLING } from '../../constants/states';
 import { SLIDE } from '../../constants/types';
 import { EventInterface, RequestInterval, RequestIntervalInterface } from '../../constructors';
 import { Splide } from '../../core/Splide/Splide';
 import { AnyFunction, BaseComponent, Components, Options } from '../../types';
-import { abs, between, max } from '../../utils';
+import { abs, apply, between, floor, max, sign } from '../../utils';
 import { BASE_VELOCITY, BOUNCE_DIFF_THRESHOLD, BOUNCE_DURATION, FRICTION_FACTOR, MIN_DURATION } from './constants';
 
 
@@ -13,7 +14,7 @@ import { BASE_VELOCITY, BOUNCE_DIFF_THRESHOLD, BOUNCE_DURATION, FRICTION_FACTOR,
  * @since 3.0.0
  */
 export interface ScrollComponent extends BaseComponent {
-  scroll( position: number, duration?: number, callback?: AnyFunction ): void;
+  scroll( position: number, duration?: number, snap?: boolean, callback?: AnyFunction ): void;
   cancel(): void;
 }
 
@@ -30,8 +31,9 @@ export interface ScrollComponent extends BaseComponent {
  */
 export function Scroll( Splide: Splide, Components: Components, options: Options ): ScrollComponent {
   const { on, emit } = EventInterface( Splide );
+  const { state: { set } } = Splide;
   const { Move } = Components;
-  const { getPosition, getLimit, exceededLimit } = Move;
+  const { getPosition, getLimit, exceededLimit, translate } = Move;
 
   /**
    * Retains the active RequestInterval object.
@@ -41,7 +43,12 @@ export function Scroll( Splide: Splide, Components: Components, options: Options
   /**
    * Holds the callback function.
    */
-  let scrollCallback: AnyFunction;
+  let callback: AnyFunction;
+
+  /**
+   * The current friction (<= 1).
+   */
+  let friction = 1;
 
   /**
    * Called when the component is mounted.
@@ -54,77 +61,80 @@ export function Scroll( Splide: Splide, Components: Components, options: Options
   /**
    * Scrolls the slider to the provided destination.
    *
-   * @param destination        - The destination to scroll to.
-   * @param duration           - Optional. The scroll duration. If omitted, calculates it by the distance.
-   * @param callback           - Optional. A callback invoked after scroll ends.
-   * @param suppressConstraint - Optional. Whether to suppress constraint process when the slider exceeds bounds.
+   * @param destination - The destination to scroll the slider to.
+   * @param duration    - Optional. The scroll duration. If omitted, calculates it by the distance.
+   * @param snap        - Optional. Whether to snap the slider to the closest slide or not.
+   * @param onScrolled  - Optional. A callback invoked after scroll ends.
+   * @param noConstrain - Optional. Whether to suppress constraint process when the slider exceeds bounds.
    */
   function scroll(
     destination: number,
     duration?: number,
-    callback?: AnyFunction,
-    suppressConstraint?: boolean
+    snap?: boolean,
+    onScrolled?: AnyFunction,
+    noConstrain?: boolean
   ): void {
-    const start = getPosition();
-    let friction = 1;
+    const from = getPosition();
 
-    duration       = duration || computeDuration( abs( destination - start ) );
-    scrollCallback = callback;
     clear();
 
-    interval = RequestInterval( duration, onScrolled, rate => {
-      const position = getPosition();
-      const target   = start + ( destination - start ) * easing( rate );
-      const diff     = ( target - getPosition() ) * friction;
-
-      Move.translate( position + diff );
-
-      if ( Splide.is( SLIDE ) && ! suppressConstraint && exceededLimit() ) {
-        friction *= FRICTION_FACTOR;
+    if ( snap ) {
+      const size   = Components.Layout.sliderSize();
+      const offset = sign( destination ) * size * floor( abs( destination ) / size ) || 0;
+      destination = Move.toPosition( Components.Controller.toDest( destination % size ) ) + offset;
+    }
 
-        if ( abs( diff ) < BOUNCE_DIFF_THRESHOLD ) {
-          bounce( exceededLimit( false ) );
-        }
-      }
-    }, 1 );
+    friction = 1;
+    duration = duration || max( abs( destination - from ) / BASE_VELOCITY, MIN_DURATION );
+    callback = onScrolled;
+    interval = RequestInterval( duration, onEnd, apply( update, from, destination, noConstrain ), 1 );
 
+    set( SCROLLING );
     emit( EVENT_SCROLL );
     interval.start();
   }
 
-  /**
-   * Triggers the bounce effect when the slider reaches bounds.
-   *
-   * @param backwards - The direction the slider is going towards.
-   */
-  function bounce( backwards: boolean ): void {
-    scroll( getLimit( ! backwards ), BOUNCE_DURATION, null, true );
-  }
-
   /**
    * Called when scroll ends or has been just canceled.
    */
-  function onScrolled(): void {
+  function onEnd(): void {
     const position = getPosition();
-    const index = Move.toIndex( position );
+    const index    = Move.toIndex( position );
 
     if ( ! between( index, 0, Splide.length - 1 ) ) {
-      Move.translate( Move.shift( position, index > 0 ), true );
+      translate( Move.shift( position, index > 0 ), true );
     }
 
-    scrollCallback && scrollCallback();
+    // todo
+    // translate( getPosition() );
+
+    set( IDLE );
+    callback && callback();
     emit( EVENT_SCROLLED );
   }
 
   /**
-   * Computes the scroll duration by the distance and the base velocity.
+   * Called whenever the interval timer is updated.
    *
-   * @param distance - Distance in pixel.
-   *
-   * @return The duration for scroll.
+   * @param from        - A position where scroll starts.
+   * @param to          - A destination where the slider goes.
+   * @param noConstrain - Whether to suppress constraint process when the slider exceeds bounds.
+   * @param rate        - A current rate.
    */
-  function computeDuration( distance: number ): number {
-    return max( distance / BASE_VELOCITY, MIN_DURATION );
+  function update( from: number, to: number, noConstrain: boolean | undefined, rate: number ): void {
+    const position = getPosition();
+    const target   = from + ( to - from ) * easing( rate );
+    const diff     = ( target - position ) * friction;
+
+    translate( position + diff );
+
+    if ( Splide.is( SLIDE ) && ! noConstrain && exceededLimit() ) {
+      friction *= FRICTION_FACTOR;
+
+      if ( abs( diff ) < BOUNCE_DIFF_THRESHOLD ) {
+        scroll( getLimit( exceededLimit( true ) ), BOUNCE_DURATION, false, undefined, true );
+      }
+    }
   }
 
   /**
@@ -142,7 +152,7 @@ export function Scroll( Splide: Splide, Components: Components, options: Options
   function cancel(): void {
     if ( interval && ! interval.isPaused() ) {
       clear();
-      onScrolled();
+      onEnd();
     }
   }
 

+ 0 - 1
src/js/constants/defaults.ts

@@ -22,7 +22,6 @@ export const DEFAULTS: Options = {
   pauseOnHover     : true,
   pauseOnFocus     : true,
   resetProgress    : true,
-  keyboard         : true,
   easing           : 'cubic-bezier(0.25, 1, 0.5, 1)',
   drag             : true,
   direction        : 'ltr',

+ 8 - 2
src/js/constants/states.ts

@@ -18,15 +18,20 @@ export const IDLE = 3;
  */
 export const MOVING = 4;
 
+/**
+ * Splide is moving.
+ */
+export const SCROLLING = 5;
+
 /**
  * The user is dragging the slider.
  */
-export const DRAGGING = 5;
+export const DRAGGING = 6;
 
 /**
  * Splide has been destroyed.
  */
-export const DESTROYED = 6;
+export const DESTROYED = 7;
 
 /**
  * The collection of all states.
@@ -38,6 +43,7 @@ export const STATES = {
   MOUNTED,
   IDLE,
   MOVING,
+  SCROLLING,
   DRAGGING,
   DESTROYED,
 };

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů