瀏覽代碼

Create the Motion component.

NaotoshiFujita 3 年之前
父節點
當前提交
e50fae0e20

文件差異過大導致無法顯示
+ 0 - 0
dist/js/splide-renderer.min.js


+ 115 - 86
dist/js/splide.js

@@ -159,15 +159,17 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     return object;
   }
 
-  function merge(object, source) {
-    forOwn(source, function (value, key) {
-      if (isArray(value)) {
-        object[key] = value.slice();
-      } else if (isObject(value)) {
-        object[key] = merge(isObject(object[key]) ? object[key] : {}, value);
-      } else {
-        object[key] = value;
-      }
+  function merge(object) {
+    slice(arguments).forEach(function (source) {
+      forOwn(source, function (value, key) {
+        if (isArray(value)) {
+          object[key] = value.slice();
+        } else if (isObject(value)) {
+          object[key] = merge(isObject(object[key]) ? object[key] : {}, value);
+        } else {
+          object[key] = value;
+        }
+      });
     });
     return object;
   }
@@ -449,7 +451,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
   var EVENT_AUTOPLAY_PAUSE = "autoplay:pause";
   var EVENT_LAZYLOAD_LOADED = "lazyload:loaded";
 
-  function EventInterface(Splide2) {
+  function EventInterface(Splide2, manual) {
     var event = Splide2.event;
     var key = {};
     var listeners = [];
@@ -464,8 +466,10 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
 
     function bind(targets, events, callback, options) {
       forEachEvent(targets, events, function (target, event2) {
-        listeners.push([target, event2, callback, options]);
-        target.addEventListener(event2, callback, options);
+        var isEventTarget = ("addEventListener" in target);
+        var remover = isEventTarget ? target.removeEventListener.bind(target, event2, callback, options) : target["removeListener"].bind(target, callback);
+        isEventTarget ? target.addEventListener(event2, callback, options) : target["addListener"](callback);
+        listeners.push([target, event2, callback, remover]);
       });
     }
 
@@ -473,7 +477,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       forEachEvent(targets, events, function (target, event2) {
         listeners = listeners.filter(function (listener) {
           if (listener[0] === target && listener[1] === event2 && (!callback || listener[2] === callback)) {
-            target.removeEventListener(event2, listener[2], listener[3]);
+            listener[3]();
             return false;
           }
 
@@ -492,12 +496,12 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
 
     function destroy() {
       listeners = listeners.filter(function (data) {
-        return unbind(data[0], data[1]);
+        data[3]();
       });
       event.offBy(key);
     }
 
-    event.on(EVENT_DESTROY, destroy, key);
+    !manual && event.on(EVENT_DESTROY, destroy, key);
     return {
       on: on,
       off: off,
@@ -609,12 +613,12 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     var interval;
 
     function throttled() {
-      var _arguments2 = arguments,
-          _this = this;
+      var _this = this;
 
       if (!interval) {
+        var args = slice(arguments);
         interval = RequestInterval(duration || 0, function () {
-          func.apply(_this, _arguments2);
+          func.apply(_this, args);
           interval = null;
         }, null, 1);
         interval.start();
@@ -625,68 +629,66 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
   }
 
   function Options(Splide2, Components2, options) {
-    var throttledObserve = Throttle(observe);
-    var initialOptions;
+    var event = EventInterface(Splide2, true);
+    var breakpoints = options.breakpoints || {};
+    var userOptions = merge({}, options);
+    var fixedOptions = {};
     var points;
-    var currPoint;
 
     function setup() {
-      initialOptions = merge({}, options);
-      var breakpoints = options.breakpoints;
-
-      if (breakpoints) {
-        var isMin = options.mediaQuery === "min";
-        points = Object.keys(breakpoints).sort(function (n, m) {
-          return isMin ? +m - +n : +n - +m;
-        }).map(function (point) {
-          return [point, matchMedia("(" + (isMin ? "min" : "max") + "-width:" + point + "px)")];
-        });
-        observe();
-      }
-    }
-
-    function mount() {
-      if (points) {
-        addEventListener("resize", throttledObserve);
-      }
+      var isMin = options.mediaQuery === "min";
+      points = Object.keys(breakpoints).sort(function (n, m) {
+        return isMin ? +m - +n : +n - +m;
+      }).map(function (point) {
+        var queryList = matchMedia("(" + (isMin ? "min" : "max") + "-width:" + point + "px)");
+        event.bind(queryList, "change", check);
+        return [point, queryList];
+      });
+      check();
     }
 
     function destroy(completely) {
       if (completely) {
-        removeEventListener("resize", throttledObserve);
-      }
-    }
-
-    function observe() {
-      var item = find(points, function (item2) {
-        return item2[1].matches;
-      }) || [];
-
-      if (item[0] !== currPoint) {
-        onMatch(currPoint = item[0]);
+        event.destroy();
       }
     }
 
-    function onMatch(point) {
-      var newOptions = options.breakpoints[point] || initialOptions;
+    function check() {
+      var point = (find(points, function (item) {
+        return item[1].matches;
+      }) || [])[0];
+      var newOptions = merge({}, breakpoints[point] || userOptions, fixedOptions);
 
       if (newOptions.destroy) {
-        Splide2.options = initialOptions;
+        Splide2.options = userOptions;
         Splide2.destroy(newOptions.destroy === "completely");
       } else {
         if (Splide2.state.is(DESTROYED)) {
           destroy(true);
           Splide2.mount();
+        } else {
+          Splide2.options = newOptions;
+        }
+      }
+    }
+
+    function fix(key, value) {
+      if (fixedOptions[key] !== value) {
+        if (isUndefined(value)) {
+          delete fixedOptions[key];
+        } else {
+          fixedOptions[key] = value;
         }
 
-        Splide2.options = newOptions;
+        check();
       }
     }
 
     return {
       setup: setup,
-      mount: mount,
-      destroy: destroy
+      mount: noop,
+      destroy: destroy,
+      fix: fix
     };
   }
 
@@ -1181,7 +1183,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
 
     function mount() {
       init();
-      bind(window, "resize load", Throttle(emit.bind(this, EVENT_RESIZE)));
+      bind(window, "resize load", Throttle(apply(emit, EVENT_RESIZE)));
       on([EVENT_UPDATED, EVENT_REFRESH], init);
       on(EVENT_RESIZE, resize);
     }
@@ -2668,9 +2670,40 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     };
   }
 
-  function Live(Splide2, Components2, options) {
+  function Wheel(Splide2, Components2, options) {
     var _EventInterface16 = EventInterface(Splide2),
-        on = _EventInterface16.on;
+        bind = _EventInterface16.bind;
+
+    function mount() {
+      if (options.wheel) {
+        bind(Components2.Elements.track, "wheel", onWheel, SCROLL_LISTENER_OPTIONS);
+      }
+    }
+
+    function onWheel(e) {
+      if (e.cancelable) {
+        var deltaY = e.deltaY;
+
+        if (deltaY) {
+          var backwards = deltaY < 0;
+          Splide2.go(backwards ? "<" : ">");
+          shouldPrevent(backwards) && prevent(e);
+        }
+      }
+    }
+
+    function shouldPrevent(backwards) {
+      return !options.releaseWheel || Splide2.state.is(MOVING) || Components2.Controller.getAdjacent(backwards) !== -1;
+    }
+
+    return {
+      mount: mount
+    };
+  }
+
+  function Live(Splide2, Components2, options) {
+    var _EventInterface17 = EventInterface(Splide2),
+        on = _EventInterface17.on;
 
     var list = Components2.Elements.list;
     var live = options.live;
@@ -2696,34 +2729,30 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     };
   }
 
-  function Wheel(Splide2, Components2, options) {
-    var _EventInterface17 = EventInterface(Splide2),
-        bind = _EventInterface17.bind;
+  function Motion(Splide2, Components2, options) {
+    var _EventInterface18 = EventInterface(Splide2),
+        bind = _EventInterface18.bind;
+
+    var query = matchMedia("(prefers-reduced-motion:reduce)");
 
     function mount() {
-      if (options.wheel) {
-        bind(Components2.Elements.track, "wheel", onWheel, SCROLL_LISTENER_OPTIONS);
-      }
+      bind(query, "change", check);
+      check();
     }
 
-    function onWheel(e) {
-      if (e.cancelable) {
-        var deltaY = e.deltaY;
-
-        if (deltaY) {
-          var backwards = deltaY < 0;
-          Splide2.go(backwards ? "<" : ">");
-          shouldPrevent(backwards) && prevent(e);
-        }
-      }
+    function check() {
+      var reduced = isReduced();
+      Components2.Options.fix("speed", reduced ? 0 : void 0);
+      reduced && Components2.Autoplay.pause();
     }
 
-    function shouldPrevent(backwards) {
-      return !options.releaseWheel || Splide2.state.is(MOVING) || Components2.Controller.getAdjacent(backwards) !== -1;
+    function isReduced() {
+      return query.matches;
     }
 
     return {
-      mount: mount
+      mount: mount,
+      isReduced: isReduced
     };
   }
 
@@ -2746,8 +2775,9 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
     LazyLoad: LazyLoad,
     Pagination: Pagination,
     Sync: Sync,
+    Wheel: Wheel,
     Live: Live,
-    Wheel: Wheel
+    Motion: Motion
   });
   var I18N = {
     prev: "Previous slide",
@@ -2787,8 +2817,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
   };
 
   function Fade(Splide2, Components2, options) {
-    var _EventInterface18 = EventInterface(Splide2),
-        on = _EventInterface18.on;
+    var _EventInterface19 = EventInterface(Splide2),
+        on = _EventInterface19.on;
 
     function mount() {
       on([EVENT_MOUNTED, EVENT_REFRESH], function () {
@@ -2815,8 +2845,8 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
   }
 
   function Slide(Splide2, Components2, options) {
-    var _EventInterface19 = EventInterface(Splide2),
-        bind = _EventInterface19.bind;
+    var _EventInterface20 = EventInterface(Splide2),
+        bind = _EventInterface20.bind;
 
     var Move = Components2.Move,
         Controller = Components2.Controller;
@@ -2888,13 +2918,12 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
       var root = isString(target) ? query(document, target) : target;
       assert(root, root + " is invalid.");
       this.root = root;
-      merge(DEFAULTS, _Splide.defaults);
-      merge(merge(this._options, DEFAULTS), options || {});
+      merge(this._options, DEFAULTS, _Splide.defaults, options || {});
 
       try {
         merge(this._options, JSON.parse(getAttribute(root, DATA_ATTRIBUTE)));
       } catch (e) {
-        assert(false, e.message);
+        assert(false, "Invalid JSON");
       }
     }
 

文件差異過大導致無法顯示
+ 0 - 0
dist/js/splide.min.js


二進制
dist/js/splide.min.js.gz


文件差異過大導致無法顯示
+ 0 - 0
dist/js/splide.min.js.map


+ 2 - 2
src/js/components/Layout/Layout.ts

@@ -3,7 +3,7 @@ import { EVENT_REFRESH, EVENT_RESIZE, EVENT_RESIZED, EVENT_UPDATED } from '../..
 import { EventInterface, Throttle } from '../../constructors';
 import { Splide } from '../../core/Splide/Splide';
 import { BaseComponent, Components, Options } from '../../types';
-import { abs, assert, isObject, rect, style, unit } from '../../utils';
+import { abs, apply, assert, isObject, rect, style, unit } from '../../utils';
 
 
 /**
@@ -52,7 +52,7 @@ export function Layout( Splide: Splide, Components: Components, options: Options
    */
   function mount(): void {
     init();
-    bind( window, 'resize load', Throttle( emit.bind( this, EVENT_RESIZE ) ) );
+    bind( window, 'resize load', Throttle( apply( emit, EVENT_RESIZE ) ) );
     on( [ EVENT_UPDATED, EVENT_REFRESH ], init );
     on( EVENT_RESIZE, resize );
   }

+ 62 - 0
src/js/components/Motion/Motion.ts

@@ -0,0 +1,62 @@
+import { EventInterface } from '../../constructors';
+import { Splide } from '../../core/Splide/Splide';
+import { BaseComponent, Components, Options } from '../../types';
+
+
+/**
+ * The interface for the Motion component.
+ *
+ * @since 3.7.0
+ */
+export interface MotionComponent extends BaseComponent {
+  isReduced(): boolean;
+}
+
+/**
+ * The component to reduce non-essential motion if the user requests it.
+ * This component does not work in IE since it does not support `prefers-reduced-motion`.
+ *
+ * @link https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion
+ *
+ * @since 3.7.0
+ *
+ * @param Splide     - A Splide instance.
+ * @param Components - A collection of components.
+ * @param options    - Options.
+ *
+ * @return A Motion component object.
+ */
+export function Motion( Splide: Splide, Components: Components, options: Options ): MotionComponent {
+  const { bind } = EventInterface( Splide );
+  const query = matchMedia( '(prefers-reduced-motion:reduce)' );
+
+  /**
+   * Called when the component is mounted.
+   * The event handler will never be fired on IE since it does not support `prefers-reduced-motion`.
+   */
+  function mount(): void {
+    bind( query, 'change', check );
+    check();
+  }
+
+  /**
+   * Checks the query and updates the slider if necessary.
+   */
+  function check(): void {
+    const reduced = isReduced();
+    Components.Options.fix( 'speed', reduced ? 0 : undefined );
+    reduced && Components.Autoplay.pause();
+  }
+
+  /**
+   * Checks if the motion should be reduced or not.
+   */
+  function isReduced(): boolean {
+    return query.matches;
+  }
+
+  return {
+    mount,
+    isReduced,
+  };
+}

+ 45 - 54
src/js/components/Options/Options.ts

@@ -1,8 +1,8 @@
 import { DESTROYED } from '../../constants/states';
-import { Throttle } from '../../constructors';
+import { EventInterface } from '../../constructors';
 import { Splide } from '../../core/Splide/Splide';
 import { BaseComponent, Components, Options } from '../../types';
-import { find, merge } from '../../utils';
+import { find, isUndefined, merge, noop } from '../../utils';
 
 
 /**
@@ -11,6 +11,7 @@ import { find, merge } from '../../utils';
  * @since 3.0.0
  */
 export interface OptionsComponent extends BaseComponent {
+  fix<K extends keyof Options>( key: K, value?: Options[ K ] ): void;
 }
 
 /**
@@ -25,55 +26,39 @@ export interface OptionsComponent extends BaseComponent {
  * @return An Options component object.
  */
 export function Options( Splide: Splide, Components: Components, options: Options ): OptionsComponent {
-  /**
-   * The throttled `observe` function.
-   */
-  const throttledObserve = Throttle( observe );
+  const event = EventInterface( Splide, true );
+  const breakpoints = options.breakpoints || {};
 
   /**
    * Keeps the initial options to apply when no matched query exists.
    */
-  let initialOptions: Options;
+  const userOptions: Options = merge( {}, options );
 
   /**
-   * Stores breakpoints with the MediaQueryList object.
+   * Stores fixed options.
    */
-  let points: [ string, MediaQueryList ][];
+  const fixedOptions: Options = {};
 
   /**
-   * Holds the current breakpoint.
+   * Stores breakpoints with the MediaQueryList object.
    */
-  let currPoint: string | undefined;
+  let points: [ string, MediaQueryList ][];
 
   /**
    * Called when the component is constructed.
    */
   function setup(): void {
-    initialOptions = merge( {}, options );
-
-    const { breakpoints } = options;
+    const isMin = options.mediaQuery === 'min';
 
-    if ( breakpoints ) {
-      const isMin = options.mediaQuery === 'min';
+    points = Object.keys( breakpoints )
+      .sort( ( n, m ) => isMin ? +m - +n : +n - +m )
+      .map( point => {
+        const queryList = matchMedia( `(${ isMin ? 'min' : 'max' }-width:${ point }px)` );
+        event.bind( queryList, 'change', check );
+        return [ point, queryList ];
+      } );
 
-      points = Object.keys( breakpoints )
-        .sort( ( n, m ) => isMin ? +m - +n : +n - +m )
-        .map( point => [
-          point,
-          matchMedia( `(${ isMin ? 'min' : 'max' }-width:${ point }px)` ),
-        ] );
-
-      observe();
-    }
-  }
-
-  /**
-   * Called when the component is mounted.
-   */
-  function mount(): void {
-    if ( points ) {
-      addEventListener( 'resize', throttledObserve );
-    }
+    check();
   }
 
   /**
@@ -83,46 +68,52 @@ export function Options( Splide: Splide, Components: Components, options: Option
    */
   function destroy( completely: boolean ): void {
     if ( completely ) {
-      removeEventListener( 'resize', throttledObserve );
+      event.destroy();
     }
   }
 
   /**
    * Observes breakpoints.
-   * The `currPoint` may be `undefined`.
    */
-  function observe(): void {
-    const item = find( points, item => item[ 1 ].matches ) || [];
-
-    if ( item[ 0 ] !== currPoint ) {
-      onMatch( ( currPoint = item[ 0 ] ) );
-    }
-  }
-
-  /**
-   * Called when the media query matches breakpoints.
-   *
-   * @param point - A matched point, or `undefined` that means no breakpoint matches a media query.
-   */
-  function onMatch( point: string | undefined ): void {
-    const newOptions = options.breakpoints[ point ] || initialOptions;
+  function check(): void {
+    const point      = ( find( points, item => item[ 1 ].matches ) || [] )[ 0 ];
+    const newOptions = merge( {}, breakpoints[ point ] || userOptions, fixedOptions );
 
     if ( newOptions.destroy ) {
-      Splide.options = initialOptions;
+      Splide.options = userOptions;
       Splide.destroy( newOptions.destroy === 'completely' );
     } else {
       if ( Splide.state.is( DESTROYED ) ) {
         destroy( true );
         Splide.mount();
+      } else {
+        Splide.options = newOptions;
+      }
+    }
+  }
+
+  /**
+   * Fixes options to prevent breakpoints from overwriting them.
+   *
+   * @param key   - A key.
+   * @param value - Optional. A value to fix. If omitted, the value will be restored.
+   */
+  function fix<K extends keyof Options>( key: K, value?: Options[ K ] ): void {
+    if ( fixedOptions[ key ] !== value ) {
+      if ( isUndefined( value ) ) {
+        delete fixedOptions[ key ];
+      } else {
+        fixedOptions[ key ] = value;
       }
 
-      Splide.options = newOptions;
+      check();
     }
   }
 
   return {
     setup,
-    mount,
+    mount: noop,
     destroy,
+    fix,
   };
 }

+ 2 - 1
src/js/components/index.ts

@@ -15,5 +15,6 @@ export { Keyboard }   from './Keyboard/Keyboard';
 export { LazyLoad }   from './LazyLoad/LazyLoad';
 export { Pagination } from './Pagination/Pagination';
 export { Sync }       from './Sync/Sync';
-export { Live }       from './Live/Live';
 export { Wheel }      from './Wheel/Wheel';
+export { Live }       from './Live/Live';
+export { Motion }     from './Motion/Motion';

+ 1 - 0
src/js/components/types.ts

@@ -18,5 +18,6 @@ export type { PaginationComponent } from './Pagination/Pagination';
 export type { SyncComponent }       from './Sync/Sync';
 export type { WheelComponent }      from './Wheel/Wheel';
 export type { LiveComponent }       from './Live/Live';
+export type { MotionComponent }     from './Motion/Motion';
 
 export type { PaginationData, PaginationItem } from './Pagination/Pagination';

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

@@ -31,4 +31,4 @@ export const DEFAULTS: Options = {
   focusableNodes   : 'a, button, textarea, input, select, iframe',
   classes          : CLASSES,
   i18n             : I18N,
-};
+};

+ 27 - 33
src/js/constructors/EventInterface/EventInterface.ts

@@ -5,6 +5,13 @@ import { apply, forEach } from '../../utils';
 import { EventBusCallback } from '../EventBus/EventBus';
 
 
+/**
+ * The type for an EventTarget or an array with EventTarget objects.
+ *
+ * @since 3.7.0
+ */
+type EventTargets = EventTarget | EventTarget[];
+
 /**
  * The interface for the EventInterface object.
  *
@@ -16,37 +23,22 @@ export interface EventInterfaceObject {
   off<K extends keyof EventMap>( events: K | K[] | string | string[] ): void;
   emit<K extends keyof EventMap>( event: K, ...args: Parameters<EventMap[ K ]> ): void
   emit( event: string, ...args: any[] ): void;
-  bind(
-    target: Element | Window | Document | Array<Element | Window | Document>,
-    events: string,
-    callback: AnyFunction,
-    options?: AddEventListenerOptions
-  ): void
-  unbind(
-    target: Element | Window | Document | Array<Element | Window | Document>,
-    events: string,
-    callback?: AnyFunction,
-  ): void;
+  bind( target: EventTargets, events: string, callback: AnyFunction, options?: AddEventListenerOptions ): void
+  unbind( target: EventTarget | EventTarget[], events: string, callback?: AnyFunction ): void;
   destroy(): void;
 }
 
-/**
- * The type for event targets.
- *
- * @since 3.0.0
- */
-type EventTarget = Element | Window | Document;
-
 /**
  * The function that provides interface for internal and native events.
  *
  * @since 3.0.0
  *
  * @param Splide - A Splide instance.
+ * @param manual - Optional. Whether to destroy the interface manually or not.
  *
  * @return A collection of interface functions.
  */
-export function EventInterface( Splide: Splide ): EventInterfaceObject {
+export function EventInterface( Splide: Splide, manual?: boolean ): EventInterfaceObject {
   /**
    * Holds the event object.
    */
@@ -60,7 +52,7 @@ export function EventInterface( Splide: Splide ): EventInterfaceObject {
   /**
    * Stores all handlers that listen to native events.
    */
-  let listeners: [ EventTarget, string, AnyFunction, AddEventListenerOptions? ][] = [];
+  let listeners: [ EventTarget, string, AnyFunction, () => void ][] = [];
 
   /**
    * Registers an event handler with an unique key.
@@ -87,21 +79,23 @@ export function EventInterface( Splide: Splide ): EventInterfaceObject {
   /**
    * Listens to native events.
    * Splide#destory() will remove all registered listeners.
+   * In IE and Safari, mediaQueryList does not inherit EventTarget,
+   * and only supports deprecated `addListener` and `removeListener`.
    *
    * @param targets  - A target element, the window object or the document object.
    * @param events   - An event or events to listen to.
    * @param callback - A callback function.
    * @param options  - Optional. The options to pass to the `addEventListener` function.
    */
-  function bind(
-    targets: EventTarget | EventTarget[],
-    events: string,
-    callback: AnyFunction,
-    options?: AddEventListenerOptions
-  ): void {
+  function bind( targets: EventTargets, events: string, callback: AnyFunction, options?: AddEventListenerOptions ): void {
     forEachEvent( targets, events, ( target, event ) => {
-      listeners.push( [ target, event, callback, options ] );
-      target.addEventListener( event, callback, options );
+      const isEventTarget = 'addEventListener' in target;
+      const remover = isEventTarget
+        ? target.removeEventListener.bind( target, event, callback, options )
+        : target[ 'removeListener' ].bind( target, callback );
+
+      isEventTarget ? target.addEventListener( event, callback, options ) : target[ 'addListener' ]( callback );
+      listeners.push( [ target, event, callback, remover ] );
     } );
   }
 
@@ -112,11 +106,11 @@ export function EventInterface( Splide: Splide ): EventInterfaceObject {
    * @param events   - An event name or names to remove.
    * @param callback - Optional. Specify the callback to remove.
    */
-  function unbind( targets: EventTarget | EventTarget[], events: string, callback?: AnyFunction ): void {
+  function unbind( targets: EventTargets, events: string, callback?: AnyFunction ): void {
     forEachEvent( targets, events, ( target, event ) => {
       listeners = listeners.filter( listener => {
         if ( listener[ 0 ] === target && listener[ 1 ] === event && ( ! callback || listener[ 2 ] === callback ) ) {
-          target.removeEventListener( event, listener[ 2 ], listener[ 3 ] );
+          listener[ 3 ]();
           return false;
         }
 
@@ -133,7 +127,7 @@ export function EventInterface( Splide: Splide ): EventInterfaceObject {
    * @param iteratee - An iteratee function.
    */
   function forEachEvent(
-    targets: EventTarget | EventTarget[],
+    targets: EventTargets,
     events: string,
     iteratee: ( target: EventTarget, event: string ) => void
   ): void {
@@ -148,14 +142,14 @@ export function EventInterface( Splide: Splide ): EventInterfaceObject {
    * Removes all listeners.
    */
   function destroy(): void {
-    listeners = listeners.filter( data => unbind( data[ 0 ], data[ 1 ] ) );
+    listeners = listeners.filter( data => { data[ 3 ]() } );
     event.offBy( key );
   }
 
   /**
    * Invokes destroy when the slider is destroyed.
    */
-  event.on( EVENT_DESTROY, destroy, key );
+  ! manual && event.on( EVENT_DESTROY, destroy, key );
 
   return {
     on,

+ 4 - 1
src/js/constructors/Throttle/Throttle.ts

@@ -1,4 +1,5 @@
 import { AnyFunction } from '../../types';
+import { slice } from '../../utils';
 import { RequestInterval, RequestIntervalInterface } from '../RequestInterval/RequestInterval';
 
 
@@ -27,9 +28,11 @@ export function Throttle<F extends AnyFunction>(
 
   function throttled( this: ThisParameterType<F> ): void {
     if ( ! interval ) {
+      const args = slice( arguments );
+
       interval = RequestInterval( duration || 0, () => {
         // eslint-disable-next-line prefer-rest-params
-        func.apply( this, arguments );
+        func.apply( this, args );
         interval = null;
       }, null, 1 );
 

+ 2 - 3
src/js/core/Splide/Splide.ts

@@ -86,13 +86,12 @@ export class Splide {
 
     this.root = root;
 
-    merge( DEFAULTS, Splide.defaults );
-    merge( merge( this._options, DEFAULTS ), options || {} );
+    merge( this._options, DEFAULTS, Splide.defaults, options || {} );
 
     try {
       merge( this._options, JSON.parse( getAttribute( root, DATA_ATTRIBUTE ) ) );
     } catch ( e ) {
-      assert( false, e.message );
+      assert( false, 'Invalid JSON' );
     }
   }
 

+ 1 - 1
src/js/test/utils/utils.ts

@@ -156,7 +156,7 @@ export function fire(
     delete data.timeStamp;
   }
 
-  target.dispatchEvent( assign( e, data ) );
+  target.dispatchEvent( Object.assign( e, data ) );
   return e;
 }
 

+ 2 - 0
src/js/types/components.ts

@@ -10,6 +10,7 @@ import { KeyboardComponent } from '../components/Keyboard/Keyboard';
 import { LayoutComponent } from '../components/Layout/Layout';
 import { LazyLoadComponent } from '../components/LazyLoad/LazyLoad';
 import { LiveComponent } from '../components/Live/Live';
+import { MotionComponent } from '../components/Motion/Motion';
 import { MoveComponent } from '../components/Move/Move';
 import { OptionsComponent } from '../components/Options/Options';
 import { PaginationComponent } from '../components/Pagination/Pagination';
@@ -46,5 +47,6 @@ export interface Components {
   Sync: SyncComponent;
   Wheel: WheelComponent;
   Live: LiveComponent;
+  Motion: MotionComponent;
   Transition: TransitionComponent;
 }

+ 16 - 0
src/js/types/utils.ts

@@ -5,6 +5,13 @@
  */
 export type Cast<T, U> = T extends U ? T : U;
 
+/**
+ * Makes the T easy to read.
+ */
+export type Resolve<T> = {
+  [ K in keyof T ]: T[ K ];
+} & unknown;
+
 /**
  * Pushes U to tuple T.
  *
@@ -12,6 +19,15 @@ export type Cast<T, U> = T extends U ? T : U;
  */
 export type Push<T extends any[], U = any> = [ ...T, U ];
 
+/**
+ * Returns the first type of the tuple.
+ *
+ * @internal
+ */
+export type Head<T extends any[]> = ( ( ...args: T ) => any ) extends ( arg: infer A, ...args: any[] ) => any
+  ? A
+  : never;
+
 /**
  * Removes the first type from the tuple T.
  *

+ 2 - 2
src/js/utils/dom/create/create.ts

@@ -6,13 +6,13 @@ import { setAttribute } from '../setAttribute/setAttribute';
 
 export function create<K extends keyof HTMLElementTagNameMap>(
   tag: K,
-  attrs?: Record<string, string | number | boolean> | string | string[],
+  attrs?: Record<string, string | number | boolean> | string,
   parent?: HTMLElement
 ): HTMLElementTagNameMap[ K ];
 
 export function create(
   tag: string,
-  attrs?: Record<string, string | number | boolean> | string | string[],
+  attrs?: Record<string, string | number | boolean> | string,
   parent?: HTMLElement
 ): HTMLElement;
 

+ 1 - 1
src/js/utils/dom/getAttribute/getAttribute.ts

@@ -4,6 +4,6 @@
  * @param elm  - An element.
  * @param attr - An attribute to get.
  */
-export function getAttribute( elm: Element, attr: string ): string {
+export function getAttribute( elm: Element, attr: string ): string | null {
   return elm.getAttribute( attr );
 }

+ 12 - 1
src/js/utils/object/assign/assign.test.ts

@@ -5,8 +5,19 @@ describe( 'assign', () => {
   test( 'can assign own enumerable properties of the source object to the target.', () => {
     const object = { a: 1, b: '2' };
     const source = { a: 2, c: true };
+    const assigned = assign( object, source );
+
+    expect( assigned ).toStrictEqual( { a: 2, b: '2', c: true } );
+  } );
+
+  test( 'can assign properties of multiple sources to the target.', () => {
+    const object  = { a: 1, b: '2' };
+    const source1 = { a: 2, c: true };
+    const source2 = { d: 3, e: '3' };
+    const source3 = { e: Infinity };
+    const assigned = assign( object, source1, source2, source3 );
 
-    expect( assign( object, source ) ).toStrictEqual( { a: 2, b: '2', c: true } );
+    expect( assigned ).toStrictEqual( { a: 2, b: '2', c: true, d: 3, e: Infinity } );
   } );
 
   test( 'should assign a nested object as a reference.', () => {

+ 19 - 11
src/js/utils/object/assign/assign.ts

@@ -1,9 +1,10 @@
+import { Cast, Head, Push, Resolve, Shift } from '../../../types';
 import { slice } from '../../arrayLike';
 import { forOwn } from '../forOwn/forOwn';
 
 
 /**
- * Assign U to T.
+ * Assigns U to T.
  *
  * @typeParam T - An object to assign to.
  * @typeParam U - An object to assign.
@@ -12,18 +13,25 @@ import { forOwn } from '../forOwn/forOwn';
  */
 export type Assign<T, U> = Omit<T, keyof U> & U;
 
-export function assign<T extends object>( object: T ): T;
-
-// There is a way to type arguments recursively, but these fixed definitions are enough for this project.
-export function assign<T extends object, U extends object>( object: T, source: U ): Assign<T, U>;
+/**
+ * Recursively assigns U[] to T.
+ *
+ * @typeParam T - An object to assign to.
+ * @typeParam U - A tuple contains objects.
+ *
+ * @return An assigned object type.
+ */
+export type Assigned<T extends object, U extends object[], N extends number, C extends any[] = []> = {
+  0: T,
+  1: Assigned<Assign<T, Head<U>>, Shift<U>, N, Push<C>>,
+}[ C['length'] extends N ? 0 : 1 ] extends infer A ? Cast<A, any> : never;
 
-export function assign<T extends object, U1 extends object, U2 extends object>(
-  object: T, source1: U1, source2: U2
-): Assign<Assign<T, U1>, U2>;
+export function assign<T extends object>( object: T ): T;
 
-export function assign<T extends object, U1 extends object, U2 extends object, U3 extends object>(
-  object: T, source1: U1, source2: U2, source3: U3
-): Assign<Assign<Assign<T, U1>, U2>, U3>;
+export function assign<T extends object, U extends object[]>(
+  object: T,
+  ...sources: U
+): Resolve<Assigned<T, U, U['length']>>
 
 /**
  * Assigns all own enumerable properties of all source objects to the provided object.

+ 17 - 0
src/js/utils/object/merge/merge.test.ts

@@ -22,4 +22,21 @@ describe( 'merge', () => {
       f: true,
     } );
   } );
+
+  test( 'can merge multiple objects recursively.', () => {
+    const object  = { a: 1, b: { c: 2, d: 3 } };
+    const source1 = { b: { d: 4, e: 5 }, f: true };
+    const source2 = { b: { d: '4', g: 6 }, h: [ 1, 2, 3 ] };
+    const source3 = { h: [ 4, 5, 6 ], i: Infinity };
+    const source4 = { a: '1' };
+    const merged  = merge( object, source1, source2, source3, source4 )
+
+    expect( merged ).toStrictEqual( {
+      a: '1',
+      b: { c: 2, d: '4', e: 5, g: 6 },
+      f: true,
+      h: [ 4, 5, 6 ],
+      i: Infinity,
+    } );
+  } );
 } );

+ 35 - 13
src/js/utils/object/merge/merge.ts

@@ -1,4 +1,5 @@
-import { Cast } from '../../../types';
+import { Cast, Head, Push, Resolve, Shift } from '../../../types';
+import { slice } from '../../arrayLike';
 import { isArray, isObject } from '../../type/type';
 import { forOwn } from '../forOwn/forOwn';
 
@@ -18,30 +19,51 @@ export type Merge<T extends object, U extends object> = Omit<T, keyof U> & {
         ? Array<T[ K ][ number ] | U[ K ][ number ]>
         : U[ K ]
       : T[ K ] extends object
-        ? Merge<T[ K ], U[ K ]> extends infer A ? Cast<A, object> : never
+        ? Merge<T[ K ], U[ K ]> extends infer A ? Resolve<Cast<A, object>> : never
         : U[ K ]
     : U[ K ];
 } & Omit<U, keyof T>;
 
+/**
+ * Recursively merges U[] to T.
+ *
+ * @typeParam T - An object to assign to.
+ * @typeParam U - A tuple contains objects.
+ *
+ * @return An assigned object type.
+ */
+export type Merged<T extends object, U extends object[], N extends number, C extends any[] = []> = {
+  0: T,
+  1: Merged<Merge<T, Head<U>>, Shift<U>, N, Push<C>>,
+}[ C['length'] extends N ? 0 : 1 ] extends infer A ? Cast<A, any> : never;
+
+export function merge<T extends object>( object: T ): T;
+
+export function merge<T extends object, U extends object[]>(
+  object: T,
+  ...sources: U
+): Resolve<Merged<T, U, U['length']>>
+
 /**
  * Recursively merges source properties to the object.
  * Be aware that this method does not merge arrays. They are just duplicated by `slice()`.
  *
  * @param object - An object to merge properties to.
- * @param source - A source object to merge properties from.
  *
  * @return A new object with merged properties.
  */
-export function merge<T extends object, U extends object>( object: T, source: U ): Merge<T, U> {
-  forOwn( source, ( value, key ) => {
-    if ( isArray( value ) ) {
-      object[ key ] = value.slice();
-    } else if ( isObject( value ) ) {
-      object[ key ] = merge( isObject( object[ key ] ) ? object[ key ] : {}, value );
-    } else {
-      object[ key ] = value;
-    }
+export function merge<T extends object>( object: T ): any {
+  slice( arguments ).forEach( source => {
+    forOwn( source, ( value, key ) => {
+      if ( isArray( value ) ) {
+        object[ key ] = value.slice();
+      } else if ( isObject( value ) ) {
+        object[ key ] = merge( isObject( object[ key ] ) ? object[ key ] : {}, value );
+      } else {
+        object[ key ] = value;
+      }
+    } );
   } );
 
-  return object as Merge<T, U>;
+  return object;
 }

+ 2 - 2
tsconfig.json

@@ -7,9 +7,9 @@
     "moduleResolution": "node",
     "esModuleInterop": true,
     "skipLibCheck": true,
-    "noImplicitAny": true,
+    "strict": true,
+    "strictNullChecks": false,
     "suppressImplicitAnyIndexErrors": true,
-    "allowJs": false,
     "declarationDir": "./dist/types",
     "declaration": true,
     "lib": [

部分文件因文件數量過多而無法顯示