Переглянути джерело

Clean up. Add `snap` and `paginationKeyboard` options.

NaotoshiFujita 3 роки тому
батько
коміт
c323055b28

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
dist/js/splide-renderer.min.js


+ 82 - 83
dist/js/splide.cjs.js

@@ -45,13 +45,15 @@ const CREATED = 1;
 const MOUNTED = 2;
 const MOUNTED = 2;
 const IDLE = 3;
 const IDLE = 3;
 const MOVING = 4;
 const MOVING = 4;
-const DRAGGING = 5;
-const DESTROYED = 6;
+const SCROLLING = 5;
+const DRAGGING = 6;
+const DESTROYED = 7;
 const STATES = {
 const STATES = {
   CREATED,
   CREATED,
   MOUNTED,
   MOUNTED,
   IDLE,
   IDLE,
   MOVING,
   MOVING,
+  SCROLLING,
   DRAGGING,
   DRAGGING,
   DESTROYED
   DESTROYED
 };
 };
@@ -581,6 +583,7 @@ const ORIENTATION_MAP = {
   paddingLeft: ["paddingTop", "paddingRight"],
   paddingLeft: ["paddingTop", "paddingRight"],
   paddingRight: ["paddingBottom", "paddingLeft"],
   paddingRight: ["paddingBottom", "paddingLeft"],
   width: ["height"],
   width: ["height"],
+  Width: ["Height"],
   left: ["top", "right"],
   left: ["top", "right"],
   right: ["bottom", "left"],
   right: ["bottom", "left"],
   x: ["y"],
   x: ["y"],
@@ -1085,6 +1088,7 @@ function Layout(Splide2, Components2, options) {
   };
   };
 }
 }
 
 
+const MULTIPLIER = 2;
 function Clones(Splide2, Components2, options) {
 function Clones(Splide2, Components2, options) {
   const { on, emit } = EventInterface(Splide2);
   const { on, emit } = EventInterface(Splide2);
   const { Elements, Slides } = Components2;
   const { Elements, Slides } = Components2;
@@ -1141,7 +1145,7 @@ function Clones(Splide2, Components2, options) {
     } else if (!clones2) {
     } else if (!clones2) {
       const fixedSize = options[resolve("fixedWidth")] && Components2.Layout.slideSize(0);
       const fixedSize = options[resolve("fixedWidth")] && Components2.Layout.slideSize(0);
       const fixedCount = fixedSize && ceil(rect(Elements.track)[resolve("width")] / fixedSize);
       const fixedCount = fixedSize && ceil(rect(Elements.track)[resolve("width")] / fixedSize);
-      clones2 = fixedCount || options[resolve("autoWidth")] && Splide2.length || options.perPage;
+      clones2 = fixedCount || options[resolve("autoWidth")] && Splide2.length || options.perPage * MULTIPLIER;
     }
     }
     return clones2;
     return clones2;
   }
   }
@@ -1153,6 +1157,7 @@ function Clones(Splide2, Components2, options) {
 
 
 function Move(Splide2, Components2, options) {
 function Move(Splide2, Components2, options) {
   const { on, emit } = EventInterface(Splide2);
   const { on, emit } = EventInterface(Splide2);
+  const { set } = Splide2.state;
   const { slideSize, getPadding, totalSize, listSize, sliderSize } = Components2.Layout;
   const { slideSize, getPadding, totalSize, listSize, sliderSize } = Components2.Layout;
   const { resolve, orient } = Components2.Direction;
   const { resolve, orient } = Components2.Direction;
   const { list, track } = Components2.Elements;
   const { list, track } = Components2.Elements;
@@ -1162,32 +1167,29 @@ function Move(Splide2, Components2, options) {
     on([EVENT_MOUNTED, EVENT_RESIZED, EVENT_UPDATED, EVENT_REFRESH], reposition);
     on([EVENT_MOUNTED, EVENT_RESIZED, EVENT_UPDATED, EVENT_REFRESH], reposition);
   }
   }
   function reposition() {
   function reposition() {
-    if (!isBusy()) {
+    if (!Components2.Controller.isBusy()) {
       Components2.Scroll.cancel();
       Components2.Scroll.cancel();
       jump(Splide2.index);
       jump(Splide2.index);
       emit(EVENT_REPOSITIONED);
       emit(EVENT_REPOSITIONED);
     }
     }
   }
   }
   function move(dest, index, prev, callback) {
   function move(dest, index, prev, callback) {
-    if (!isBusy()) {
-      const { set } = Splide2.state;
-      const position = getPosition();
-      if (dest !== index) {
-        Transition.cancel();
-        translate(shift(position, dest > index), true);
+    const position = getPosition();
+    if (dest !== index && canShift(dest > index)) {
+      cancel();
+      translate(shift(position, dest > index), true);
+    }
+    set(MOVING);
+    emit(EVENT_MOVE, index, prev, dest);
+    Transition.start(index, () => {
+      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();
       }
       }
-      set(MOVING);
-      emit(EVENT_MOVE, index, prev, dest);
-      Transition.start(index, () => {
-        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();
-        }
-      });
-    }
+    });
   }
   }
   function jump(index) {
   function jump(index) {
     translate(toPosition(index, true));
     translate(toPosition(index, true));
@@ -1195,7 +1197,7 @@ function Move(Splide2, Components2, options) {
   function translate(position, preventLoop) {
   function translate(position, preventLoop) {
     if (!Splide2.is(FADE)) {
     if (!Splide2.is(FADE)) {
       const destination = preventLoop ? position : loop(position);
       const destination = preventLoop ? position : loop(position);
-      list.style.transform = `translate${resolve("X")}(${destination}px)`;
+      style(list, "transform", `translate${resolve("X")}(${destination}px)`);
       position !== destination && emit(EVENT_SHIFTED);
       position !== destination && emit(EVENT_SHIFTED);
     }
     }
   }
   }
@@ -1257,8 +1259,9 @@ function Move(Splide2, Components2, options) {
   function getLimit(max) {
   function getLimit(max) {
     return toPosition(max ? Components2.Controller.getEnd() : 0, !!options.trimSpace);
     return toPosition(max ? Components2.Controller.getEnd() : 0, !!options.trimSpace);
   }
   }
-  function isBusy() {
-    return Splide2.state.is(MOVING) && options.waitForTransition;
+  function canShift(backwards) {
+    const shifted = orient(shift(getPosition(), backwards));
+    return backwards ? shifted >= 0 : shifted <= list[`scroll${resolve("Width")}`] - rect(track)[resolve("width")];
   }
   }
   function exceededLimit(max, position) {
   function exceededLimit(max, position) {
     position = isUndefined(position) ? getPosition() : position;
     position = isUndefined(position) ? getPosition() : position;
@@ -1277,7 +1280,6 @@ function Move(Splide2, Components2, options) {
     toPosition,
     toPosition,
     getPosition,
     getPosition,
     getLimit,
     getLimit,
-    isBusy,
     exceededLimit,
     exceededLimit,
     reposition
     reposition
   };
   };
@@ -1286,7 +1288,7 @@ function Move(Splide2, Components2, options) {
 function Controller(Splide2, Components2, options) {
 function Controller(Splide2, Components2, options) {
   const { on } = EventInterface(Splide2);
   const { on } = EventInterface(Splide2);
   const { Move } = Components2;
   const { Move } = Components2;
-  const { getPosition, getLimit } = Move;
+  const { getPosition, getLimit, toPosition } = Move;
   const { isEnough, getLength } = Components2.Slides;
   const { isEnough, getLength } = Components2.Slides;
   const isLoop = Splide2.is(LOOP);
   const isLoop = Splide2.is(LOOP);
   const isSlide = Splide2.is(SLIDE);
   const isSlide = Splide2.is(SLIDE);
@@ -1312,24 +1314,24 @@ function Controller(Splide2, Components2, options) {
     }
     }
   }
   }
   function go(control, allowSameIndex, callback) {
   function go(control, allowSameIndex, callback) {
-    const dest = parse(control);
-    if (options.useScroll) {
-      scroll(dest, true, true, options.speed, callback);
-    } else {
+    if (!isBusy()) {
+      const dest = parse(control);
       const index = loop(dest);
       const index = loop(dest);
-      if (index > -1 && !Move.isBusy() && (allowSameIndex || index !== currIndex)) {
+      if (index > -1 && (allowSameIndex || index !== currIndex)) {
         setIndex(index);
         setIndex(index);
-        Move.move(dest, index, prevIndex, callback);
+        options.useScroll ? scrollTo(dest, options.speed, callback) : Move.move(dest, index, prevIndex, callback);
       }
       }
     }
     }
   }
   }
-  function scroll(destination, useIndex, snap, duration, callback) {
-    const dest = useIndex ? destination : toDest(destination);
-    Components2.Scroll.scroll(useIndex || snap ? Move.toPosition(dest, true) : destination, duration, () => {
+  function scroll(destination, duration, snap, callback) {
+    Components2.Scroll.scroll(destination, duration, snap, () => {
       setIndex(Move.toIndex(Move.getPosition()));
       setIndex(Move.toIndex(Move.getPosition()));
       callback && callback();
       callback && callback();
     });
     });
   }
   }
+  function scrollTo(index, duration, callback) {
+    scroll(toPosition(index, true), duration, false, callback);
+  }
   function parse(control) {
   function parse(control) {
     let index = currIndex;
     let index = currIndex;
     if (isString(control)) {
     if (isString(control)) {
@@ -1382,27 +1384,16 @@ function Controller(Splide2, Components2, options) {
     return dest;
     return dest;
   }
   }
   function getEnd() {
   function getEnd() {
-    let end = slideCount - perPage;
-    if (hasFocus() || isLoop && perMove) {
-      end = slideCount - 1;
-    }
-    return max(end, 0);
+    return max(slideCount - (hasFocus() || isLoop && perMove ? 1 : perPage), 0);
   }
   }
   function loop(index) {
   function loop(index) {
-    if (isLoop) {
-      return isEnough() ? index % slideCount + (index < 0 ? slideCount : 0) : -1;
-    }
-    return index;
+    return isLoop ? (index + slideCount) % slideCount || 0 : index;
   }
   }
   function toIndex(page) {
   function toIndex(page) {
     return clamp(hasFocus() ? page : perPage * page, 0, getEnd());
     return clamp(hasFocus() ? page : perPage * page, 0, getEnd());
   }
   }
   function toPage(index) {
   function toPage(index) {
-    if (!hasFocus()) {
-      index = between(index, slideCount - perPage, slideCount - 1) ? slideCount - 1 : index;
-      index = floor(index / perPage);
-    }
-    return index;
+    return hasFocus() ? index : floor((index >= getEnd() ? slideCount - 1 : index) / perPage);
   }
   }
   function toDest(destination) {
   function toDest(destination) {
     const closest = Move.toIndex(destination);
     const closest = Move.toIndex(destination);
@@ -1420,10 +1411,14 @@ function Controller(Splide2, Components2, options) {
   function hasFocus() {
   function hasFocus() {
     return !isUndefined(options.focus) || options.isNavigation;
     return !isUndefined(options.focus) || options.isNavigation;
   }
   }
+  function isBusy() {
+    return Splide2.state.is([MOVING, SCROLLING]) && options.waitForTransition;
+  }
   return {
   return {
     mount,
     mount,
     go,
     go,
     scroll,
     scroll,
+    scrollTo,
     getNext,
     getNext,
     getPrev,
     getPrev,
     getAdjacent,
     getAdjacent,
@@ -1433,7 +1428,8 @@ function Controller(Splide2, Components2, options) {
     toIndex,
     toIndex,
     toPage,
     toPage,
     toDest,
     toDest,
-    hasFocus
+    hasFocus,
+    isBusy
   };
   };
 }
 }
 
 
@@ -1635,49 +1631,53 @@ const MIN_DURATION = 800;
 
 
 function Scroll(Splide2, Components2, options) {
 function Scroll(Splide2, Components2, options) {
   const { on, emit } = EventInterface(Splide2);
   const { on, emit } = EventInterface(Splide2);
+  const { state: { set } } = Splide2;
   const { Move } = Components2;
   const { Move } = Components2;
-  const { getPosition, getLimit, exceededLimit } = Move;
+  const { getPosition, getLimit, exceededLimit, translate } = Move;
   let interval;
   let interval;
-  let scrollCallback;
+  let callback;
+  let friction = 1;
   function mount() {
   function mount() {
     on(EVENT_MOVE, clear);
     on(EVENT_MOVE, clear);
     on([EVENT_UPDATED, EVENT_REFRESH], cancel);
     on([EVENT_UPDATED, EVENT_REFRESH], cancel);
   }
   }
-  function scroll(destination, duration, callback, suppressConstraint) {
-    const start = getPosition();
-    let friction = 1;
-    duration = duration || computeDuration(abs(destination - start));
-    scrollCallback = callback;
+  function scroll(destination, duration, snap, onScrolled, noConstrain) {
+    const from = getPosition();
     clear();
     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 (Splide2.is(SLIDE) && !suppressConstraint && exceededLimit()) {
-        friction *= FRICTION_FACTOR;
-        if (abs(diff) < BOUNCE_DIFF_THRESHOLD) {
-          bounce(exceededLimit(false));
-        }
-      }
-    }, 1);
+    if (snap) {
+      const size = Components2.Layout.sliderSize();
+      const offset = sign(destination) * size * floor(abs(destination) / size) || 0;
+      destination = Move.toPosition(Components2.Controller.toDest(destination % size)) + offset;
+    }
+    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);
     emit(EVENT_SCROLL);
     interval.start();
     interval.start();
   }
   }
-  function bounce(backwards) {
-    scroll(getLimit(!backwards), BOUNCE_DURATION, null, true);
-  }
-  function onScrolled() {
+  function onEnd() {
     const position = getPosition();
     const position = getPosition();
     const index = Move.toIndex(position);
     const index = Move.toIndex(position);
     if (!between(index, 0, Splide2.length - 1)) {
     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);
     emit(EVENT_SCROLLED);
   }
   }
-  function computeDuration(distance) {
-    return max(distance / BASE_VELOCITY, MIN_DURATION);
+  function update(from, to, noConstrain, rate) {
+    const position = getPosition();
+    const target = from + (to - from) * easing(rate);
+    const 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() {
   function clear() {
     if (interval) {
     if (interval) {
@@ -1687,7 +1687,7 @@ function Scroll(Splide2, Components2, options) {
   function cancel() {
   function cancel() {
     if (interval && !interval.isPaused()) {
     if (interval && !interval.isPaused()) {
       clear();
       clear();
-      onScrolled();
+      onEnd();
     }
     }
   }
   }
   function easing(t) {
   function easing(t) {
@@ -1746,9 +1746,9 @@ function Drag(Splide2, Components2, options) {
       const isTouch = isTouchEvent(e);
       const isTouch = isTouchEvent(e);
       const isDraggable = !noDrag || !matches(e.target, noDrag);
       const isDraggable = !noDrag || !matches(e.target, noDrag);
       if (isDraggable && (isTouch || !e.button)) {
       if (isDraggable && (isTouch || !e.button)) {
-        if (!Move.isBusy()) {
+        if (!Controller.isBusy()) {
           target = isTouch ? track : window;
           target = isTouch ? track : window;
-          dragging = state.is(MOVING);
+          dragging = state.is([MOVING, SCROLLING]);
           prevBaseEvent = null;
           prevBaseEvent = null;
           bind(target, POINTER_MOVE_EVENTS, onPointerMove, SCROLL_LISTENER_OPTIONS);
           bind(target, POINTER_MOVE_EVENTS, onPointerMove, SCROLL_LISTENER_OPTIONS);
           bind(target, POINTER_UP_EVENTS, onPointerUp, SCROLL_LISTENER_OPTIONS);
           bind(target, POINTER_UP_EVENTS, onPointerUp, SCROLL_LISTENER_OPTIONS);
@@ -1811,7 +1811,7 @@ function Drag(Splide2, Components2, options) {
     const destination = computeDestination(velocity);
     const destination = computeDestination(velocity);
     const rewind = options.rewind && options.rewindByDrag;
     const rewind = options.rewind && options.rewindByDrag;
     if (isFree) {
     if (isFree) {
-      Controller.scroll(destination);
+      Controller.scrollTo(destination);
     } else if (Splide2.is(FADE)) {
     } else if (Splide2.is(FADE)) {
       Controller.go(orient(sign(velocity)) < 0 ? rewind ? "<" : "-" : rewind ? ">" : "+");
       Controller.go(orient(sign(velocity)) < 0 ? rewind ? "<" : "-" : rewind ? ">" : "+");
     } else if (Splide2.is(SLIDE) && exceeded && rewind) {
     } else if (Splide2.is(SLIDE) && exceeded && rewind) {
@@ -2302,7 +2302,6 @@ const DEFAULTS = {
   pauseOnHover: true,
   pauseOnHover: true,
   pauseOnFocus: true,
   pauseOnFocus: true,
   resetProgress: true,
   resetProgress: true,
-  keyboard: true,
   easing: "cubic-bezier(0.25, 1, 0.5, 1)",
   easing: "cubic-bezier(0.25, 1, 0.5, 1)",
   drag: true,
   drag: true,
   direction: "ltr",
   direction: "ltr",

+ 82 - 83
dist/js/splide.esm.js

@@ -41,13 +41,15 @@ const CREATED = 1;
 const MOUNTED = 2;
 const MOUNTED = 2;
 const IDLE = 3;
 const IDLE = 3;
 const MOVING = 4;
 const MOVING = 4;
-const DRAGGING = 5;
-const DESTROYED = 6;
+const SCROLLING = 5;
+const DRAGGING = 6;
+const DESTROYED = 7;
 const STATES = {
 const STATES = {
   CREATED,
   CREATED,
   MOUNTED,
   MOUNTED,
   IDLE,
   IDLE,
   MOVING,
   MOVING,
+  SCROLLING,
   DRAGGING,
   DRAGGING,
   DESTROYED
   DESTROYED
 };
 };
@@ -577,6 +579,7 @@ const ORIENTATION_MAP = {
   paddingLeft: ["paddingTop", "paddingRight"],
   paddingLeft: ["paddingTop", "paddingRight"],
   paddingRight: ["paddingBottom", "paddingLeft"],
   paddingRight: ["paddingBottom", "paddingLeft"],
   width: ["height"],
   width: ["height"],
+  Width: ["Height"],
   left: ["top", "right"],
   left: ["top", "right"],
   right: ["bottom", "left"],
   right: ["bottom", "left"],
   x: ["y"],
   x: ["y"],
@@ -1081,6 +1084,7 @@ function Layout(Splide2, Components2, options) {
   };
   };
 }
 }
 
 
+const MULTIPLIER = 2;
 function Clones(Splide2, Components2, options) {
 function Clones(Splide2, Components2, options) {
   const { on, emit } = EventInterface(Splide2);
   const { on, emit } = EventInterface(Splide2);
   const { Elements, Slides } = Components2;
   const { Elements, Slides } = Components2;
@@ -1137,7 +1141,7 @@ function Clones(Splide2, Components2, options) {
     } else if (!clones2) {
     } else if (!clones2) {
       const fixedSize = options[resolve("fixedWidth")] && Components2.Layout.slideSize(0);
       const fixedSize = options[resolve("fixedWidth")] && Components2.Layout.slideSize(0);
       const fixedCount = fixedSize && ceil(rect(Elements.track)[resolve("width")] / fixedSize);
       const fixedCount = fixedSize && ceil(rect(Elements.track)[resolve("width")] / fixedSize);
-      clones2 = fixedCount || options[resolve("autoWidth")] && Splide2.length || options.perPage;
+      clones2 = fixedCount || options[resolve("autoWidth")] && Splide2.length || options.perPage * MULTIPLIER;
     }
     }
     return clones2;
     return clones2;
   }
   }
@@ -1149,6 +1153,7 @@ function Clones(Splide2, Components2, options) {
 
 
 function Move(Splide2, Components2, options) {
 function Move(Splide2, Components2, options) {
   const { on, emit } = EventInterface(Splide2);
   const { on, emit } = EventInterface(Splide2);
+  const { set } = Splide2.state;
   const { slideSize, getPadding, totalSize, listSize, sliderSize } = Components2.Layout;
   const { slideSize, getPadding, totalSize, listSize, sliderSize } = Components2.Layout;
   const { resolve, orient } = Components2.Direction;
   const { resolve, orient } = Components2.Direction;
   const { list, track } = Components2.Elements;
   const { list, track } = Components2.Elements;
@@ -1158,32 +1163,29 @@ function Move(Splide2, Components2, options) {
     on([EVENT_MOUNTED, EVENT_RESIZED, EVENT_UPDATED, EVENT_REFRESH], reposition);
     on([EVENT_MOUNTED, EVENT_RESIZED, EVENT_UPDATED, EVENT_REFRESH], reposition);
   }
   }
   function reposition() {
   function reposition() {
-    if (!isBusy()) {
+    if (!Components2.Controller.isBusy()) {
       Components2.Scroll.cancel();
       Components2.Scroll.cancel();
       jump(Splide2.index);
       jump(Splide2.index);
       emit(EVENT_REPOSITIONED);
       emit(EVENT_REPOSITIONED);
     }
     }
   }
   }
   function move(dest, index, prev, callback) {
   function move(dest, index, prev, callback) {
-    if (!isBusy()) {
-      const { set } = Splide2.state;
-      const position = getPosition();
-      if (dest !== index) {
-        Transition.cancel();
-        translate(shift(position, dest > index), true);
+    const position = getPosition();
+    if (dest !== index && canShift(dest > index)) {
+      cancel();
+      translate(shift(position, dest > index), true);
+    }
+    set(MOVING);
+    emit(EVENT_MOVE, index, prev, dest);
+    Transition.start(index, () => {
+      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();
       }
       }
-      set(MOVING);
-      emit(EVENT_MOVE, index, prev, dest);
-      Transition.start(index, () => {
-        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();
-        }
-      });
-    }
+    });
   }
   }
   function jump(index) {
   function jump(index) {
     translate(toPosition(index, true));
     translate(toPosition(index, true));
@@ -1191,7 +1193,7 @@ function Move(Splide2, Components2, options) {
   function translate(position, preventLoop) {
   function translate(position, preventLoop) {
     if (!Splide2.is(FADE)) {
     if (!Splide2.is(FADE)) {
       const destination = preventLoop ? position : loop(position);
       const destination = preventLoop ? position : loop(position);
-      list.style.transform = `translate${resolve("X")}(${destination}px)`;
+      style(list, "transform", `translate${resolve("X")}(${destination}px)`);
       position !== destination && emit(EVENT_SHIFTED);
       position !== destination && emit(EVENT_SHIFTED);
     }
     }
   }
   }
@@ -1253,8 +1255,9 @@ function Move(Splide2, Components2, options) {
   function getLimit(max) {
   function getLimit(max) {
     return toPosition(max ? Components2.Controller.getEnd() : 0, !!options.trimSpace);
     return toPosition(max ? Components2.Controller.getEnd() : 0, !!options.trimSpace);
   }
   }
-  function isBusy() {
-    return Splide2.state.is(MOVING) && options.waitForTransition;
+  function canShift(backwards) {
+    const shifted = orient(shift(getPosition(), backwards));
+    return backwards ? shifted >= 0 : shifted <= list[`scroll${resolve("Width")}`] - rect(track)[resolve("width")];
   }
   }
   function exceededLimit(max, position) {
   function exceededLimit(max, position) {
     position = isUndefined(position) ? getPosition() : position;
     position = isUndefined(position) ? getPosition() : position;
@@ -1273,7 +1276,6 @@ function Move(Splide2, Components2, options) {
     toPosition,
     toPosition,
     getPosition,
     getPosition,
     getLimit,
     getLimit,
-    isBusy,
     exceededLimit,
     exceededLimit,
     reposition
     reposition
   };
   };
@@ -1282,7 +1284,7 @@ function Move(Splide2, Components2, options) {
 function Controller(Splide2, Components2, options) {
 function Controller(Splide2, Components2, options) {
   const { on } = EventInterface(Splide2);
   const { on } = EventInterface(Splide2);
   const { Move } = Components2;
   const { Move } = Components2;
-  const { getPosition, getLimit } = Move;
+  const { getPosition, getLimit, toPosition } = Move;
   const { isEnough, getLength } = Components2.Slides;
   const { isEnough, getLength } = Components2.Slides;
   const isLoop = Splide2.is(LOOP);
   const isLoop = Splide2.is(LOOP);
   const isSlide = Splide2.is(SLIDE);
   const isSlide = Splide2.is(SLIDE);
@@ -1308,24 +1310,24 @@ function Controller(Splide2, Components2, options) {
     }
     }
   }
   }
   function go(control, allowSameIndex, callback) {
   function go(control, allowSameIndex, callback) {
-    const dest = parse(control);
-    if (options.useScroll) {
-      scroll(dest, true, true, options.speed, callback);
-    } else {
+    if (!isBusy()) {
+      const dest = parse(control);
       const index = loop(dest);
       const index = loop(dest);
-      if (index > -1 && !Move.isBusy() && (allowSameIndex || index !== currIndex)) {
+      if (index > -1 && (allowSameIndex || index !== currIndex)) {
         setIndex(index);
         setIndex(index);
-        Move.move(dest, index, prevIndex, callback);
+        options.useScroll ? scrollTo(dest, options.speed, callback) : Move.move(dest, index, prevIndex, callback);
       }
       }
     }
     }
   }
   }
-  function scroll(destination, useIndex, snap, duration, callback) {
-    const dest = useIndex ? destination : toDest(destination);
-    Components2.Scroll.scroll(useIndex || snap ? Move.toPosition(dest, true) : destination, duration, () => {
+  function scroll(destination, duration, snap, callback) {
+    Components2.Scroll.scroll(destination, duration, snap, () => {
       setIndex(Move.toIndex(Move.getPosition()));
       setIndex(Move.toIndex(Move.getPosition()));
       callback && callback();
       callback && callback();
     });
     });
   }
   }
+  function scrollTo(index, duration, callback) {
+    scroll(toPosition(index, true), duration, false, callback);
+  }
   function parse(control) {
   function parse(control) {
     let index = currIndex;
     let index = currIndex;
     if (isString(control)) {
     if (isString(control)) {
@@ -1378,27 +1380,16 @@ function Controller(Splide2, Components2, options) {
     return dest;
     return dest;
   }
   }
   function getEnd() {
   function getEnd() {
-    let end = slideCount - perPage;
-    if (hasFocus() || isLoop && perMove) {
-      end = slideCount - 1;
-    }
-    return max(end, 0);
+    return max(slideCount - (hasFocus() || isLoop && perMove ? 1 : perPage), 0);
   }
   }
   function loop(index) {
   function loop(index) {
-    if (isLoop) {
-      return isEnough() ? index % slideCount + (index < 0 ? slideCount : 0) : -1;
-    }
-    return index;
+    return isLoop ? (index + slideCount) % slideCount || 0 : index;
   }
   }
   function toIndex(page) {
   function toIndex(page) {
     return clamp(hasFocus() ? page : perPage * page, 0, getEnd());
     return clamp(hasFocus() ? page : perPage * page, 0, getEnd());
   }
   }
   function toPage(index) {
   function toPage(index) {
-    if (!hasFocus()) {
-      index = between(index, slideCount - perPage, slideCount - 1) ? slideCount - 1 : index;
-      index = floor(index / perPage);
-    }
-    return index;
+    return hasFocus() ? index : floor((index >= getEnd() ? slideCount - 1 : index) / perPage);
   }
   }
   function toDest(destination) {
   function toDest(destination) {
     const closest = Move.toIndex(destination);
     const closest = Move.toIndex(destination);
@@ -1416,10 +1407,14 @@ function Controller(Splide2, Components2, options) {
   function hasFocus() {
   function hasFocus() {
     return !isUndefined(options.focus) || options.isNavigation;
     return !isUndefined(options.focus) || options.isNavigation;
   }
   }
+  function isBusy() {
+    return Splide2.state.is([MOVING, SCROLLING]) && options.waitForTransition;
+  }
   return {
   return {
     mount,
     mount,
     go,
     go,
     scroll,
     scroll,
+    scrollTo,
     getNext,
     getNext,
     getPrev,
     getPrev,
     getAdjacent,
     getAdjacent,
@@ -1429,7 +1424,8 @@ function Controller(Splide2, Components2, options) {
     toIndex,
     toIndex,
     toPage,
     toPage,
     toDest,
     toDest,
-    hasFocus
+    hasFocus,
+    isBusy
   };
   };
 }
 }
 
 
@@ -1631,49 +1627,53 @@ const MIN_DURATION = 800;
 
 
 function Scroll(Splide2, Components2, options) {
 function Scroll(Splide2, Components2, options) {
   const { on, emit } = EventInterface(Splide2);
   const { on, emit } = EventInterface(Splide2);
+  const { state: { set } } = Splide2;
   const { Move } = Components2;
   const { Move } = Components2;
-  const { getPosition, getLimit, exceededLimit } = Move;
+  const { getPosition, getLimit, exceededLimit, translate } = Move;
   let interval;
   let interval;
-  let scrollCallback;
+  let callback;
+  let friction = 1;
   function mount() {
   function mount() {
     on(EVENT_MOVE, clear);
     on(EVENT_MOVE, clear);
     on([EVENT_UPDATED, EVENT_REFRESH], cancel);
     on([EVENT_UPDATED, EVENT_REFRESH], cancel);
   }
   }
-  function scroll(destination, duration, callback, suppressConstraint) {
-    const start = getPosition();
-    let friction = 1;
-    duration = duration || computeDuration(abs(destination - start));
-    scrollCallback = callback;
+  function scroll(destination, duration, snap, onScrolled, noConstrain) {
+    const from = getPosition();
     clear();
     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 (Splide2.is(SLIDE) && !suppressConstraint && exceededLimit()) {
-        friction *= FRICTION_FACTOR;
-        if (abs(diff) < BOUNCE_DIFF_THRESHOLD) {
-          bounce(exceededLimit(false));
-        }
-      }
-    }, 1);
+    if (snap) {
+      const size = Components2.Layout.sliderSize();
+      const offset = sign(destination) * size * floor(abs(destination) / size) || 0;
+      destination = Move.toPosition(Components2.Controller.toDest(destination % size)) + offset;
+    }
+    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);
     emit(EVENT_SCROLL);
     interval.start();
     interval.start();
   }
   }
-  function bounce(backwards) {
-    scroll(getLimit(!backwards), BOUNCE_DURATION, null, true);
-  }
-  function onScrolled() {
+  function onEnd() {
     const position = getPosition();
     const position = getPosition();
     const index = Move.toIndex(position);
     const index = Move.toIndex(position);
     if (!between(index, 0, Splide2.length - 1)) {
     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);
     emit(EVENT_SCROLLED);
   }
   }
-  function computeDuration(distance) {
-    return max(distance / BASE_VELOCITY, MIN_DURATION);
+  function update(from, to, noConstrain, rate) {
+    const position = getPosition();
+    const target = from + (to - from) * easing(rate);
+    const 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() {
   function clear() {
     if (interval) {
     if (interval) {
@@ -1683,7 +1683,7 @@ function Scroll(Splide2, Components2, options) {
   function cancel() {
   function cancel() {
     if (interval && !interval.isPaused()) {
     if (interval && !interval.isPaused()) {
       clear();
       clear();
-      onScrolled();
+      onEnd();
     }
     }
   }
   }
   function easing(t) {
   function easing(t) {
@@ -1742,9 +1742,9 @@ function Drag(Splide2, Components2, options) {
       const isTouch = isTouchEvent(e);
       const isTouch = isTouchEvent(e);
       const isDraggable = !noDrag || !matches(e.target, noDrag);
       const isDraggable = !noDrag || !matches(e.target, noDrag);
       if (isDraggable && (isTouch || !e.button)) {
       if (isDraggable && (isTouch || !e.button)) {
-        if (!Move.isBusy()) {
+        if (!Controller.isBusy()) {
           target = isTouch ? track : window;
           target = isTouch ? track : window;
-          dragging = state.is(MOVING);
+          dragging = state.is([MOVING, SCROLLING]);
           prevBaseEvent = null;
           prevBaseEvent = null;
           bind(target, POINTER_MOVE_EVENTS, onPointerMove, SCROLL_LISTENER_OPTIONS);
           bind(target, POINTER_MOVE_EVENTS, onPointerMove, SCROLL_LISTENER_OPTIONS);
           bind(target, POINTER_UP_EVENTS, onPointerUp, SCROLL_LISTENER_OPTIONS);
           bind(target, POINTER_UP_EVENTS, onPointerUp, SCROLL_LISTENER_OPTIONS);
@@ -1807,7 +1807,7 @@ function Drag(Splide2, Components2, options) {
     const destination = computeDestination(velocity);
     const destination = computeDestination(velocity);
     const rewind = options.rewind && options.rewindByDrag;
     const rewind = options.rewind && options.rewindByDrag;
     if (isFree) {
     if (isFree) {
-      Controller.scroll(destination);
+      Controller.scrollTo(destination);
     } else if (Splide2.is(FADE)) {
     } else if (Splide2.is(FADE)) {
       Controller.go(orient(sign(velocity)) < 0 ? rewind ? "<" : "-" : rewind ? ">" : "+");
       Controller.go(orient(sign(velocity)) < 0 ? rewind ? "<" : "-" : rewind ? ">" : "+");
     } else if (Splide2.is(SLIDE) && exceeded && rewind) {
     } else if (Splide2.is(SLIDE) && exceeded && rewind) {
@@ -2298,7 +2298,6 @@ const DEFAULTS = {
   pauseOnHover: true,
   pauseOnHover: true,
   pauseOnFocus: true,
   pauseOnFocus: true,
   resetProgress: true,
   resetProgress: true,
-  keyboard: true,
   easing: "cubic-bezier(0.25, 1, 0.5, 1)",
   easing: "cubic-bezier(0.25, 1, 0.5, 1)",
   drag: true,
   drag: true,
   direction: "ltr",
   direction: "ltr",

+ 27 - 44
dist/js/splide.js

@@ -1253,6 +1253,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     };
     };
   }
   }
 
 
+  var MULTIPLIER = 2;
+
   function Clones(Splide2, Components2, options) {
   function Clones(Splide2, Components2, options) {
     var _EventInterface5 = EventInterface(Splide2),
     var _EventInterface5 = EventInterface(Splide2),
         on = _EventInterface5.on,
         on = _EventInterface5.on,
@@ -1323,8 +1325,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       } else if (!clones2) {
       } else if (!clones2) {
         var fixedSize = options[resolve("fixedWidth")] && Components2.Layout.slideSize(0);
         var fixedSize = options[resolve("fixedWidth")] && Components2.Layout.slideSize(0);
         var fixedCount = fixedSize && ceil(rect(Elements.track)[resolve("width")] / fixedSize);
         var fixedCount = fixedSize && ceil(rect(Elements.track)[resolve("width")] / fixedSize);
-        var baseCount = fixedCount || options[resolve("autoWidth")] && Splide2.length || options.perPage;
-        clones2 = baseCount * 2;
+        clones2 = fixedCount || options[resolve("autoWidth")] && Splide2.length || options.perPage * MULTIPLIER;
       }
       }
 
 
       return clones2;
       return clones2;
@@ -1379,18 +1380,16 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
 
 
       set(MOVING);
       set(MOVING);
       emit(EVENT_MOVE, index, prev, dest);
       emit(EVENT_MOVE, index, prev, dest);
-      Transition.start(index, apply(onTransitionEnd, position, dest, index, prev, callback));
-    }
-
-    function onTransitionEnd(from, dest, index, prev, callback) {
-      set(IDLE);
-      emit(EVENT_MOVED, index, prev, dest);
+      Transition.start(index, function () {
+        set(IDLE);
+        emit(EVENT_MOVED, index, prev, dest);
 
 
-      if (options.trimSpace === "move" && dest !== prev && from === 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) {
     function jump(index) {
@@ -1547,16 +1546,11 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     function go(control, allowSameIndex, callback) {
     function go(control, allowSameIndex, callback) {
       if (!isBusy()) {
       if (!isBusy()) {
         var dest = parse(control);
         var dest = parse(control);
+        var index = loop(dest);
 
 
-        if (options.useScroll) {
-          scrollTo(dest, options.speed, callback);
-        } else {
-          var index = loop(dest);
-
-          if (index > -1 && (allowSameIndex || index !== currIndex)) {
-            setIndex(index);
-            Move.move(dest, index, prevIndex, callback);
-          }
+        if (index > -1 && (allowSameIndex || index !== currIndex)) {
+          setIndex(index);
+          options.useScroll ? scrollTo(dest, options.speed, callback) : Move.move(dest, index, prevIndex, callback);
         }
         }
       }
       }
     }
     }
@@ -1569,7 +1563,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     }
     }
 
 
     function scrollTo(index, duration, callback) {
     function scrollTo(index, duration, callback) {
-      scroll(toPosition(index), duration, false, callback);
+      scroll(toPosition(index, true), duration, false, callback);
     }
     }
 
 
     function parse(control) {
     function parse(control) {
@@ -1636,21 +1630,11 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     }
     }
 
 
     function getEnd() {
     function getEnd() {
-      var end = slideCount - perPage;
-
-      if (hasFocus() || isLoop && perMove) {
-        end = slideCount - 1;
-      }
-
-      return max(end, 0);
+      return max(slideCount - (hasFocus() || isLoop && perMove ? 1 : perPage), 0);
     }
     }
 
 
     function loop(index) {
     function loop(index) {
-      if (isLoop) {
-        return isEnough() ? index % slideCount + (index < 0 ? slideCount : 0) : -1;
-      }
-
-      return index;
+      return isLoop ? (index + slideCount) % slideCount || 0 : index;
     }
     }
 
 
     function toIndex(page) {
     function toIndex(page) {
@@ -1658,12 +1642,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     }
     }
 
 
     function toPage(index) {
     function toPage(index) {
-      if (!hasFocus()) {
-        index = between(index, slideCount - perPage, slideCount - 1) ? slideCount - 1 : index;
-        index = floor(index / perPage);
-      }
-
-      return index;
+      return hasFocus() ? index : floor((index >= getEnd() ? slideCount - 1 : index) / perPage);
     }
     }
 
 
     function toDest(destination) {
     function toDest(destination) {
@@ -2181,7 +2160,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       var rewind = options.rewind && options.rewindByDrag;
       var rewind = options.rewind && options.rewindByDrag;
 
 
       if (isFree) {
       if (isFree) {
-        Controller.scrollTo(destination);
+        Controller.scroll(destination, 0, options.snap);
       } else if (Splide2.is(FADE)) {
       } else if (Splide2.is(FADE)) {
         Controller.go(orient(sign(velocity)) < 0 ? rewind ? "<" : "-" : rewind ? ">" : "+");
         Controller.go(orient(sign(velocity)) < 0 ? rewind ? "<" : "-" : rewind ? ">" : "+");
       } else if (Splide2.is(SLIDE) && exceeded && rewind) {
       } else if (Splide2.is(SLIDE) && exceeded && rewind) {
@@ -2531,7 +2510,11 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
         });
         });
         var text = !hasFocus() && perPage > 1 ? i18n.pageX : i18n.slideX;
         var text = !hasFocus() && perPage > 1 ? i18n.pageX : i18n.slideX;
         bind(button, "click", apply(onClick, i));
         bind(button, "click", apply(onClick, i));
-        bind(button, "keydown", apply(onKeydown, i));
+
+        if (options.paginationKeyboard) {
+          bind(button, "keydown", apply(onKeydown, i));
+        }
+
         setAttribute(li, ROLE, "none");
         setAttribute(li, ROLE, "none");
         setAttribute(button, ROLE, "tab");
         setAttribute(button, ROLE, "tab");
         setAttribute(button, ARIA_CONTROLS, controls.join(" "));
         setAttribute(button, ARIA_CONTROLS, controls.join(" "));
@@ -2788,11 +2771,11 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     type: "slide",
     type: "slide",
     role: "region",
     role: "region",
     speed: 400,
     speed: 400,
-    waitForTransition: true,
     perPage: 1,
     perPage: 1,
     cloneStatus: true,
     cloneStatus: true,
     arrows: true,
     arrows: true,
     pagination: true,
     pagination: true,
+    paginationKeyboard: true,
     interval: 5e3,
     interval: 5e3,
     pauseOnHover: true,
     pauseOnHover: true,
     pauseOnFocus: true,
     pauseOnFocus: true,

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
dist/js/splide.min.js


BIN
dist/js/splide.min.js.gz


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
dist/js/splide.min.js.map


+ 5 - 3
dist/types/index.d.ts

@@ -95,7 +95,6 @@ interface MoveComponent extends BaseComponent {
     toPosition(index: number, trimming?: boolean): number;
     toPosition(index: number, trimming?: boolean): number;
     getPosition(): number;
     getPosition(): number;
     getLimit(max: boolean): number;
     getLimit(max: boolean): number;
-    isBusy(): boolean;
     exceededLimit(max?: boolean | undefined, position?: number): boolean;
     exceededLimit(max?: boolean | undefined, position?: number): boolean;
     /** @internal */
     /** @internal */
     reposition(): void;
     reposition(): void;
@@ -108,7 +107,8 @@ interface MoveComponent extends BaseComponent {
  */
  */
 interface ControllerComponent extends BaseComponent {
 interface ControllerComponent extends BaseComponent {
     go(control: number | string, allowSameIndex?: boolean, callback?: AnyFunction): void;
     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;
     getNext(destination?: boolean): number;
     getPrev(destination?: boolean): number;
     getPrev(destination?: boolean): number;
     getAdjacent(prev: boolean, destination?: boolean): number;
     getAdjacent(prev: boolean, destination?: boolean): number;
@@ -119,6 +119,7 @@ interface ControllerComponent extends BaseComponent {
     toPage(index: number): number;
     toPage(index: number): number;
     toDest(position: number): number;
     toDest(position: number): number;
     hasFocus(): boolean;
     hasFocus(): boolean;
+    isBusy(): boolean;
 }
 }
 
 
 /**
 /**
@@ -158,7 +159,7 @@ interface CoverComponent extends BaseComponent {
  * @since 3.0.0
  * @since 3.0.0
  */
  */
 interface ScrollComponent extends BaseComponent {
 interface ScrollComponent extends BaseComponent {
-    scroll(position: number, duration?: number, callback?: AnyFunction): void;
+    scroll(position: number, duration?: number, snap?: boolean, callback?: AnyFunction): void;
     cancel(): void;
     cancel(): void;
 }
 }
 
 
@@ -916,6 +917,7 @@ declare class Splide {
         MOUNTED: number;
         MOUNTED: number;
         IDLE: number;
         IDLE: number;
         MOVING: number;
         MOVING: number;
+        SCROLLING: number;
         DRAGGING: number;
         DRAGGING: number;
         DESTROYED: number;
         DESTROYED: number;
     };
     };

+ 8 - 4
src/js/components/Clones/Clones.ts

@@ -14,6 +14,13 @@ import { addClass, append, before, ceil, empty, pad, push, rect, remove } from '
 export interface ClonesComponent extends BaseComponent {
 export interface ClonesComponent extends BaseComponent {
 }
 }
 
 
+/**
+ * The multiplier to determine the number of clones.
+ *
+ * @since 3.7.0
+ */
+export const MULTIPLIER = 2;
+
 /**
 /**
  * The component that generates clones for the loop slider.
  * The component that generates clones for the loop slider.
  *
  *
@@ -70,8 +77,6 @@ export function Clones( Splide: Splide, Components: Components, options: Options
 
 
   /**
   /**
    * Observes the required clone count and refreshes the slider if necessary.
    * Observes the required clone count and refreshes the slider if necessary.
-   *
-   * @todo
    */
    */
   function observe(): void {
   function observe(): void {
     if ( cloneCount < computeCloneCount() ) {
     if ( cloneCount < computeCloneCount() ) {
@@ -132,8 +137,7 @@ export function Clones( Splide: Splide, Components: Components, options: Options
     } else if ( ! clones ) {
     } else if ( ! clones ) {
       const fixedSize  = options[ resolve( 'fixedWidth' ) ] && Components.Layout.slideSize( 0 );
       const fixedSize  = options[ resolve( 'fixedWidth' ) ] && Components.Layout.slideSize( 0 );
       const fixedCount = fixedSize && ceil( rect( Elements.track )[ resolve( 'width' ) ] / fixedSize );
       const fixedCount = fixedSize && ceil( rect( Elements.track )[ resolve( 'width' ) ] / fixedSize );
-      const baseCount  = fixedCount || ( options[ resolve( 'autoWidth' ) ] && Splide.length ) || options.perPage;
-      clones = baseCount * 2; // todo
+      clones = fixedCount || ( options[ resolve( 'autoWidth' ) ] && Splide.length ) || options.perPage * MULTIPLIER;
     }
     }
 
 
     return clones;
     return clones;

+ 8 - 6
src/js/components/Clones/test/general.test.ts

@@ -1,5 +1,6 @@
 import { CLASS_CLONE } from '../../../constants/classes';
 import { CLASS_CLONE } from '../../../constants/classes';
 import { init } from '../../../test';
 import { init } from '../../../test';
+import { MULTIPLIER } from '../Clones';
 
 
 
 
 describe( 'Clones', () => {
 describe( 'Clones', () => {
@@ -13,22 +14,23 @@ describe( 'Clones', () => {
     expect( clones[ 2 ].id ).toBe( 'splide01-clone03' );
     expect( clones[ 2 ].id ).toBe( 'splide01-clone03' );
   } );
   } );
 
 
-  test( 'can generate clones according to the flickMaxPages option.', () => {
+  test( 'can generate clones.', () => {
     const splide = init( { type: 'loop', perPage: 3 } );
     const splide = init( { type: 'loop', perPage: 3 } );
     const clones = splide.root.getElementsByClassName( CLASS_CLONE );
     const clones = splide.root.getElementsByClassName( CLASS_CLONE );
     const Slides = splide.Components.Slides.get( true );
     const Slides = splide.Components.Slides.get( true );
+    const count  = clones.length / 2; // each side.
 
 
-    expect( clones.length ).toBe( splide.options.perPage * 2 );
-    expect( clones[ 2 ].nextElementSibling ).toBe( Slides[ 0 ].slide );
-    expect( clones[ 3 ].previousElementSibling ).toBe( Slides[ Slides.length - 1 ].slide );
+    expect( count ).toBe( splide.options.perPage * MULTIPLIER );
+    expect( clones[ count - 1 ].nextElementSibling ).toBe( Slides[ 0 ].slide );
+    expect( clones[ count ].previousElementSibling ).toBe( Slides[ Slides.length - 1 ].slide );
   } );
   } );
 
 
   test( 'can generate clones according to the perPage option.', () => {
   test( 'can generate clones according to the perPage option.', () => {
-    const splide = init( { type: 'loop', flickMaxPages: 1, perPage: 3 } );
+    const splide = init( { type: 'loop', perPage: 3 } );
     const clones = splide.root.getElementsByClassName( CLASS_CLONE );
     const clones = splide.root.getElementsByClassName( CLASS_CLONE );
     const { perPage } = splide.options;
     const { perPage } = splide.options;
 
 
-    expect( clones.length ).toBe( perPage * 2 );
+    expect( clones.length / 2 ).toBe( perPage * MULTIPLIER );
   } );
   } );
 
 
   test( 'should register clones to Slides component.', () => {
   test( 'should register clones to Slides component.', () => {

+ 11 - 31
src/js/components/Controller/Controller.ts

@@ -112,17 +112,12 @@ export function Controller( Splide: Splide, Components: Components, options: Opt
    */
    */
   function go( control: number | string, allowSameIndex?: boolean, callback?: AnyFunction ): void {
   function go( control: number | string, allowSameIndex?: boolean, callback?: AnyFunction ): void {
     if ( ! isBusy() ) {
     if ( ! isBusy() ) {
-      const dest = parse( control );
+      const dest  = parse( control );
+      const index = loop( dest );
 
 
-      if ( options.useScroll ) {
-        scrollTo( dest, options.speed, callback );
-      } else {
-        const index = loop( dest );
-
-        if ( index > -1 && ( allowSameIndex || index !== currIndex ) ) {
-          setIndex( index );
-          Move.move( dest, index, prevIndex, callback );
-        }
+      if ( index > -1 && ( allowSameIndex || index !== currIndex ) ) {
+        setIndex( index );
+        options.useScroll ? scrollTo( dest, options.speed, callback ) : Move.move( dest, index, prevIndex, callback );
       }
       }
     }
     }
   }
   }
@@ -150,7 +145,7 @@ export function Controller( Splide: Splide, Components: Components, options: Opt
    * @param callback - Optional. A callback function invoked after scroll ends.
    * @param callback - Optional. A callback function invoked after scroll ends.
    */
    */
   function scrollTo( index: number, duration?: number, callback?: AnyFunction ): void {
   function scrollTo( index: number, duration?: number, callback?: AnyFunction ): void {
-    scroll( toPosition( index ), duration, false, callback );
+    scroll( toPosition( index, true ), duration, false, callback );
   }
   }
 
 
   /**
   /**
@@ -254,13 +249,7 @@ export function Controller( Splide: Splide, Components: Components, options: Opt
    * @return An end index.
    * @return An end index.
    */
    */
   function getEnd(): number {
   function getEnd(): number {
-    let end = slideCount - perPage;
-
-    if ( hasFocus() || ( isLoop && perMove ) ) {
-      end = slideCount - 1;
-    }
-
-    return max( end, 0 );
+    return max( slideCount - ( hasFocus() || ( isLoop && perMove ) ? 1 : perPage ), 0 );
   }
   }
 
 
   /**
   /**
@@ -271,11 +260,7 @@ export function Controller( Splide: Splide, Components: Components, options: Opt
    * @return A looped index.
    * @return A looped index.
    */
    */
   function loop( index: number ): number {
   function loop( index: number ): number {
-    if ( isLoop ) {
-      return isEnough() ? index % slideCount + ( index < 0 ? slideCount : 0 ) : -1;
-    }
-
-    return index;
+    return isLoop ? ( index + slideCount ) % slideCount || 0 : index;
   }
   }
 
 
   /**
   /**
@@ -295,19 +280,14 @@ export function Controller( Splide: Splide, Components: Components, options: Opt
    * @param index - An index to convert.
    * @param index - An index to convert.
    */
    */
   function toPage( index: number ): number {
   function toPage( index: number ): number {
-    if ( ! hasFocus() ) {
-      index = between( index, slideCount - perPage, slideCount - 1 ) ? slideCount - 1 : index;
-      index = floor( index / perPage );
-    }
-
-    return index;
+    return hasFocus()
+      ? index
+      : floor( ( index >= getEnd() ? slideCount - 1 : index ) / perPage );
   }
   }
 
 
   /**
   /**
    * Converts the destination position to the dest index.
    * Converts the destination position to the dest index.
    *
    *
-   * @todo
-   *
    * @param destination - A position to convert.
    * @param destination - A position to convert.
    *
    *
    * @return A dest index.
    * @return A dest index.

+ 1 - 1
src/js/components/Controller/test/toPage.test.ts

@@ -25,7 +25,7 @@ describe( 'Controller#toPage()', () => {
     expect( toPage( 3 ) ).toBe( 1 );
     expect( toPage( 3 ) ).toBe( 1 );
   } );
   } );
 
 
-  test( 'should return the slide index if the focus option is available.', () => {
+  test( 'should return the slide index as is if the focus option is available.', () => {
     const splide = init( { focus: 'center', perPage: 3 } );
     const splide = init( { focus: 'center', perPage: 3 } );
     const { toPage } = splide.Components.Controller;
     const { toPage } = splide.Components.Controller;
 
 

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

@@ -229,7 +229,7 @@ export function Drag( Splide: Splide, Components: Components, options: Options )
     const rewind      = options.rewind && options.rewindByDrag;
     const rewind      = options.rewind && options.rewindByDrag;
 
 
     if ( isFree ) {
     if ( isFree ) {
-      Controller.scrollTo( destination );
+      Controller.scroll( destination, 0, options.snap );
     } else if ( Splide.is( FADE ) ) {
     } else if ( Splide.is( FADE ) ) {
       Controller.go( orient( sign( velocity ) ) < 0 ? ( rewind ? '<' : '-' ) : ( rewind ? '>' : '+' ) );
       Controller.go( orient( sign( velocity ) ) < 0 ? ( rewind ? '<' : '-' ) : ( rewind ? '>' : '+' ) );
     } else if ( Splide.is( SLIDE ) && exceeded && rewind ) {
     } else if ( Splide.is( SLIDE ) && exceeded && rewind ) {

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

@@ -13,7 +13,7 @@ import { FADE, LOOP, SLIDE } from '../../constants/types';
 import { EventInterface } from '../../constructors';
 import { EventInterface } from '../../constructors';
 import { Splide } from '../../core/Splide/Splide';
 import { Splide } from '../../core/Splide/Splide';
 import { AnyFunction, BaseComponent, Components, Options, TransitionComponent } from '../../types';
 import { AnyFunction, BaseComponent, Components, Options, TransitionComponent } from '../../types';
-import { abs, apply, ceil, clamp, isUndefined, rect, style } from '../../utils';
+import { abs, ceil, clamp, isUndefined, rect, style } from '../../utils';
 
 
 
 
 /**
 /**
@@ -101,28 +101,16 @@ export function Move( Splide: Splide, Components: Components, options: Options )
     set( MOVING );
     set( MOVING );
     emit( EVENT_MOVE, index, prev, dest );
     emit( EVENT_MOVE, index, prev, dest );
 
 
-    Transition.start( index, apply( onTransitionEnd, position, dest, index, prev, callback ) );
-  }
+    Transition.start( index, () => {
+      set( IDLE );
+      emit( EVENT_MOVED, index, prev, dest );
 
 
-  /**
-   * Called just after the transition ends.
-   *
-   * @param from     - A position where transition starts.
-   * @param dest     - A destination index to go to, including clones'.
-   * @param prev     - A previous index.
-   * @param index    - A slide index.
-   * @param callback - Optional. A callback function invoked after transition ends.
-   */
-  function onTransitionEnd( from: number, dest: number, index: number, prev: number, callback?: AnyFunction ): void {
-    set( IDLE );
-    emit( EVENT_MOVED, index, prev, dest );
-
-    // todo can I optimize?
-    if ( options.trimSpace === 'move' && dest !== prev && from === 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();
+      }
+    } );
   }
   }
 
 
   /**
   /**

+ 4 - 1
src/js/components/Pagination/Pagination.ts

@@ -152,7 +152,10 @@ export function Pagination( Splide: Splide, Components: Components, options: Opt
       const text     = ! hasFocus() && perPage > 1 ? i18n.pageX : i18n.slideX;
       const text     = ! hasFocus() && perPage > 1 ? i18n.pageX : i18n.slideX;
 
 
       bind( button, 'click', apply( onClick, i ) );
       bind( button, 'click', apply( onClick, i ) );
-      bind( button, 'keydown', apply( onKeydown, i ) );
+
+      if ( options.paginationKeyboard ) {
+        bind( button, 'keydown', apply( onKeydown, i ) );
+      }
 
 
       setAttribute( li, ROLE, 'none' );
       setAttribute( li, ROLE, 'none' );
       setAttribute( button, ROLE, 'tab' );
       setAttribute( button, ROLE, 'tab' );

+ 21 - 21
src/js/constants/defaults.ts

@@ -10,25 +10,25 @@ import { I18N } from './i18n';
  * @since 3.0.0
  * @since 3.0.0
  */
  */
 export const DEFAULTS: Options = {
 export const DEFAULTS: Options = {
-  type             : 'slide',
-  role             : 'region',
-  speed            : 400,
-  waitForTransition: true,
-  perPage          : 1,
-  cloneStatus      : true,
-  arrows           : true,
-  pagination       : true,
-  interval         : 5000,
-  pauseOnHover     : true,
-  pauseOnFocus     : true,
-  resetProgress    : true,
-  easing           : 'cubic-bezier(0.25, 1, 0.5, 1)',
-  drag             : true,
-  direction        : 'ltr',
-  slideFocus       : true,
-  trimSpace        : true,
-  focusableNodes   : 'a, button, textarea, input, select, iframe',
-  live             : true,
-  classes          : CLASSES,
-  i18n             : I18N,
+  type              : 'slide',
+  role              : 'region',
+  speed             : 400,
+  perPage           : 1,
+  cloneStatus       : true,
+  arrows            : true,
+  pagination        : true,
+  paginationKeyboard: true,
+  interval          : 5000,
+  pauseOnHover      : true,
+  pauseOnFocus      : true,
+  resetProgress     : true,
+  easing            : 'cubic-bezier(0.25, 1, 0.5, 1)',
+  drag              : true,
+  direction         : 'ltr',
+  slideFocus        : true,
+  trimSpace         : true,
+  focusableNodes    : 'a, button, textarea, input, select, iframe',
+  live              : true,
+  classes           : CLASSES,
+  i18n              : I18N,
 };
 };

+ 13 - 2
src/js/types/options.ts

@@ -95,13 +95,19 @@ export interface Options extends ResponsiveOptions {
   preloadPages?: number;
   preloadPages?: number;
 
 
   /**
   /**
-   * Determines whether to enable keyboard shortcuts or not.
+   * Enables keyboard shortcuts for the slider control.
    * - `true` or `'global'`: Listens to the `keydown` event of the document.
    * - `true` or `'global'`: Listens to the `keydown` event of the document.
    * - 'focused': Listens to the `keydown` event of the slider root element with adding `tabindex="0"` to it.
    * - 'focused': Listens to the `keydown` event of the slider root element with adding `tabindex="0"` to it.
-   * - `false`: Disables keyboard shortcuts.
+   * - `false`: Disables keyboard shortcuts (default).
    */
    */
   keyboard?: boolean | string;
   keyboard?: boolean | string;
 
 
+  /**
+   * Enables keyboard shortcuts for the pagination, recommended by W3C.
+   * The default value is `true`.
+   */
+  paginationKeyboard?: boolean;
+
   /**
   /**
    * Enables navigation by the mouse wheel.
    * Enables navigation by the mouse wheel.
    * Set `waitForTransition` to `ture` or provide the `sleep` duration.
    * Set `waitForTransition` to `ture` or provide the `sleep` duration.
@@ -363,6 +369,11 @@ export interface ResponsiveOptions {
    */
    */
   drag?: boolean | 'free';
   drag?: boolean | 'free';
 
 
+  /**
+   * Snaps the closest slide in the drag-free mode.
+   */
+  snap?: boolean;
+
   /**
   /**
    * The required distance to start moving the slider by the touch action.
    * The required distance to start moving the slider by the touch action.
    * If you want to define the threshold for the mouse, provide an object.
    * If you want to define the threshold for the mouse, provide an object.

Деякі файли не було показано, через те що забагато файлів було змінено