Browse Source

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

NaotoshiFujita 3 năm trước cách đây
mục cha
commit
c323055b28

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 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 IDLE = 3;
 const MOVING = 4;
-const DRAGGING = 5;
-const DESTROYED = 6;
+const SCROLLING = 5;
+const DRAGGING = 6;
+const DESTROYED = 7;
 const STATES = {
   CREATED,
   MOUNTED,
   IDLE,
   MOVING,
+  SCROLLING,
   DRAGGING,
   DESTROYED
 };
@@ -581,6 +583,7 @@ const ORIENTATION_MAP = {
   paddingLeft: ["paddingTop", "paddingRight"],
   paddingRight: ["paddingBottom", "paddingLeft"],
   width: ["height"],
+  Width: ["Height"],
   left: ["top", "right"],
   right: ["bottom", "left"],
   x: ["y"],
@@ -1085,6 +1088,7 @@ function Layout(Splide2, Components2, options) {
   };
 }
 
+const MULTIPLIER = 2;
 function Clones(Splide2, Components2, options) {
   const { on, emit } = EventInterface(Splide2);
   const { Elements, Slides } = Components2;
@@ -1141,7 +1145,7 @@ function Clones(Splide2, Components2, options) {
     } else if (!clones2) {
       const fixedSize = options[resolve("fixedWidth")] && Components2.Layout.slideSize(0);
       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;
   }
@@ -1153,6 +1157,7 @@ function Clones(Splide2, Components2, options) {
 
 function Move(Splide2, Components2, options) {
   const { on, emit } = EventInterface(Splide2);
+  const { set } = Splide2.state;
   const { slideSize, getPadding, totalSize, listSize, sliderSize } = Components2.Layout;
   const { resolve, orient } = Components2.Direction;
   const { list, track } = Components2.Elements;
@@ -1162,32 +1167,29 @@ function Move(Splide2, Components2, options) {
     on([EVENT_MOUNTED, EVENT_RESIZED, EVENT_UPDATED, EVENT_REFRESH], reposition);
   }
   function reposition() {
-    if (!isBusy()) {
+    if (!Components2.Controller.isBusy()) {
       Components2.Scroll.cancel();
       jump(Splide2.index);
       emit(EVENT_REPOSITIONED);
     }
   }
   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) {
     translate(toPosition(index, true));
@@ -1195,7 +1197,7 @@ function Move(Splide2, Components2, options) {
   function translate(position, preventLoop) {
     if (!Splide2.is(FADE)) {
       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);
     }
   }
@@ -1257,8 +1259,9 @@ function Move(Splide2, Components2, options) {
   function getLimit(max) {
     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) {
     position = isUndefined(position) ? getPosition() : position;
@@ -1277,7 +1280,6 @@ function Move(Splide2, Components2, options) {
     toPosition,
     getPosition,
     getLimit,
-    isBusy,
     exceededLimit,
     reposition
   };
@@ -1286,7 +1288,7 @@ function Move(Splide2, Components2, options) {
 function Controller(Splide2, Components2, options) {
   const { on } = EventInterface(Splide2);
   const { Move } = Components2;
-  const { getPosition, getLimit } = Move;
+  const { getPosition, getLimit, toPosition } = Move;
   const { isEnough, getLength } = Components2.Slides;
   const isLoop = Splide2.is(LOOP);
   const isSlide = Splide2.is(SLIDE);
@@ -1312,24 +1314,24 @@ function Controller(Splide2, Components2, options) {
     }
   }
   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);
-      if (index > -1 && !Move.isBusy() && (allowSameIndex || index !== currIndex)) {
+      if (index > -1 && (allowSameIndex || index !== currIndex)) {
         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()));
       callback && callback();
     });
   }
+  function scrollTo(index, duration, callback) {
+    scroll(toPosition(index, true), duration, false, callback);
+  }
   function parse(control) {
     let index = currIndex;
     if (isString(control)) {
@@ -1382,27 +1384,16 @@ function Controller(Splide2, Components2, options) {
     return dest;
   }
   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) {
-    if (isLoop) {
-      return isEnough() ? index % slideCount + (index < 0 ? slideCount : 0) : -1;
-    }
-    return index;
+    return isLoop ? (index + slideCount) % slideCount || 0 : index;
   }
   function toIndex(page) {
     return clamp(hasFocus() ? page : perPage * page, 0, getEnd());
   }
   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) {
     const closest = Move.toIndex(destination);
@@ -1420,10 +1411,14 @@ function Controller(Splide2, Components2, options) {
   function hasFocus() {
     return !isUndefined(options.focus) || options.isNavigation;
   }
+  function isBusy() {
+    return Splide2.state.is([MOVING, SCROLLING]) && options.waitForTransition;
+  }
   return {
     mount,
     go,
     scroll,
+    scrollTo,
     getNext,
     getPrev,
     getAdjacent,
@@ -1433,7 +1428,8 @@ function Controller(Splide2, Components2, options) {
     toIndex,
     toPage,
     toDest,
-    hasFocus
+    hasFocus,
+    isBusy
   };
 }
 
@@ -1635,49 +1631,53 @@ const MIN_DURATION = 800;
 
 function Scroll(Splide2, Components2, options) {
   const { on, emit } = EventInterface(Splide2);
+  const { state: { set } } = Splide2;
   const { Move } = Components2;
-  const { getPosition, getLimit, exceededLimit } = Move;
+  const { getPosition, getLimit, exceededLimit, translate } = Move;
   let interval;
-  let scrollCallback;
+  let callback;
+  let friction = 1;
   function mount() {
     on(EVENT_MOVE, clear);
     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();
-    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);
     interval.start();
   }
-  function bounce(backwards) {
-    scroll(getLimit(!backwards), BOUNCE_DURATION, null, true);
-  }
-  function onScrolled() {
+  function onEnd() {
     const position = getPosition();
     const 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) {
+    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() {
     if (interval) {
@@ -1687,7 +1687,7 @@ function Scroll(Splide2, Components2, options) {
   function cancel() {
     if (interval && !interval.isPaused()) {
       clear();
-      onScrolled();
+      onEnd();
     }
   }
   function easing(t) {
@@ -1746,9 +1746,9 @@ function Drag(Splide2, Components2, options) {
       const isTouch = isTouchEvent(e);
       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);
           bind(target, POINTER_UP_EVENTS, onPointerUp, SCROLL_LISTENER_OPTIONS);
@@ -1811,7 +1811,7 @@ function Drag(Splide2, Components2, options) {
     const destination = computeDestination(velocity);
     const 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) {
@@ -2302,7 +2302,6 @@ const DEFAULTS = {
   pauseOnHover: true,
   pauseOnFocus: true,
   resetProgress: true,
-  keyboard: true,
   easing: "cubic-bezier(0.25, 1, 0.5, 1)",
   drag: true,
   direction: "ltr",

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

@@ -41,13 +41,15 @@ const CREATED = 1;
 const MOUNTED = 2;
 const IDLE = 3;
 const MOVING = 4;
-const DRAGGING = 5;
-const DESTROYED = 6;
+const SCROLLING = 5;
+const DRAGGING = 6;
+const DESTROYED = 7;
 const STATES = {
   CREATED,
   MOUNTED,
   IDLE,
   MOVING,
+  SCROLLING,
   DRAGGING,
   DESTROYED
 };
@@ -577,6 +579,7 @@ const ORIENTATION_MAP = {
   paddingLeft: ["paddingTop", "paddingRight"],
   paddingRight: ["paddingBottom", "paddingLeft"],
   width: ["height"],
+  Width: ["Height"],
   left: ["top", "right"],
   right: ["bottom", "left"],
   x: ["y"],
@@ -1081,6 +1084,7 @@ function Layout(Splide2, Components2, options) {
   };
 }
 
+const MULTIPLIER = 2;
 function Clones(Splide2, Components2, options) {
   const { on, emit } = EventInterface(Splide2);
   const { Elements, Slides } = Components2;
@@ -1137,7 +1141,7 @@ function Clones(Splide2, Components2, options) {
     } else if (!clones2) {
       const fixedSize = options[resolve("fixedWidth")] && Components2.Layout.slideSize(0);
       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;
   }
@@ -1149,6 +1153,7 @@ function Clones(Splide2, Components2, options) {
 
 function Move(Splide2, Components2, options) {
   const { on, emit } = EventInterface(Splide2);
+  const { set } = Splide2.state;
   const { slideSize, getPadding, totalSize, listSize, sliderSize } = Components2.Layout;
   const { resolve, orient } = Components2.Direction;
   const { list, track } = Components2.Elements;
@@ -1158,32 +1163,29 @@ function Move(Splide2, Components2, options) {
     on([EVENT_MOUNTED, EVENT_RESIZED, EVENT_UPDATED, EVENT_REFRESH], reposition);
   }
   function reposition() {
-    if (!isBusy()) {
+    if (!Components2.Controller.isBusy()) {
       Components2.Scroll.cancel();
       jump(Splide2.index);
       emit(EVENT_REPOSITIONED);
     }
   }
   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) {
     translate(toPosition(index, true));
@@ -1191,7 +1193,7 @@ function Move(Splide2, Components2, options) {
   function translate(position, preventLoop) {
     if (!Splide2.is(FADE)) {
       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);
     }
   }
@@ -1253,8 +1255,9 @@ function Move(Splide2, Components2, options) {
   function getLimit(max) {
     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) {
     position = isUndefined(position) ? getPosition() : position;
@@ -1273,7 +1276,6 @@ function Move(Splide2, Components2, options) {
     toPosition,
     getPosition,
     getLimit,
-    isBusy,
     exceededLimit,
     reposition
   };
@@ -1282,7 +1284,7 @@ function Move(Splide2, Components2, options) {
 function Controller(Splide2, Components2, options) {
   const { on } = EventInterface(Splide2);
   const { Move } = Components2;
-  const { getPosition, getLimit } = Move;
+  const { getPosition, getLimit, toPosition } = Move;
   const { isEnough, getLength } = Components2.Slides;
   const isLoop = Splide2.is(LOOP);
   const isSlide = Splide2.is(SLIDE);
@@ -1308,24 +1310,24 @@ function Controller(Splide2, Components2, options) {
     }
   }
   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);
-      if (index > -1 && !Move.isBusy() && (allowSameIndex || index !== currIndex)) {
+      if (index > -1 && (allowSameIndex || index !== currIndex)) {
         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()));
       callback && callback();
     });
   }
+  function scrollTo(index, duration, callback) {
+    scroll(toPosition(index, true), duration, false, callback);
+  }
   function parse(control) {
     let index = currIndex;
     if (isString(control)) {
@@ -1378,27 +1380,16 @@ function Controller(Splide2, Components2, options) {
     return dest;
   }
   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) {
-    if (isLoop) {
-      return isEnough() ? index % slideCount + (index < 0 ? slideCount : 0) : -1;
-    }
-    return index;
+    return isLoop ? (index + slideCount) % slideCount || 0 : index;
   }
   function toIndex(page) {
     return clamp(hasFocus() ? page : perPage * page, 0, getEnd());
   }
   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) {
     const closest = Move.toIndex(destination);
@@ -1416,10 +1407,14 @@ function Controller(Splide2, Components2, options) {
   function hasFocus() {
     return !isUndefined(options.focus) || options.isNavigation;
   }
+  function isBusy() {
+    return Splide2.state.is([MOVING, SCROLLING]) && options.waitForTransition;
+  }
   return {
     mount,
     go,
     scroll,
+    scrollTo,
     getNext,
     getPrev,
     getAdjacent,
@@ -1429,7 +1424,8 @@ function Controller(Splide2, Components2, options) {
     toIndex,
     toPage,
     toDest,
-    hasFocus
+    hasFocus,
+    isBusy
   };
 }
 
@@ -1631,49 +1627,53 @@ const MIN_DURATION = 800;
 
 function Scroll(Splide2, Components2, options) {
   const { on, emit } = EventInterface(Splide2);
+  const { state: { set } } = Splide2;
   const { Move } = Components2;
-  const { getPosition, getLimit, exceededLimit } = Move;
+  const { getPosition, getLimit, exceededLimit, translate } = Move;
   let interval;
-  let scrollCallback;
+  let callback;
+  let friction = 1;
   function mount() {
     on(EVENT_MOVE, clear);
     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();
-    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);
     interval.start();
   }
-  function bounce(backwards) {
-    scroll(getLimit(!backwards), BOUNCE_DURATION, null, true);
-  }
-  function onScrolled() {
+  function onEnd() {
     const position = getPosition();
     const 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) {
+    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() {
     if (interval) {
@@ -1683,7 +1683,7 @@ function Scroll(Splide2, Components2, options) {
   function cancel() {
     if (interval && !interval.isPaused()) {
       clear();
-      onScrolled();
+      onEnd();
     }
   }
   function easing(t) {
@@ -1742,9 +1742,9 @@ function Drag(Splide2, Components2, options) {
       const isTouch = isTouchEvent(e);
       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);
           bind(target, POINTER_UP_EVENTS, onPointerUp, SCROLL_LISTENER_OPTIONS);
@@ -1807,7 +1807,7 @@ function Drag(Splide2, Components2, options) {
     const destination = computeDestination(velocity);
     const 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) {
@@ -2298,7 +2298,6 @@ const DEFAULTS = {
   pauseOnHover: true,
   pauseOnFocus: true,
   resetProgress: true,
-  keyboard: true,
   easing: "cubic-bezier(0.25, 1, 0.5, 1)",
   drag: true,
   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) {
     var _EventInterface5 = EventInterface(Splide2),
         on = _EventInterface5.on,
@@ -1323,8 +1325,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       } else if (!clones2) {
         var fixedSize = options[resolve("fixedWidth")] && Components2.Layout.slideSize(0);
         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;
@@ -1379,18 +1380,16 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
 
       set(MOVING);
       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) {
@@ -1547,16 +1546,11 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     function go(control, allowSameIndex, callback) {
       if (!isBusy()) {
         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) {
-      scroll(toPosition(index), duration, false, callback);
+      scroll(toPosition(index, true), duration, false, callback);
     }
 
     function parse(control) {
@@ -1636,21 +1630,11 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     }
 
     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) {
-      if (isLoop) {
-        return isEnough() ? index % slideCount + (index < 0 ? slideCount : 0) : -1;
-      }
-
-      return index;
+      return isLoop ? (index + slideCount) % slideCount || 0 : index;
     }
 
     function toIndex(page) {
@@ -1658,12 +1642,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     }
 
     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) {
@@ -2181,7 +2160,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       var rewind = options.rewind && options.rewindByDrag;
 
       if (isFree) {
-        Controller.scrollTo(destination);
+        Controller.scroll(destination, 0, options.snap);
       } else if (Splide2.is(FADE)) {
         Controller.go(orient(sign(velocity)) < 0 ? rewind ? "<" : "-" : 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;
         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(button, ROLE, "tab");
         setAttribute(button, ARIA_CONTROLS, controls.join(" "));
@@ -2788,11 +2771,11 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     type: "slide",
     role: "region",
     speed: 400,
-    waitForTransition: true,
     perPage: 1,
     cloneStatus: true,
     arrows: true,
     pagination: true,
+    paginationKeyboard: true,
     interval: 5e3,
     pauseOnHover: true,
     pauseOnFocus: true,

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 0 - 0
dist/js/splide.min.js


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


Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 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;
     getPosition(): number;
     getLimit(max: boolean): number;
-    isBusy(): boolean;
     exceededLimit(max?: boolean | undefined, position?: number): boolean;
     /** @internal */
     reposition(): void;
@@ -108,7 +107,8 @@ interface MoveComponent extends BaseComponent {
  */
 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;
@@ -119,6 +119,7 @@ interface ControllerComponent extends BaseComponent {
     toPage(index: number): number;
     toDest(position: number): number;
     hasFocus(): boolean;
+    isBusy(): boolean;
 }
 
 /**
@@ -158,7 +159,7 @@ interface CoverComponent extends BaseComponent {
  * @since 3.0.0
  */
 interface ScrollComponent extends BaseComponent {
-    scroll(position: number, duration?: number, callback?: AnyFunction): void;
+    scroll(position: number, duration?: number, snap?: boolean, callback?: AnyFunction): void;
     cancel(): void;
 }
 
@@ -916,6 +917,7 @@ declare class Splide {
         MOUNTED: number;
         IDLE: number;
         MOVING: number;
+        SCROLLING: number;
         DRAGGING: 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 {
 }
 
+/**
+ * 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.
  *
@@ -70,8 +77,6 @@ export function Clones( Splide: Splide, Components: Components, options: Options
 
   /**
    * Observes the required clone count and refreshes the slider if necessary.
-   *
-   * @todo
    */
   function observe(): void {
     if ( cloneCount < computeCloneCount() ) {
@@ -132,8 +137,7 @@ export function Clones( Splide: Splide, Components: Components, options: Options
     } else if ( ! clones ) {
       const fixedSize  = options[ resolve( 'fixedWidth' ) ] && Components.Layout.slideSize( 0 );
       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;

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

@@ -1,5 +1,6 @@
 import { CLASS_CLONE } from '../../../constants/classes';
 import { init } from '../../../test';
+import { MULTIPLIER } from '../Clones';
 
 
 describe( 'Clones', () => {
@@ -13,22 +14,23 @@ describe( 'Clones', () => {
     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 clones = splide.root.getElementsByClassName( CLASS_CLONE );
     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.', () => {
-    const splide = init( { type: 'loop', flickMaxPages: 1, perPage: 3 } );
+    const splide = init( { type: 'loop', perPage: 3 } );
     const clones = splide.root.getElementsByClassName( CLASS_CLONE );
     const { perPage } = splide.options;
 
-    expect( clones.length ).toBe( perPage * 2 );
+    expect( clones.length / 2 ).toBe( perPage * MULTIPLIER );
   } );
 
   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 {
     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.
    */
   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.
    */
   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.
    */
   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.
    */
   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.
    *
-   * @todo
-   *
    * @param destination - A position to convert.
    *
    * @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 );
   } );
 
-  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 { 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;
 
     if ( isFree ) {
-      Controller.scrollTo( destination );
+      Controller.scroll( destination, 0, options.snap );
     } else if ( Splide.is( FADE ) ) {
       Controller.go( orient( sign( velocity ) ) < 0 ? ( rewind ? '<' : '-' ) : ( 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 { Splide } from '../../core/Splide/Splide';
 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 );
     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;
 
       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( button, ROLE, 'tab' );

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

@@ -10,25 +10,25 @@ import { I18N } from './i18n';
  * @since 3.0.0
  */
 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;
 
   /**
-   * 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.
    * - '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;
 
+  /**
+   * Enables keyboard shortcuts for the pagination, recommended by W3C.
+   * The default value is `true`.
+   */
+  paginationKeyboard?: boolean;
+
   /**
    * Enables navigation by the mouse wheel.
    * Set `waitForTransition` to `ture` or provide the `sleep` duration.
@@ -363,6 +369,11 @@ export interface ResponsiveOptions {
    */
   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.
    * If you want to define the threshold for the mouse, provide an object.

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác