(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
        typeof define === 'function' && define.amd ? define(['exports'], factory) :
            (factory((global.SpriteSpin = {})));
}(this, (function (exports) { 'use strict';

    /**
     * @internal
     */
    var Api = /** @class */ (function () {
        function Api(data) {
            this.data = data;
        }
        return Api;
    }());
    /**
     * Adds methods to the SpriteSpin api
     *
     * @public
     */
    function extendApi(methods) {
        var api = Api.prototype;
        for (var key in methods) {
            if (methods.hasOwnProperty(key)) {
                if (api[key]) {
                    throw new Error('API method is already defined: ' + key);
                }
                else {
                    api[key] = methods[key];
                }
            }
        }
        return api;
    }

    var $$1 = window.jQuery || window.$;

    function getCursorPosition(event) {
        var touches = event.touches;
        var source = event;
        // jQuery Event normalization does not preserve the 'event.touches'
        // try to grab touches from the original event
        if (event.touches === undefined && event.originalEvent !== undefined) {
            touches = event.originalEvent.touches;
        }
        // get current touch or mouse position
        if (touches !== undefined && touches.length > 0) {
            source = touches[0];
        }
        return {
            x: source.clientX || 0,
            y: source.clientY || 0
        };
    }

    var canvas;
    var context;
    function detectionContext() {
        if (context) {
            return context;
        }
        if (!canvas) {
            canvas = document.createElement('canvas');
        }
        if (!canvas || !canvas.getContext) {
            return null;
        }
        context = canvas.getContext('2d');
        return context;
    }
    /**
     * Idea taken from https://github.com/stomita/ios-imagefile-megapixel
     * Detects whether the image has been sub sampled by the browser and does not have its original dimensions.
     * This method unfortunately does not work for images that have transparent background.
     */
    function detectSubsampling(img, width, height) {
        if (!detectionContext()) {
            return false;
        }
        // sub sampling happens on images above 1 megapixel
        if (width * height <= 1024 * 1024) {
            return false;
        }
        // set canvas to 1x1 pixel size and fill it with magenta color
        canvas.width = canvas.height = 1;
        context.fillStyle = '#FF00FF';
        context.fillRect(0, 0, 1, 1);
        // render the image with a negative offset to the left so that it would
        // fill the canvas pixel with the top right pixel of the image.
        context.drawImage(img, -width + 1, 0);
        // check color value to confirm image is covering edge pixel or not.
        // if color still magenta, the image is assumed to be sub sampled.
        try {
            var dat = context.getImageData(0, 0, 1, 1).data;
            return (dat[0] === 255) && (dat[1] === 0) && (dat[2] === 255);
        }
        catch (err) {
            // avoids cross origin exception for chrome when code runs without a server
            return false;
        }
    }

    /**
     *
     */
    function getOuterSize(data) {
        var width = Math.floor(data.width || data.frameWidth || data.target.innerWidth());
        var height = Math.floor(data.height || data.frameHeight || data.target.innerHeight());
        return {
            aspect: width / height,
            height: height,
            width: width
        };
    }
    function getComputedSize(data) {
        var size = getOuterSize(data);
        if (typeof window.getComputedStyle !== 'function') {
            return size;
        }
        var style = window.getComputedStyle(data.target[0]);
        if (!style.width) {
            return size;
        }
        size.width = Math.floor(Number(style.width.replace('px', '')));
        size.height = Math.floor(size.width / size.aspect);
        return size;
    }
    /**
     *
     */
    function getInnerSize(data) {
        var width = Math.floor(data.frameWidth || data.width || data.target.innerWidth());
        var height = Math.floor(data.frameHeight || data.height || data.target.innerHeight());
        return {
            aspect: width / height,
            height: height,
            width: width
        };
    }
    /**
     *
     */
    function getInnerLayout(mode, inner, outer) {
        // get mode
        var isFit = mode === 'fit';
        var isFill = mode === 'fill';
        var isMatch = mode === 'stretch';
        // resulting layout
        var layout = {
            width: '100%',
            height: '100%',
            top: 0,
            left: 0,
            bottom: 0,
            right: 0,
            position: 'absolute',
            overflow: 'hidden'
        };
        // no calculation here
        if (!mode || isMatch) {
            return layout;
        }
        // get size and aspect
        var aspectIsGreater = inner.aspect >= outer.aspect;
        // mode == original
        var width = inner.width;
        var height = inner.height;
        // keep aspect ratio but fit/fill into container
        if (isFit && aspectIsGreater || isFill && !aspectIsGreater) {
            width = outer.width;
            height = outer.width / inner.aspect;
        }
        if (isFill && aspectIsGreater || isFit && !aspectIsGreater) {
            height = outer.height;
            width = outer.height * inner.aspect;
        }
        // floor the numbers
        width = Math.floor(width);
        height = Math.floor(height);
        // position in center
        layout.width = width;
        layout.height = height;
        layout.top = Math.floor((outer.height - height) / 2);
        layout.left = Math.floor((outer.width - width) / 2);
        layout.right = layout.left;
        layout.bottom = layout.top;
        return layout;
    }

    var img;
    /**
     * gets the original width and height of an image element
     */
    function naturalSize(image) {
        // for browsers that support naturalWidth and naturalHeight properties
        if (image.naturalWidth) {
            return {
                height: image.naturalHeight,
                width: image.naturalWidth
            };
        }
        // browsers that do not support naturalWidth and naturalHeight properties have to fall back to the width and
        // height properties. However, the image might have a css style applied so width and height would return the
        // css size. To avoid thet create a new Image object that is free of css rules and grab width and height
        // properties
        //
        // assume that the src has already been downloaded, so no onload callback is needed.
        img = img || new Image();
        img.src = image.src;
        return {
            height: img.height,
            width: img.width
        };
    }

    /**
     * Measures the image frames that are used in the given data object
     */
    function measure(images, options) {
        if (images.length === 1) {
            return [measureSheet(images[0], options)];
        }
        else if (options.framesX && options.framesY) {
            return measureMutipleSheets(images, options);
        }
        else {
            return measureFrames(images, options);
        }
    }
    function measureSheet(image, options) {
        var result = { id: 0, sprites: [] };
        measureImage(image, options, result);
        var frames = options.frames;
        var framesX = Number(options.framesX) || frames;
        var framesY = Math.ceil(frames / framesX);
        var frameWidth = Math.floor(result.width / framesX);
        var frameHeight = Math.floor(result.height / framesY);
        var divisor = result.isSubsampled ? 2 : 1;
        for (var i = 0; i < frames; i++) {
            var x = (i % framesX) * frameWidth;
            var y = Math.floor(i / framesX) * frameHeight;
            result.sprites.push({
                id: i,
                x: x, y: y,
                width: frameWidth,
                height: frameHeight,
                sampledX: x / divisor,
                sampledY: y / divisor,
                sampledWidth: frameWidth / divisor,
                sampledHeight: frameHeight / divisor
            });
        }
        return result;
    }
    function measureFrames(images, options) {
        var result = [];
        for (var id = 0; id < images.length; id++) {
            // TODO: optimize
            // dont measure images with same size twice
            var sheet = measureSheet(images[id], { frames: 1, framesX: 1, detectSubsampling: options.detectSubsampling });
            sheet.id = id;
            result.push(sheet);
        }
        return result;
    }
    function measureMutipleSheets(images, options) {
        var result = [];
        for (var id = 0; id < images.length; id++) {
            // TODO: optimize
            // dont measure images with same size twice
            var sheet = measureSheet(images[id], {
                frames: undefined,
                framesX: options.framesX,
                framesY: options.framesY,
                detectSubsampling: options.detectSubsampling
            });
            sheet.id = id;
            result.push(sheet);
        }
        return result;
    }
    function measureImage(image, options, result) {
        var size = naturalSize(image);
        result.isSubsampled = options.detectSubsampling && detectSubsampling(image, size.width, size.height);
        result.width = size.width;
        result.height = size.height;
        result.sampledWidth = size.width / (result.isSubsampled ? 2 : 1);
        result.sampledHeight = size.height / (result.isSubsampled ? 2 : 1);
        return result;
    }
    function findSpecs(metrics, frames, frame, lane) {
        var spriteId = lane * frames + frame;
        var sheetId = 0;
        var sprite = null;
        var sheet = null;
        while (true) {
            sheet = metrics[sheetId];
            if (!sheet) {
                break;
            }
            if (spriteId >= sheet.sprites.length) {
                spriteId -= sheet.sprites.length;
                sheetId++;
                continue;
            }
            sprite = sheet.sprites[spriteId];
            break;
        }
        return { sprite: sprite, sheet: sheet };
    }

    function indexOf(element, arr) {
        for (var i = 0; i < arr.length; i++) {
            if (arr[i] === element) {
                return i;
            }
        }
    }
    function noop() {
        //
    }
    function preload(opts) {
        var src;
        var input = opts.source;
        src = typeof input === 'string' ? [input] : input;
        // const src: string[] =  ? [opts.source] : opts.source
        var images = [];
        var targetCount = (opts.preloadCount || src.length);
        var onInitiated = opts.initiated || noop;
        var onProgress = opts.progress || noop;
        var onComplete = opts.complete || noop;
        var count = 0;
        var completed = false;
        var firstLoaded = false;
        var tick = function () {
            count += 1;
            onProgress({
                index: indexOf(this, images),
                loaded: count,
                total: src.length,
                percent: Math.round((count / src.length) * 100)
            });
            firstLoaded = firstLoaded || (this === images[0]);
            if (firstLoaded && !completed && (count >= targetCount)) {
                completed = true;
                onComplete(images);
            }
        };
        for (var _i = 0, src_1 = src; _i < src_1.length; _i++) {
            var url = src_1[_i];
            var img = new Image();
            // push result
            images.push(img);
            // bind logic, dont care about abort/errors
            img.onload = img.onabort = img.onerror = tick;
            // begin load
            img.src = url;
        }
        onInitiated(images);
    }

    function padNumber(num, length, pad) {
        var result = String(num);
        while (result.length < length) {
            result = String(pad) + result;
        }
        return result;
    }
    /**
     * Generates an array of source strings
     *
     * @remarks
     * Takes a template string and generates an array of strings by interpolating {lane} and {frame} placeholders.
     *
     * ```
     * sourceArray('http://example.com/image_{frame}.jpg, { frame: [1, 3], digits: 2 })
     * // gives:
     * // [ 'http://example.com/image_01.jpg', 'http://example.com/image_02.jpg', 'http://example.com/image_03.jpg' ]
     *
     * sourceArray('http://example.com/image_FRAME.jpg, { frame: [1, 3], digits: 2, framePlacer: 'FRAME' })
     * // gives:
     * // [ 'http://example.com/image_01.jpg', 'http://example.com/image_02.jpg', 'http://example.com/image_03.jpg' ]
     * ```
     *
     * @param template - The template string
     * @param opts - Interpolation options
     *
     * @public
     */
    function sourceArray(template, opts) {
        var digits = opts.digits || 2;
        var lPlacer = opts.lanePlacer || '{lane}';
        var fPlacer = opts.framePlacer || '{frame}';
        var fStart = 0;
        var fEnd = 0;
        if (opts.frame) {
            fStart = opts.frame[0];
            fEnd = opts.frame[1];
        }
        var lStart = 0;
        var lEnd = 0;
        if (opts.lane) {
            lStart = opts.lane[0];
            lEnd = opts.lane[1];
        }
        var result = [];
        for (var lane = lStart; lane <= lEnd; lane += 1) {
            for (var frame = fStart; frame <= fEnd; frame += 1) {
                result.push(template
                    .replace(lPlacer, padNumber(lane, digits, '0'))
                    .replace(fPlacer, padNumber(frame, digits, '0')));
            }
        }
        return result;
    }

    /**
     * The namespace that is used to bind functions to DOM events and store the data object
     */
    var namespace = 'spritespin';
    /**
     * Event names that are recognized by SpriteSpin. A module can implement any of these and they will be bound
     * to the target element on which the plugin is called.
     */
    var eventNames = [
        'mousedown',
        'mousemove',
        'mouseup',
        'mouseenter',
        'mouseover',
        'mouseleave',
        'mousewheel',
        'wheel',
        'click',
        'dblclick',
        'touchstart',
        'touchmove',
        'touchend',
        'touchcancel',
        'selectstart',
        'gesturestart',
        'gesturechange',
        'gestureend'
    ];
    /**
     *
     */
    var callbackNames = [
        'onInit',
        'onProgress',
        'onLoad',
        'onFrameChanged',
        'onFrame',
        'onDraw',
        'onComplete',
        'onDestroy'
    ];
    /**
     * Names of events for that the default behavior should be prevented.
     */
    var eventsToPrevent = [
        'dragstart'
    ];
    /**
     * Default set of SpriteSpin options. This also represents the majority of data attributes that are used during the
     * lifetime of a SpriteSpin instance. The data is stored inside the target DOM element on which the plugin is called.
     */
    var defaults = {
        source: undefined,
        width: undefined,
        height: undefined,
        frames: undefined,
        framesX: undefined,
        lanes: 1,
        sizeMode: undefined,
        renderer: 'canvas',
        lane: 0,
        frame: 0,
        frameTime: 40,
        animate: true,
        retainAnimate: false,
        reverse: false,
        loop: true,
        stopFrame: 0,
        wrap: true,
        wrapLane: false,
        sense: 1,
        senseLane: undefined,
        orientation: 'horizontal',
        detectSubsampling: true,
        preloadCount: undefined,
        responsive: undefined,
        plugins: [
            '360',
            'drag'
        ]
    };

    function noop$1() {
        // noop
    }
    function wrapConsole(type) {
        return console && console[type] ? function () {
            var args = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                args[_i] = arguments[_i];
            }
            return console.log.apply(console, args);
        } : noop$1;
    }
    var log = wrapConsole('log');
    var warn = wrapConsole('warn');
    var error = wrapConsole('error');
    function toArray(value) {
        return Array.isArray(value) ? value : [value];
    }
    /**
     * clamps the given value by the given min and max values
     */
    function clamp(value, min, max) {
        return (value > max ? max : (value < min ? min : value));
    }
    /**
     *
     */
    function wrap(value, min, max, size) {
        while (value > max) {
            value -= size;
        }
        while (value < min) {
            value += size;
        }
        return value;
    }
    /**
     * prevents default action on the given event
     */
    function prevent(e) {
        e.preventDefault();
        return false;
    }
    /**
     * Binds on the given target and event the given function.
     * The SpriteSpin namespace is attached to the event name
     */
    function bind(target, event, func) {
        if (func) {
            target.bind(event + '.' + namespace, function (e) {
                func.apply(target, [e, target.spritespin('data')]);
            });
        }
    }
    /**
     * Unbinds all SpriteSpin events from given target element
     */
    function unbind(target) {
        target.unbind('.' + namespace);
    }
    /**
     * Checks if given object is a function
     */
    function isFunction(fn) {
        return typeof fn === 'function';
    }
    function pixelRatio(context) {
        var devicePixelRatio = window.devicePixelRatio || 1;
        var backingStoreRatio = context.webkitBackingStorePixelRatio ||
            context.mozBackingStorePixelRatio ||
            context.msBackingStorePixelRatio ||
            context.oBackingStorePixelRatio ||
            context.backingStorePixelRatio || 1;
        return devicePixelRatio / backingStoreRatio;
    }



    var _Utils = Object.freeze({
        $: $$1,
        getCursorPosition: getCursorPosition,
        detectSubsampling: detectSubsampling,
        getOuterSize: getOuterSize,
        getComputedSize: getComputedSize,
        getInnerSize: getInnerSize,
        getInnerLayout: getInnerLayout,
        measure: measure,
        findSpecs: findSpecs,
        naturalSize: naturalSize,
        preload: preload,
        sourceArray: sourceArray,
        noop: noop$1,
        log: log,
        warn: warn,
        error: error,
        toArray: toArray,
        clamp: clamp,
        wrap: wrap,
        prevent: prevent,
        bind: bind,
        unbind: unbind,
        isFunction: isFunction,
        pixelRatio: pixelRatio
    });

    /**
     * Applies css attributes to layout the SpriteSpin containers.
     *
     * @internal
     */
    function applyLayout(data) {
        // disable selection
        data.target
            .attr('unselectable', 'on')
            .css({
                width: '',
                height: '',
                '-ms-user-select': 'none',
                '-moz-user-select': 'none',
                '-khtml-user-select': 'none',
                '-webkit-user-select': 'none',
                'user-select': 'none'
            });
        var size = data.responsive ? getComputedSize(data) : getOuterSize(data);
        var layout = getInnerLayout(data.sizeMode, getInnerSize(data), size);
        // apply layout on target
        data.target.css({
            width: size.width,
            height: size.height,
            position: 'relative',
            overflow: 'hidden'
        });
        // apply layout on stage
        data.stage
            .css(layout)
            .hide();
        if (!data.canvas) {
            return;
        }
        // apply layout on canvas
        data.canvas.css(layout).hide();
        // apply pixel ratio on canvas
        data.canvasRatio = data.canvasRatio || pixelRatio(data.context);
        if (typeof layout.width === 'number' && typeof layout.height === 'number') {
            data.canvas[0].width = (layout.width * data.canvasRatio) || size.width;
            data.canvas[0].height = (layout.height * data.canvasRatio) || size.height;
        }
        else {
            data.canvas[0].width = (size.width * data.canvasRatio);
            data.canvas[0].height = (size.height * data.canvasRatio);
        }
        // width and height must be set before calling scale
        data.context.scale(data.canvasRatio, data.canvasRatio);
    }

    /**
     * Gets a state object by name.
     * @internal
     * @param data - The SpriteSpin instance data
     * @param name - The name of the state object
     */
    function getState(data, name) {
        data.state = data.state || {};
        data.state[name] = data.state[name] || {};
        return data.state[name];
    }
    /**
     * Gets a plugin state object by name.
     *
     * @remarks
     * Plugins should use this method to get or create a state object where they can
     * store any instance variables.
     *
     * @public
     * @param data - The SpriteSpin instance data
     * @param name - The name of the plugin
     */
    function getPluginState(data, name) {
        var state = getState(data, 'plugin');
        state[name] = state[name] || {};
        return state[name];
    }
    /**
     * Checks whether a flag is set. See {@link flag}.
     *
     * @public
     * @param data - The SpriteSpin instance data
     * @param key - The name of the flag
     */
    function is(data, key) {
        return !!getState(data, 'flags')[key];
    }
    /**
     * Sets a flag value. See {@link is}.
     *
     * @public
     * @param data - The SpriteSpin instance data
     * @param key - The name of the flag
     * @param value - The value to set
     */
    function flag(data, key, value) {
        getState(data, 'flags')[key] = !!value;
    }

    /**
     * Gets the playback state
     *
     * @public
     * @param data - The SpriteSpin instance data
     */
    function getPlaybackState(data) {
        return getState(data, 'playback');
    }
    function updateLane(data, lane) {
        data.lane = data.wrapLane
            ? wrap(lane, 0, data.lanes - 1, data.lanes)
            : clamp(lane, 0, data.lanes - 1);
    }
    function updateAnimationFrame(data) {
        data.frame += (data.reverse ? -1 : 1);
        // wrap the frame value to fit in range [0, data.frames)
        data.frame = wrap(data.frame, 0, data.frames - 1, data.frames);
        // stop animation if loop is disabled and the stopFrame is reached
        if (!data.loop && (data.frame === data.stopFrame)) {
            stopAnimation(data);
        }
    }
    function updateInputFrame(data, frame) {
        data.frame = Number(frame);
        data.frame = data.wrap
            ? wrap(data.frame, 0, data.frames - 1, data.frames)
            : clamp(data.frame, 0, data.frames - 1);
    }
    function updateAnimation(data) {
        var state = getPlaybackState(data);
        if (state.handler) {
            updateBefore(data);
            updateAnimationFrame(data);
            updateAfter(data);
        }
    }
    function updateBefore(data) {
        var state = getPlaybackState(data);
        state.lastFrame = data.frame;
        state.lastLane = data.lane;
    }
    function updateAfter(data) {
        var state = getPlaybackState(data);
        if (state.lastFrame !== data.frame || state.lastLane !== data.lane) {
            data.target.trigger('onFrameChanged.' + namespace, data);
        }
        data.target.trigger('onFrame.' + namespace, data);
        data.target.trigger('onDraw.' + namespace, data);
    }
    /**
     * Updates the frame or lane number of the SpriteSpin data.
     *
     * @public
     * @param data - The SpriteSpin instance data
     * @param frame - The frame number to set
     * @param lane - The lane number to set
     */
    function updateFrame(data, frame, lane) {
        updateBefore(data);
        if (frame != null) {
            updateInputFrame(data, frame);
        }
        if (lane != null) {
            updateLane(data, lane);
        }
        updateAfter(data);
    }
    /**
     * Stops the running animation.
     *
     * @public
     * @param data - The SpriteSpin instance data
     */
    function stopAnimation(data) {
        data.animate = false;
        var state = getPlaybackState(data);
        if (state.handler != null) {
            window.clearInterval(state.handler);
            state.handler = null;
        }
    }
    /**
     * Starts animation playback if needed.
     *
     * @remarks
     * Starts animation playback if `animate` property is `true` and the animation is not yet running.
     *
     * @public
     * @param data - The SpriteSpin instance data
     */
    function applyAnimation(data) {
        var state = getPlaybackState(data);
        if (state.handler && (!data.animate || state.frameTime !== data.frameTime)) {
            stopAnimation(data);
        }
        if (data.animate && !state.handler) {
            state.frameTime = data.frameTime;
            state.handler = window.setInterval(function () { return updateAnimation(data); }, state.frameTime);
        }
    }
    /**
     * Starts the animation playback
     *
     * @remarks
     * Starts the animation playback and also sets the `animate` property to `true`
     *
     * @public
     * @param data - The SpriteSpin instance data
     */
    function startAnimation(data) {
        data.animate = true;
        applyAnimation(data);
    }

    var plugins = {};
    /**
     * Registers a plugin.
     *
     * @remarks
     * Use this to add custom Rendering or Updating modules that can be addressed with the 'module' option.
     *
     * @public
     * @param name - The name of the plugin
     * @param plugin - The plugin implementation
     */
    function registerPlugin(name, plugin) {
        if (plugins[name]) {
            error("Plugin name \"" + name + "\" is already taken");
            return;
        }
        plugin = plugin || {};
        plugins[name] = plugin;
        return plugin;
    }
    /**
     * Registers a plugin.
     *
     * @public
     * @deprecated Use {@link registerPlugin} instead
     * @param name - The name of the plugin
     * @param plugin - The plugin implementation
     */
    function registerModule(name, plugin) {
        warn('"registerModule" is deprecated, use "registerPlugin" instead');
        registerPlugin(name, plugin);
    }
    /**
     * Gets an active plugin by name
     *
     * @internal
     * @param name - The name of the plugin
     */
    function getPlugin(name) {
        return plugins[name];
    }
    /**
     * Replaces module names on given SpriteSpin data and replaces them with actual implementations.
     * @internal
     */
    function applyPlugins(data) {
        fixPlugins(data);
        for (var i = 0; i < data.plugins.length; i += 1) {
            var name_1 = data.plugins[i];
            if (typeof name_1 !== 'string') {
                continue;
            }
            var plugin = getPlugin(name_1);
            if (!plugin) {
                error('No plugin found with name ' + name_1);
                continue;
            }
            data.plugins[i] = plugin;
        }
    }
    function fixPlugins(data) {
        // tslint:disable no-string-literal
        if (data['mods']) {
            warn('"mods" option is deprecated, use "plugins" instead');
            data.plugins = data['mods'];
            delete data['mods'];
        }
        if (data['behavior']) {
            warn('"behavior" option is deprecated, use "plugins" instead');
            data.plugins.push(data['behavior']);
            delete data['behavior'];
        }
        if (data['module']) {
            warn('"module" option is deprecated, use "plugins" instead');
            data.plugins.push(data['module']);
            delete data['module'];
        }
    }

    var $$2 = $$1;
    var counter = 0;
    /**
     * Collection of all SpriteSpin instances
     */
    var instances = {};
    function pushInstance(data) {
        counter += 1;
        data.id = String(counter);
        instances[data.id] = data;
    }
    function popInstance(data) {
        delete instances[data.id];
    }
    function eachInstance(cb) {
        for (var id in instances) {
            if (instances.hasOwnProperty(id)) {
                cb(instances[id]);
            }
        }
    }
    var lazyinit = function () {
        // replace function with a noop
        // this logic must run only once
        lazyinit = function () { };
        function onEvent(eventName, e) {
            eachInstance(function (data) {
                for (var _i = 0, _a = data.plugins; _i < _a.length; _i++) {
                    var module_1 = _a[_i];
                    if (typeof module_1[eventName] === 'function') {
                        module_1[eventName].apply(data.target, [e, data]);
                    }
                }
            });
        }
        function onResize() {
            eachInstance(function (data) {
                if (data.responsive) {
                    boot(data);
                }
            });
        }
        var _loop_1 = function (eventName) {
            $$2(window.document).bind(eventName + '.' + namespace, function (e) {
                onEvent('document' + eventName, e);
            });
        };
        for (var _i = 0, eventNames_1 = eventNames; _i < eventNames_1.length; _i++) {
            var eventName = eventNames_1[_i];
            _loop_1(eventName);
        }
        var resizeTimeout = null;
        $$2(window).on('resize', function () {
            window.clearTimeout(resizeTimeout);
            resizeTimeout = window.setTimeout(onResize, 100);
        });
    };
    /**
     * (re)binds all spritespin events on the target element
     *
     * @internal
     */
    function applyEvents(data) {
        var target = data.target;
        // Clear all SpriteSpin events on the target element
        unbind(target);
        // disable all default browser behavior on the following events
        // mainly prevents image drag operation
        for (var _i = 0, eventsToPrevent_1 = eventsToPrevent; _i < eventsToPrevent_1.length; _i++) {
            var eName = eventsToPrevent_1[_i];
            bind(target, eName, prevent);
        }
        // Bind module functions to SpriteSpin events
        for (var _a = 0, _b = data.plugins; _a < _b.length; _a++) {
            var plugin = _b[_a];
            for (var _c = 0, eventNames_2 = eventNames; _c < eventNames_2.length; _c++) {
                var eName = eventNames_2[_c];
                bind(target, eName, plugin[eName]);
            }
            for (var _d = 0, callbackNames_1 = callbackNames; _d < callbackNames_1.length; _d++) {
                var eName = callbackNames_1[_d];
                bind(target, eName, plugin[eName]);
            }
        }
        // bind auto start function to load event.
        bind(target, 'onLoad', function (e, d) {
            applyAnimation(d);
        });
        // bind all user events that have been passed on initialization
        for (var _e = 0, callbackNames_2 = callbackNames; _e < callbackNames_2.length; _e++) {
            var eName = callbackNames_2[_e];
            bind(target, eName, data[eName]);
        }
    }
    function applyMetrics(data) {
        if (!data.images) {
            data.metrics = [];
        }
        data.metrics = measure(data.images, data);
        var spec = findSpecs(data.metrics, data.frames, 0, 0);
        if (spec.sprite) {
            // TODO: try to remove frameWidth/frameHeight
            data.frameWidth = spec.sprite.width;
            data.frameHeight = spec.sprite.height;
        }
    }
    /**
     * Runs the boot process.
     *
     * @remarks
     * (re)initializes plugins, (re)initializes the layout, (re)binds events and loads source images.
     *
     * @internal
     */
    function boot(data) {
        applyPlugins(data);
        applyEvents(data);
        applyLayout(data);
        data.source = toArray(data.source);
        data.loading = true;
        data.target
            .addClass('loading')
            .trigger('onInit.' + namespace, data);
        preload({
            source: data.source,
            preloadCount: data.preloadCount,
            progress: function (progress) {
                data.progress = progress;
                data.target.trigger('onProgress.' + namespace, data);
            },
            complete: function (images) {
                data.images = images;
                data.loading = false;
                data.frames = data.frames || images.length;
                applyMetrics(data);
                applyLayout(data);
                data.stage.show();
                data.target
                    .removeClass('loading')
                    .trigger('onLoad.' + namespace, data)
                    .trigger('onFrame.' + namespace, data)
                    .trigger('onDraw.' + namespace, data)
                    .trigger('onComplete.' + namespace, data);
            }
        });
    }
    /**
     * Creates a new SpriteSpin instance
     *
     * @public
     */
    function create(options) {
        var _this = this;
        var target = options.target;
        // SpriteSpin is not initialized
        // Create default settings object and extend with given options
        var data = $$2.extend(true, {}, defaults, options);
        // ensure source is set
        data.source = data.source || [];
        // ensure plugins are set
        data.plugins = data.plugins || [];
        // if image tags are contained inside this DOM element
        // use these images as the source files
        target.find('img').each(function () {
            if (!Array.isArray(data.source)) {
                data.source = [];
            }
            data.source.push($$2(_this).attr('src'));
        });
        // build inner html
        // <div>
        //   <div class='spritespin-stage'></div>
        //   <canvas class='spritespin-canvas'></canvas>
        // </div>
        target
            .empty()
            .addClass('spritespin-instance')
            .append("<div class='spritespin-stage'></div>");
        // add the canvas element if canvas rendering is enabled and supported
        if (data.renderer === 'canvas') {
            var canvas = document.createElement('canvas');
            if (!!(canvas.getContext && canvas.getContext('2d'))) {
                data.canvas = $$2(canvas).addClass('spritespin-canvas');
                data.context = canvas.getContext('2d');
                target.append(data.canvas);
                target.addClass('with-canvas');
            }
            else {
                // fallback to image rendering mode
                data.renderer = 'image';
            }
        }
        // setup references to DOM elements
        data.target = target;
        data.stage = target.find('.spritespin-stage');
        // store the data
        target.data(namespace, data);
        pushInstance(data);
        return data;
    }
    /**
     * Creates a new SpriteSpin instance, or updates an existing one
     *
     * @public
     */
    function createOrUpdate(options) {
        lazyinit();
        var data = options.target.data(namespace);
        if (!data) {
            data = create(options);
        }
        else {
            $$2.extend(data, options);
        }
        boot(data);
        return data;
    }
    /**
     * Destroys the SpriteSpin instance
     *
     * @remarks
     * - stops running animation
     * - unbinds all events
     * - deletes the data on the target element
     *
     * @public
     */
    function destroy(data) {
        popInstance(data);
        stopAnimation(data);
        data.target
            .trigger('onDestroy', data)
            .html(null)
            .attr('style', null)
            .attr('unselectable', null)
            .removeClass(['spritespin-instance', 'with-canvas']);
        unbind(data.target);
        data.target.removeData(namespace);
    }

    /**
     * Gets the current input state
     *
     * @public
     * @param data - The SpriteSpin instance data
     */
    function getInputState(data) {
        return getState(data, 'input');
    }
    /**
     * Updates the input state using a mous or touch event.
     *
     * @public
     * @param e - The input event
     * @param data - The SpriteSpin instance data
     */
    function updateInput(e, data) {
        var cursor = getCursorPosition(e);
        var state = getInputState(data);
        // cache positions from previous frame
        state.oldX = state.currentX;
        state.oldY = state.currentY;
        state.currentX = cursor.x;
        state.currentY = cursor.y;
        // Fix old position.
        if (state.oldX === undefined || state.oldY === undefined) {
            state.oldX = state.currentX;
            state.oldY = state.currentY;
        }
        // Cache the initial click/touch position and store the frame number at which the click happened.
        // Useful for different behavior implementations. This must be restored when the click/touch is released.
        if (state.startX === undefined || state.startY === undefined) {
            state.startX = state.currentX;
            state.startY = state.currentY;
            state.clickframe = data.frame;
            state.clicklane = data.lane;
        }
        // Calculate the vector from start position to current pointer position.
        state.dX = state.currentX - state.startX;
        state.dY = state.currentY - state.startY;
        // Calculate the vector from last frame position to current pointer position.
        state.ddX = state.currentX - state.oldX;
        state.ddY = state.currentY - state.oldY;
        // Normalize vectors to range [-1:+1]
        state.ndX = state.dX / data.target.innerWidth();
        state.ndY = state.dY / data.target.innerHeight();
        state.nddX = state.ddX / data.target.innerWidth();
        state.nddY = state.ddY / data.target.innerHeight();
    }
    /**
     * Resets the input state.
     *
     * @public
     */
    function resetInput(data) {
        var input = getInputState(data);
        input.startX = input.startY = undefined;
        input.currentX = input.currentY = undefined;
        input.oldX = input.oldY = undefined;
        input.dX = input.dY = 0;
        input.ddX = input.ddY = 0;
        input.ndX = input.ndY = 0;
        input.nddX = input.nddY = 0;
    }

    function extension(option, value) {
        var $target = $$1(this);
        if (option === 'data') {
            return $target.data(namespace);
        }
        if (option === 'api') {
            var data = $target.data(namespace);
            data.api = data.api || new Api(data);
            return data.api;
        }
        if (option === 'destroy') {
            return $target.each(function () {
                var data = $target.data(namespace);
                if (data) {
                    destroy(data);
                }
            });
        }
        if (arguments.length === 2 && typeof option === 'string') {
            option = (_a = {}, _a[option] = value, _a);
        }
        if (typeof option === 'object') {
            return createOrUpdate($$1.extend(true, { target: $target }, option)).target;
        }
        throw new Error('Invalid call to spritespin');
        var _a;
    }
    $$1.fn[namespace] = extension;

// tslint:disable:object-literal-shorthand
// tslint:disable:only-arrow-functions
    extendApi({
        // Gets a value indicating whether the animation is currently running.
        isPlaying: function () {
            return getPlaybackState(this.data).handler != null;
        },
        // Gets a value indicating whether the animation looping is enabled.
        isLooping: function () {
            return this.data.loop;
        },
        // Starts/Stops the animation playback
        toggleAnimation: function () {
            if (this.isPlaying()) {
                this.stopAnimation();
            }
            else {
                this.startAnimation();
            }
        },
        // Stops animation playback
        stopAnimation: function () {
            this.data.animate = false;
            stopAnimation(this.data);
        },
        // Starts animation playback
        startAnimation: function () {
            this.data.animate = true;
            applyAnimation(this.data);
        },
        // Sets a value indicating whether the animation should be looped or not.
        // This might start the animation (if the 'animate' data attribute is set to true)
        loop: function (value) {
            this.data.loop = value;
            applyAnimation(this.data);
            return this;
        },
        // Gets the current frame number
        currentFrame: function () {
            return this.data.frame;
        },
        // Updates SpriteSpin to the specified frame.
        updateFrame: function (frame) {
            updateFrame(this.data, frame);
            return this;
        },
        // Skips the given number of frames
        skipFrames: function (step) {
            var data = this.data;
            updateFrame(data, data.frame + (data.reverse ? -step : +step));
            return this;
        },
        // Updates SpriteSpin so that the next frame is shown
        nextFrame: function () {
            return this.skipFrames(1);
        },
        // Updates SpriteSpin so that the previous frame is shown
        prevFrame: function () {
            return this.skipFrames(-1);
        },
        // Starts the animations that will play until the given frame number is reached
        // options:
        //   force [boolean] starts the animation, even if current frame is the target frame
        //   nearest [boolean] animates to the direction with minimum distance to the target frame
        playTo: function (frame, options) {
            var data = this.data;
            options = options || {};
            if (!options.force && data.frame === frame) {
                return;
            }
            if (options.nearest) {
                // distance to the target frame
                var a = frame - data.frame;
                // distance to last frame and the to target frame
                var b = frame > data.frame ? a - data.frames : a + data.frames;
                // minimum distance
                var c = Math.abs(a) < Math.abs(b) ? a : b;
                data.reverse = c < 0;
            }
            data.animate = true;
            data.loop = false;
            data.stopFrame = frame;
            applyAnimation(data);
            return this;
        }
    });

    function pick(target, names) {
        for (var _i = 0, names_1 = names; _i < names_1.length; _i++) {
            var name_1 = names_1[_i];
            if (target[name_1] || name_1 in target) {
                return name_1;
            }
        }
        return names[0];
    }
    var browser = {
        requestFullscreen: pick(document.documentElement, [
            'requestFullscreen',
            'webkitRequestFullScreen',
            'mozRequestFullScreen',
            'msRequestFullscreen'
        ]),
        exitFullscreen: pick(document, [
            'exitFullscreen',
            'webkitExitFullscreen',
            'webkitCancelFullScreen',
            'mozCancelFullScreen',
            'msExitFullscreen'
        ]),
        fullscreenElement: pick(document, [
            'fullscreenElement',
            'webkitFullscreenElement',
            'webkitCurrentFullScreenElement',
            'mozFullScreenElement',
            'msFullscreenElement'
        ]),
        fullscreenEnabled: pick(document, [
            'fullscreenEnabled',
            'webkitFullscreenEnabled',
            'mozFullScreenEnabled',
            'msFullscreenEnabled'
        ]),
        fullscreenchange: pick(document, [
            'onfullscreenchange',
            'onwebkitfullscreenchange',
            'onmozfullscreenchange',
            'onMSFullscreenChange'
        ]).replace(/^on/, ''),
        fullscreenerror: pick(document, [
            'onfullscreenerror',
            'onwebkitfullscreenerror',
            'onmozfullscreenerror',
            'onMSFullscreenError'
        ]).replace(/^on/, '')
    };
    var changeEvent = browser.fullscreenchange + '.' + namespace + '-fullscreen';
    function unbindChangeEvent() {
        $$1(document).unbind(changeEvent);
    }
    function bindChangeEvent(callback) {
        unbindChangeEvent();
        $$1(document).bind(changeEvent, callback);
    }
    var orientationEvent = 'orientationchange.' + namespace + '-fullscreen';
    function unbindOrientationEvent() {
        $$1(window).unbind(orientationEvent);
    }
    function bindOrientationEvent(callback) {
        unbindOrientationEvent();
        $$1(window).bind(orientationEvent, callback);
    }
    function requestFullscreenNative(e) {
        e = e || document.documentElement;
        e[browser.requestFullscreen]();
    }
    function exitFullscreen() {
        return document[browser.exitFullscreen]();
    }
    function fullscreenEnabled() {
        return document[browser.fullscreenEnabled];
    }
    function fullscreenElement() {
        return document[browser.fullscreenElement];
    }
    function isFullscreen() {
        return !!fullscreenElement();
    }
    function toggleFullscreen(data, opts) {
        if (isFullscreen()) {
            this.apiRequestFullscreen(opts);
        }
        else {
            this.exitFullscreen();
        }
    }
    function requestFullscreen(data, opts) {
        opts = opts || {};
        var oWidth = data.width;
        var oHeight = data.height;
        var oSource = data.source;
        var oSize = data.sizeMode;
        var oResponsive = data.responsive;
        var enter = function () {
            data.width = window.screen.width;
            data.height = window.screen.height;
            data.source = (opts.source || oSource);
            data.sizeMode = opts.sizeMode || 'fit';
            data.responsive = false;
            boot(data);
        };
        var exit = function () {
            data.width = oWidth;
            data.height = oHeight;
            data.source = oSource;
            data.sizeMode = oSize;
            data.responsive = oResponsive;
            boot(data);
        };
        bindChangeEvent(function () {
            if (isFullscreen()) {
                enter();
                bindOrientationEvent(enter);
            }
            else {
                unbindChangeEvent();
                unbindOrientationEvent();
                exit();
            }
        });
        requestFullscreenNative(data.target[0]);
    }
    extendApi({
        fullscreenEnabled: fullscreenEnabled,
        fullscreenElement: fullscreenElement,
        exitFullscreen: exitFullscreen,
        toggleFullscreen: function (opts) {
            toggleFullscreen(this.data, opts);
        },
        requestFullscreen: function (opts) {
            requestFullscreen(this.data, opts);
        }
    });

    (function () {
        var NAME = 'click';
        function click(e, data) {
            if (data.loading || !data.stage.is(':visible')) {
                return;
            }
            updateInput(e, data);
            var input = getInputState(data);
            var half, pos;
            var target = data.target, offset = target.offset();
            if (data.orientation === 'horizontal') {
                half = target.innerWidth() / 2;
                pos = input.currentX - offset.left;
            }
            else {
                half = target.innerHeight() / 2;
                pos = input.currentY - offset.top;
            }
            updateFrame(data, data.frame + (pos > half ? 1 : -1));
        }
        registerPlugin(NAME, {
            name: NAME,
            mouseup: click,
            touchend: click
        });
    })();

    (function () {
        var NAME = 'drag';
        function getState$$1(data) {
            return getPluginState(data, NAME);
        }
        function getAxis(data) {
            if (typeof data.orientation === 'number') {
                return data.orientation * Math.PI / 180;
            }
            if (data.orientation === 'horizontal') {
                return 0;
            }
            return Math.PI / 2;
        }
        function dragStart(e, data) {
            var state = getState$$1(data);
            if (data.loading || is(data, 'dragging') || !data.stage.is(':visible')) {
                return;
            }
            // allow browser scroll only on double tap
            var now = new Date().getTime();
            if (state.startAt && (now - state.startAt > 200)) {
                e.preventDefault();
            }
            state.startAt = now;
            state.wasPlaying = !!getPlaybackState(data).handler;
            state.frame = data.frame || 0;
            state.lane = data.lane || 0;
            flag(data, 'dragging', true);
            updateInput(e, data);
        }
        function dragEnd(e, data) {
            if (is(data, 'dragging')) {
                flag(data, 'dragging', false);
                resetInput(data);
                if (data.retainAnimate && getState$$1(data).wasPlaying) {
                    startAnimation(data);
                }
            }
        }
        function drag(e, data) {
            var state = getState$$1(data);
            var input = getInputState(data);
            if (!is(data, 'dragging')) {
                return;
            }
            updateInput(e, data);
            var rad = getAxis(data);
            var sn = Math.sin(rad);
            var cs = Math.cos(rad);
            var x = ((input.nddX * cs - input.nddY * sn) * data.sense) || 0;
            var y = ((input.nddX * sn + input.nddY * cs) * (data.senseLane || data.sense)) || 0;
            // accumulate
            state.frame += data.frames * x;
            state.lane += data.lanes * y;
            // update spritespin
            var oldFrame = data.frame;
            var oldLane = data.lane;
            updateFrame(data, Math.floor(state.frame), Math.floor(state.lane));
            stopAnimation(data);
        }
        function mousemove(e, data) {
            dragStart(e, data);
            drag(e, data);
        }
        registerPlugin('drag', {
            name: 'drag',
            mousedown: dragStart,
            mousemove: drag,
            mouseup: dragEnd,
            documentmousemove: drag,
            documentmouseup: dragEnd,
            touchstart: dragStart,
            touchmove: drag,
            touchend: dragEnd,
            touchcancel: dragEnd
        });
        registerPlugin('move', {
            name: 'move',
            mousemove: mousemove,
            mouseleave: dragEnd,
            touchstart: dragStart,
            touchmove: drag,
            touchend: dragEnd,
            touchcancel: dragEnd
        });
    })();

    (function () {
        var NAME = 'hold';
        function getState$$1(data) {
            return getPluginState(data, NAME);
        }
        function rememberOptions(data) {
            var state = getState$$1(data);
            state.frameTime = data.frameTime;
            state.animate = data.animate;
            state.reverse = data.reverse;
        }
        function restoreOptions(data) {
            var state = getState$$1(data);
            data.frameTime = state.frameTime;
            data.animate = state.animate;
            data.reverse = state.reverse;
        }
        function start(e, data) {
            if (is(data, 'loading') || is(data, 'dragging') || !data.stage.is(':visible')) {
                return;
            }
            rememberOptions(data);
            updateInput(e, data);
            flag(data, 'dragging', true);
            data.animate = true;
            applyAnimation(data);
        }
        function stop(e, data) {
            flag(data, 'dragging', false);
            resetInput(data);
            stopAnimation(data);
            restoreOptions(data);
            applyAnimation(data);
        }
        function update(e, data) {
            if (!is(data, 'dragging')) {
                return;
            }
            updateInput(e, data);
            var input = getInputState(data);
            var half, delta;
            var target = data.target, offset = target.offset();
            if (data.orientation === 'horizontal') {
                half = target.innerWidth() / 2;
                delta = (input.currentX - offset.left - half) / half;
            }
            else {
                half = (data.height / 2);
                delta = (input.currentY - offset.top - half) / half;
            }
            data.reverse = delta < 0;
            delta = delta < 0 ? -delta : delta;
            data.frameTime = 80 * (1 - delta) + 20;
            if (((data.orientation === 'horizontal') && (input.dX < input.dY)) ||
                ((data.orientation === 'vertical') && (input.dX < input.dY))) {
                e.preventDefault();
            }
        }
        function onFrame(e, data) {
            data.animate = true;
            applyAnimation(data);
        }
        registerPlugin(NAME, {
            name: NAME,
            mousedown: start,
            mousemove: update,
            mouseup: stop,
            mouseleave: stop,
            touchstart: start,
            touchmove: update,
            touchend: stop,
            touchcancel: stop,
            onFrame: onFrame
        });
    })();

    (function () {
        var NAME = 'swipe';
        function getState$$1(data) {
            return getPluginState(data, NAME);
        }
        function getOption(data, name, fallback) {
            return data[name] || fallback;
        }
        function init(e, data) {
            var state = getState$$1(data);
            state.fling = getOption(data, 'swipeFling', 10);
            state.snap = getOption(data, 'swipeSnap', 0.50);
        }
        function start(e, data) {
            if (!data.loading && !is(data, 'dragging')) {
                updateInput(e, data);
                flag(data, 'dragging', true);
            }
        }
        function update(e, data) {
            if (!is(data, 'dragging')) {
                return;
            }
            updateInput(e, data);
            var frame = data.frame;
            var lane = data.lane;
            updateFrame(data, frame, lane);
        }
        function end(e, data) {
            if (!is(data, 'dragging')) {
                return;
            }
            flag(data, 'dragging', false);
            var state = getState$$1(data);
            var input = getInputState(data);
            var frame = data.frame;
            var lane = data.lane;
            var snap = state.snap;
            var fling = state.fling;
            var dS, dF;
            if (data.orientation === 'horizontal') {
                dS = input.ndX;
                dF = input.ddX;
            }
            else {
                dS = input.ndY;
                dF = input.ddY;
            }
            if (dS >= snap || dF >= fling) {
                frame = data.frame - 1;
            }
            else if (dS <= -snap || dF <= -fling) {
                frame = data.frame + 1;
            }
            resetInput(data);
            updateFrame(data, frame, lane);
            stopAnimation(data);
        }
        registerPlugin(NAME, {
            name: NAME,
            onLoad: init,
            mousedown: start,
            mousemove: update,
            mouseup: end,
            mouseleave: end,
            touchstart: start,
            touchmove: update,
            touchend: end,
            touchcancel: end
        });
    })();

    (function () {
        var NAME = 'wheel';
        function wheel(e, data) {
            if (!data.loading && data.stage.is(':visible')) {
                e.preventDefault();
                var we = e.originalEvent;
                var signX = we.deltaX === 0 ? 0 : we.deltaX > 0 ? 1 : -1;
                var signY = we.deltaY === 0 ? 0 : we.deltaY > 0 ? 1 : -1;
                updateFrame(data, data.frame + signY, data.lane + signX);
            }
        }
        registerPlugin(NAME, {
            name: NAME,
            wheel: wheel
        });
    })();

    (function () {
        var template = "\n<div class='spritespin-progress'>\n  <div class='spritespin-progress-label'></div>\n  <div class='spritespin-progress-bar'></div>\n</div>\n";
        function getState$$1(data) {
            return getPluginState(data, NAME);
        }
        var NAME = 'progress';
        function onInit(e, data) {
            var state = getState$$1(data);
            if (!state.stage) {
                state.stage = $$1(template);
                state.stage.appendTo(data.target);
            }
            state.stage.find('.spritespin-progress-label')
                .text("0%")
                .css({ 'text-align': 'center' });
            state.stage.find('.spritespin-progress-bar').css({
                width: "0%"
            });
            state.stage.hide().fadeIn();
        }
        function onProgress(e, data) {
            var state = getState$$1(data);
            state.stage.find('.spritespin-progress-label')
                .text(data.progress.percent + "%")
                .css({ 'text-align': 'center' });
            state.stage.find('.spritespin-progress-bar').css({
                width: data.progress.percent + "%"
            });
        }
        function onLoad(e, data) {
            $$1(getState$$1(data).stage).fadeOut();
        }
        function onDestroy(e, data) {
            $$1(getState$$1(data).stage).remove();
        }
        registerPlugin(NAME, {
            name: NAME,
            onInit: onInit,
            onProgress: onProgress,
            onLoad: onLoad,
            onDestroy: onDestroy
        });
    })();

    (function () {
        var NAME = '360';
        function onLoad(e, data) {
            data.stage.find('.spritespin-frames').detach();
            if (data.renderer === 'image') {
                $(data.images).addClass('spritespin-frames').appendTo(data.stage);
            }
        }
        function onDraw(e, data) {
            var specs = findSpecs(data.metrics, data.frames, data.frame, data.lane);
            var sheet = specs.sheet;
            var sprite = specs.sprite;
            if (!sheet || !sprite) {
                return;
            }
            var src = data.source[sheet.id];
            var image = data.images[sheet.id];
            if (data.renderer === 'canvas') {
                data.canvas.show();
                var w = data.canvas[0].width / data.canvasRatio;
                var h = data.canvas[0].height / data.canvasRatio;
                data.context.clearRect(0, 0, w, h);
                data.context.drawImage(image, sprite.sampledX, sprite.sampledY, sprite.sampledWidth, sprite.sampledHeight, 0, 0, w, h);
                return;
            }
            var scaleX = data.stage.innerWidth() / sprite.sampledWidth;
            var scaleY = data.stage.innerHeight() / sprite.sampledHeight;
            var top = Math.floor(-sprite.sampledY * scaleY);
            var left = Math.floor(-sprite.sampledX * scaleX);
            var width = Math.floor(sheet.sampledWidth * scaleX);
            var height = Math.floor(sheet.sampledHeight * scaleY);
            if (data.renderer === 'background') {
                data.stage.css({
                    'background-image': "url('" + src + "')",
                    'background-position': left + "px " + top + "px",
                    'background-repeat': 'no-repeat',
                    // set custom background size to enable responsive rendering
                    '-webkit-background-size': width + "px " + height + "px",
                    '-moz-background-size': width + "px " + height + "px",
                    '-o-background-size': width + "px " + height + "px",
                    'background-size': width + "px " + height + "px" /* Chrome, Firefox 4+, IE 9+, Opera, Safari 5+ */
                });
                return;
            }
            $(data.images).hide();
            $(image).show().css({
                position: 'absolute',
                top: top,
                left: left,
                'max-width': 'initial',
                width: width,
                height: height
            });
        }
        registerPlugin(NAME, {
            name: NAME,
            onLoad: onLoad,
            onDraw: onDraw
        });
    })();

    (function () {
        var NAME = 'blur';
        function getState$$1(data) {
            return getPluginState(data, NAME);
        }
        function getOption(data, name, fallback) {
            return data[name] || fallback;
        }
        function init(e, data) {
            var state = getState$$1(data);
            state.canvas = state.canvas || $$1("<canvas class='blur-layer'></canvas>");
            state.context = state.context || state.canvas[0].getContext('2d');
            state.steps = state.steps || [];
            state.fadeTime = Math.max(getOption(data, 'blurFadeTime', 200), 1);
            state.frameTime = Math.max(getOption(data, 'blurFrameTime', data.frameTime), 16);
            state.trackTime = null;
            state.cssBlur = !!getOption(data, 'blurCss', false);
            var inner = getInnerSize(data);
            var outer = data.responsive ? getComputedSize(data) : getOuterSize(data);
            var css = getInnerLayout(data.sizeMode, inner, outer);
            state.canvas[0].width = data.width * data.canvasRatio;
            state.canvas[0].height = data.height * data.canvasRatio;
            state.canvas.css(css).show();
            state.context.scale(data.canvasRatio, data.canvasRatio);
            data.target.append(state.canvas);
        }
        function onFrame(e, data) {
            var state = getState$$1(data);
            trackFrame(data);
            if (state.timeout == null) {
                loop(data);
            }
        }
        function trackFrame(data) {
            var state = getState$$1(data);
            var ani = getPlaybackState(data);
            // distance between frames
            var d = Math.abs(data.frame - ani.lastFrame);
            // shortest distance
            d = d >= data.frames / 2 ? data.frames - d : d;
            state.steps.unshift({
                frame: data.frame,
                lane: data.lane,
                live: 1,
                step: state.frameTime / state.fadeTime,
                d: d,
                alpha: 0
            });
        }
        var toRemove = [];
        function removeOldFrames(frames) {
            toRemove.length = 0;
            for (var i = 0; i < frames.length; i += 1) {
                if (frames[i].alpha <= 0) {
                    toRemove.push(i);
                }
            }
            for (var _i = 0, toRemove_1 = toRemove; _i < toRemove_1.length; _i++) {
                var item = toRemove_1[_i];
                frames.splice(item, 1);
            }
        }
        function loop(data) {
            var state = getState$$1(data);
            state.timeout = window.setTimeout(function () { tick(data); }, state.frameTime);
        }
        function killLoop(data) {
            var state = getState$$1(data);
            window.clearTimeout(state.timeout);
            state.timeout = null;
        }
        function applyCssBlur(canvas, d) {
            var amount = Math.min(Math.max((d / 2) - 4, 0), 2.5);
            var blur = "blur(" + amount + "px)";
            canvas.css({
                '-webkit-filter': blur,
                filter: blur
            });
        }
        function clearFrame(data, state) {
            state.canvas.show();
            var w = state.canvas[0].width / data.canvasRatio;
            var h = state.canvas[0].height / data.canvasRatio;
            // state.context.clearRect(0, 0, w, h)
        }
        function drawFrame(data, state, step) {
            if (step.alpha <= 0) {
                return;
            }
            var specs = findSpecs(data.metrics, data.frames, step.frame, step.lane);
            var sheet = specs.sheet;
            var sprite = specs.sprite;
            if (!sheet || !sprite) {
                return;
            }
            var src = data.source[sheet.id];
            var image = data.images[sheet.id];
            if (image.complete === false) {
                return;
            }
            state.canvas.show();
            var w = state.canvas[0].width / data.canvasRatio;
            var h = state.canvas[0].height / data.canvasRatio;
            state.context.globalAlpha = step.alpha;
            state.context.drawImage(image, sprite.sampledX, sprite.sampledY, sprite.sampledWidth, sprite.sampledHeight, 0, 0, w, h);
        }
        function tick(data) {
            var state = getState$$1(data);
            killLoop(data);
            if (!state.context) {
                return;
            }
            var d = 0;
            clearFrame(data, state);
            state.context.clearRect(0, 0, data.width, data.height);
            for (var _i = 0, _a = state.steps; _i < _a.length; _i++) {
                var step = _a[_i];
                step.live = Math.max(step.live - step.step, 0);
                step.alpha = Math.max(step.live - 0.25, 0);
                drawFrame(data, state, step);
                d += step.alpha + step.d;
            }
            if (state.cssBlur) {
                applyCssBlur(state.canvas, d);
            }
            removeOldFrames(state.steps);
            if (state.steps.length) {
                loop(data);
            }
        }
        registerPlugin(NAME, {
            name: NAME,
            onLoad: init,
            onFrameChanged: onFrame
        });
    })();

    (function () {
        var max = Math.max;
        var min = Math.min;
        var NAME = 'ease';
        function getState$$1(data) {
            return getPluginState(data, NAME);
        }
        function getOption(data, name, fallback) {
            return data[name] || fallback;
        }
        function init(e, data) {
            var state = getState$$1(data);
            state.maxSamples = max(getOption(data, 'easeMaxSamples', 5), 0);
            state.damping = max(min(getOption(data, 'easeDamping', 0.9), 0.999), 0);
            state.abortTime = max(getOption(data, 'easeAbortTime', 250), 16);
            state.updateTime = max(getOption(data, 'easeUpdateTime', data.frameTime), 16);
            state.samples = [];
            state.steps = [];
        }
        function update(e, data) {
            if (is(data, 'dragging')) {
                killLoop(data);
                sampleInput(data);
            }
        }
        function end(e, data) {
            var state = getState$$1(data);
            var samples = state.samples;
            var last;
            var lanes = 0;
            var frames = 0;
            var time = 0;
            for (var _i = 0, samples_1 = samples; _i < samples_1.length; _i++) {
                var sample = samples_1[_i];
                if (!last) {
                    last = sample;
                    continue;
                }
                var dt = sample.time - last.time;
                if (dt > state.abortTime) {
                    lanes = frames = time = 0;
                    return killLoop(data);
                }
                frames += sample.frame - last.frame;
                lanes += sample.lane - last.lane;
                time += dt;
                last = sample;
            }
            samples.length = 0;
            if (!time) {
                return;
            }
            state.lane = data.lane;
            state.lanes = 0;
            state.laneStep = lanes / time * state.updateTime;
            state.frame = data.frame;
            state.frames = 0;
            state.frameStep = frames / time * state.updateTime;
            loop(data);
        }
        function sampleInput(data) {
            var state = getState$$1(data);
            // add a new sample
            state.samples.push({
                time: new Date().getTime(),
                frame: data.frame,
                lane: data.lane
            });
            // drop old samples
            while (state.samples.length > state.maxSamples) {
                state.samples.shift();
            }
        }
        function killLoop(data) {
            var state = getState$$1(data);
            if (state.handler != null) {
                window.clearTimeout(state.handler);
                state.handler = null;
            }
        }
        function loop(data) {
            var state = getState$$1(data);
            state.handler = window.setTimeout(function () { tick(data); }, state.updateTime);
        }
        function tick(data) {
            var state = getState$$1(data);
            state.lanes += state.laneStep;
            state.frames += state.frameStep;
            state.laneStep *= state.damping;
            state.frameStep *= state.damping;
            var frame = Math.floor(state.frame + state.frames);
            var lane = Math.floor(state.lane + state.lanes);
            updateFrame(data, frame, lane);
            if (is(data, 'dragging')) {
                killLoop(data);
            }
            else if (Math.abs(state.frameStep) > 0.005 || Math.abs(state.laneStep) > 0.005) {
                loop(data);
            }
            else {
                killLoop(data);
            }
        }
        registerPlugin(NAME, {
            name: NAME,
            onLoad: init,
            mousemove: update,
            mouseup: end,
            mouseleave: end,
            touchmove: update,
            touchend: end,
            touchcancel: end
        });
    })();

    (function () {
        var NAME = 'gallery';
        function getState$$1(data) {
            return getPluginState(data, NAME);
        }
        function getOption(data, name, fallback) {
            return data[name] || fallback;
        }
        function load(e, data) {
            var state = getState$$1(data);
            state.images = [];
            state.offsets = [];
            state.frame = data.frame;
            state.speed = getOption(data, 'gallerySpeed', 500);
            state.opacity = getOption(data, 'galleryOpacity', 0.25);
            state.stage = getOption(data, 'galleryStage', $$1('<div></div>'));
            state.stage.empty().addClass('gallery-stage').prependTo(data.stage);
            var size = 0;
            for (var _i = 0, _a = data.images; _i < _a.length; _i++) {
                var image = _a[_i];
                var naturalSize$$1 = naturalSize(image);
                var scale = data.height / naturalSize$$1.height;
                var img = $$1(image);
                state.stage.append(img);
                state.images.push(img);
                state.offsets.push(-size + (data.width - image.width * scale) / 2);
                size += data.width;
                img.css({
                    'max-width': 'initial',
                    opacity: state.opacity,
                    width: data.width,
                    height: data.height
                });
            }
            var innerSize = getInnerSize(data);
            var outerSize = data.responsive ? getComputedSize(data) : getOuterSize(data);
            var layout = getInnerLayout(data.sizeMode, innerSize, outerSize);
            state.stage.css(layout).css({ width: size, left: state.offsets[state.frame] });
            state.images[state.frame].animate({ opacity: 1 }, { duration: state.speed });
        }
        function draw(e, data) {
            var state = getState$$1(data);
            var input = getInputState(data);
            var isDragging = is(data, 'dragging');
            if (state.frame !== data.frame && !isDragging) {
                state.stage.stop(true, false).animate({ left: state.offsets[data.frame] }, { duration: state.speed });
                state.images[state.frame].animate({ opacity: state.opacity }, { duration: state.speed });
                state.frame = data.frame;
                state.images[state.frame].animate({ opacity: 1 }, { duration: state.speed });
                state.stage.animate({ left: state.offsets[state.frame] });
            }
            else if (isDragging || state.dX !== input.dX) {
                state.dX = input.dX;
                state.ddX = input.ddX;
                state.stage.stop(true, true).css({ left: state.offsets[state.frame] + state.dX });
            }
        }
        registerPlugin(NAME, {
            name: NAME,
            onLoad: load,
            onDraw: draw
        });
    })();

    (function () {
        var NAME = 'panorama';
        function getState$$1(data) {
            return getPluginState(data, NAME);
        }
        function onLoad(e, data) {
            var state = getState$$1(data);
            var sprite = data.metrics[0];
            if (!sprite) {
                return;
            }
            if (data.orientation === 'horizontal') {
                state.scale = data.target.innerHeight() / sprite.sampledHeight;
                data.frames = sprite.sampledWidth;
            }
            else {
                state.scale = data.target.innerWidth() / sprite.sampledWidth;
                data.frames = sprite.sampledHeight;
            }
            var width = Math.floor(sprite.sampledWidth * state.scale);
            var height = Math.floor(sprite.sampledHeight * state.scale);
            data.stage.css({
                'background-image': "url(" + data.source[sprite.id] + ")",
                'background-repeat': 'repeat-both',
                // set custom background size to enable responsive rendering
                '-webkit-background-size': width + "px " + height + "px",
                '-moz-background-size': width + "px " + height + "px",
                '-o-background-size': width + "px " + height + "px",
                'background-size': width + "px " + height + "px" /* Chrome, Firefox 4+, IE 9+, Opera, Safari 5+ */
            });
        }
        function onDraw(e, data) {
            var state = getState$$1(data);
            var px = data.orientation === 'horizontal' ? 1 : 0;
            var py = px ? 0 : 1;
            var offset = data.frame % data.frames;
            var left = Math.round(px * offset * state.scale);
            var top = Math.round(py * offset * state.scale);
            data.stage.css({ 'background-position': left + "px " + top + "px" });
        }
        registerPlugin(NAME, {
            name: NAME,
            onLoad: onLoad,
            onDraw: onDraw
        });
    })();

    (function () {
        var NAME = 'zoom';
        function getState$$1(data) {
            return getPluginState(data, NAME);
        }
        function getOption(data, name, fallback) {
            return data[name] || fallback;
        }
        function onInit(e, data) {
            var state = getState$$1(data);
            state.source = getOption(data, 'zoomSource', data.source);
            state.doubleClickTime = getOption(data, 'zoomDoubleClickTime', 500);
            state.stage = state.stage || $$1("<div class='zoom-stage'></div>");
            state.stage.css({
                width: '100%',
                height: '100%',
                top: 0,
                left: 0,
                bottom: 0,
                right: 0,
                position: 'absolute'
            })
                .appendTo(data.target)
                .hide();
        }
        function onDestroy(e, data) {
            var state = getState$$1(data);
            if (state.stage) {
                state.stage.remove();
                delete state.stage;
            }
        }
        function updateInput$$1(e, data) {
            var state = getState$$1(data);
            if (!state.stage.is(':visible')) {
                return;
            }
            e.preventDefault();
            // hack into drag/move module and disable dragging
            // prevents frame change during zoom mode
            flag(data, 'dragging', false);
            // grab touch/cursor position
            var cursor = getCursorPosition(e);
            // normalize cursor position into [0:1] range
            var x = cursor.x / data.width;
            var y = cursor.y / data.height;
            if (state.oldX == null) {
                state.oldX = x;
                state.oldY = y;
            }
            if (state.currentX == null) {
                state.currentX = x;
                state.currentY = y;
            }
            // calculate move delta since last frame and remember current position
            var dx = x - state.oldX;
            var dy = y - state.oldY;
            state.oldX = x;
            state.oldY = y;
            // invert drag direction for touch events to enable 'natural' scrolling
            if (e.type.match(/touch/)) {
                dx = -dx;
                dy = -dy;
            }
            // accumulate display coordinates
            state.currentX = clamp(state.currentX + dx, 0, 1);
            state.currentY = clamp(state.currentY + dy, 0, 1);
            updateFrame(data, data.frame, data.lane);
        }
        function onClick(e, data) {
            e.preventDefault();
            var state = getState$$1(data);
            // simulate double click
            var clickTime = new Date().getTime();
            if (!state.clickTime) {
                // on first click
                state.clickTime = clickTime;
                return;
            }
            // on second click
            var timeDelta = clickTime - state.clickTime;
            if (timeDelta > state.doubleClickTime) {
                // took too long, back to first click
                state.clickTime = clickTime;
                return;
            }
            // on valid double click
            state.clickTime = undefined;
            if (toggleZoom(data)) {
                updateInput$$1(e, data);
            }
        }
        function onMove(e, data) {
            var state = getState$$1(data);
            if (state.stage.is(':visible')) {
                updateInput$$1(e, data);
            }
        }
        function onDraw(e, data) {
            var state = getState$$1(data);
            // calculate the frame index
            var index = data.lane * data.frames + data.frame;
            // get the zoom image. Use original frames as fallback. This won't work for spritesheets
            var source = state.source[index];
            var spec = findSpecs(data.metrics, data.frames, data.frame, data.lane);
            // get display position
            var x = state.currentX;
            var y = state.currentY;
            // fallback to centered position
            if (x == null) {
                x = state.currentX = 0.5;
                y = state.currentY = 0.5;
            }
            if (source) {
                // scale up from [0:1] to [0:100] range
                x = Math.floor(x * 100);
                y = Math.floor(y * 100);
                // update background image and position
                state.stage.css({
                    'background-repeat': 'no-repeat',
                    'background-image': "url('" + source + "')",
                    'background-position': x + "% " + y + "%"
                });
            }
            else if (spec.sheet && spec.sprite) {
                var sprite = spec.sprite;
                var sheet = spec.sheet;
                var src = data.source[sheet.id];
                var left = -Math.floor(sprite.sampledX + x * (sprite.sampledWidth - data.width));
                var top_1 = -Math.floor(sprite.sampledY + y * (sprite.sampledHeight - data.height));
                var width = sheet.sampledWidth;
                var height = sheet.sampledHeight;
                state.stage.css({
                    'background-image': "url('" + src + "')",
                    'background-position': left + "px " + top_1 + "px",
                    'background-repeat': 'no-repeat',
                    // set custom background size to enable responsive rendering
                    '-webkit-background-size': width + "px " + height + "px",
                    '-moz-background-size': width + "px " + height + "px",
                    '-o-background-size': width + "px " + height + "px",
                    'background-size': width + "px " + height + "px" /* Chrome, Firefox 4+, IE 9+, Opera, Safari 5+ */
                });
            }
        }
        function toggleZoom(data) {
            var state = getState$$1(data);
            if (!state.stage) {
                throw new Error('zoom module is not initialized or is not available.');
            }
            if (state.stage.is(':visible')) {
                state.stage.fadeOut();
                data.stage.fadeIn();
            }
            else {
                state.stage.fadeIn();
                data.stage.fadeOut();
                return true;
            }
            return false;
        }
        registerPlugin(NAME, {
            name: NAME,
            mousedown: onClick,
            touchstart: onClick,
            mousemove: onMove,
            touchmove: onMove,
            onInit: onInit,
            onDestroy: onDestroy,
            onDraw: onDraw
        });
        extendApi({
            toggleZoom: function () { toggleZoom(this.data); } // tslint:disable-line
        });
    })();

    var Utils = _Utils;

    exports.Utils = Utils;
    exports.sourceArray = sourceArray;
    exports.Api = Api;
    exports.extendApi = extendApi;
    exports.instances = instances;
    exports.applyEvents = applyEvents;
    exports.boot = boot;
    exports.create = create;
    exports.createOrUpdate = createOrUpdate;
    exports.destroy = destroy;
    exports.namespace = namespace;
    exports.eventNames = eventNames;
    exports.callbackNames = callbackNames;
    exports.eventsToPrevent = eventsToPrevent;
    exports.defaults = defaults;
    exports.getInputState = getInputState;
    exports.updateInput = updateInput;
    exports.resetInput = resetInput;
    exports.applyLayout = applyLayout;
    exports.getPlaybackState = getPlaybackState;
    exports.updateFrame = updateFrame;
    exports.stopAnimation = stopAnimation;
    exports.applyAnimation = applyAnimation;
    exports.startAnimation = startAnimation;
    exports.registerPlugin = registerPlugin;
    exports.registerModule = registerModule;
    exports.getPlugin = getPlugin;
    exports.applyPlugins = applyPlugins;
    exports.getState = getState;
    exports.getPluginState = getPluginState;
    exports.is = is;
    exports.flag = flag;

    Object.defineProperty(exports, '__esModule', { value: true });

})));
//# sourceMappingURL=spritespin.js.map