/*! rangeslider.js - v2.3.2 | © 2018 @andreruffert | MIT license | github.com/andreruffert/rangeslider.js */ (function(factory) {

'use strict';

if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(['jquery'], factory);
} else if (typeof exports === 'object') {
    // CommonJS
    module.exports = factory(require('jquery'));
} else {
    // Browser globals
    factory(jQuery);
}

}(function($) {

'use strict';

// Polyfill Number.isNaN(value)
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
Number.isNaN = Number.isNaN || function(value) {
    return typeof value === 'number' && value !== value;
};

/**
 * Range feature detection
 * @return {Boolean}
 */
function supportsRange() {
    var input = document.createElement('input');
    input.setAttribute('type', 'range');
    return input.type !== 'text';
}

var pluginName = 'rangeslider',
    pluginIdentifier = 0,
    hasInputRangeSupport = supportsRange(),
    defaults = {
        polyfill: true,
        orientation: 'horizontal',
        rangeClass: 'rangeslider',
        disabledClass: 'rangeslider--disabled',
        activeClass: 'rangeslider--active',
        horizontalClass: 'rangeslider--horizontal',
        verticalClass: 'rangeslider--vertical',
        fillClass: 'rangeslider__fill',
        handleClass: 'rangeslider__handle',
        startEvent: ['mousedown', 'touchstart', 'pointerdown'],
        moveEvent: ['mousemove', 'touchmove', 'pointermove'],
        endEvent: ['mouseup', 'touchend', 'pointerup']
    },
    constants = {
        orientation: {
            horizontal: {
                dimension: 'width',
                direction: 'left',
                directionStyle: 'left',
                coordinate: 'x'
            },
            vertical: {
                dimension: 'height',
                direction: 'top',
                directionStyle: 'bottom',
                coordinate: 'y'
            }
        }
    };

/**
 * Delays a function for the given number of milliseconds, and then calls
 * it with the arguments supplied.
 *
 * @param  {Function} fn   [description]
 * @param  {Number}   wait [description]
 * @return {Function}
 */
function delay(fn, wait) {
    var args = Array.prototype.slice.call(arguments, 2);
    return setTimeout(function(){ return fn.apply(null, args); }, wait);
}

/**
 * Returns a debounced function that will make sure the given
 * function is not triggered too much.
 *
 * @param  {Function} fn Function to debounce.
 * @param  {Number}   debounceDuration OPTIONAL. The amount of time in milliseconds for which we will debounce the function. (defaults to 100ms)
 * @return {Function}
 */
function debounce(fn, debounceDuration) {
    debounceDuration = debounceDuration || 100;
    return function() {
        if (!fn.debouncing) {
            var args = Array.prototype.slice.apply(arguments);
            fn.lastReturnVal = fn.apply(window, args);
            fn.debouncing = true;
        }
        clearTimeout(fn.debounceTimeout);
        fn.debounceTimeout = setTimeout(function(){
            fn.debouncing = false;
        }, debounceDuration);
        return fn.lastReturnVal;
    };
}

/**
 * Check if a `element` is visible in the DOM
 *
 * @param  {Element}  element
 * @return {Boolean}
 */
function isHidden(element) {
    return (
        element && (
            element.offsetWidth === 0 ||
            element.offsetHeight === 0 ||
            // Also Consider native `<details>` elements.
            element.open === false
        )
    );
}

/**
 * Get hidden parentNodes of an `element`
 *
 * @param  {Element} element
 * @return {[type]}
 */
function getHiddenParentNodes(element) {
    var parents = [],
        node    = element.parentNode;

    while (isHidden(node)) {
        parents.push(node);
        node = node.parentNode;
    }
    return parents;
}

/**
 * Returns dimensions for an element even if it is not visible in the DOM.
 *
 * @param  {Element} element
 * @param  {String}  key     (e.g. offsetWidth …)
 * @return {Number}
 */
function getDimension(element, key) {
    var hiddenParentNodes       = getHiddenParentNodes(element),
        hiddenParentNodesLength = hiddenParentNodes.length,
        inlineStyle             = [],
        dimension               = element[key];

    // Used for native `<details>` elements
    function toggleOpenProperty(element) {
        if (typeof element.open !== 'undefined') {
            element.open = (element.open) ? false : true;
        }
    }

    if (hiddenParentNodesLength) {
        for (var i = 0; i < hiddenParentNodesLength; i++) {

            // Cache style attribute to restore it later.
            inlineStyle[i] = hiddenParentNodes[i].style.cssText;

            // visually hide
            if (hiddenParentNodes[i].style.setProperty) {
                hiddenParentNodes[i].style.setProperty('display', 'block', 'important');
            } else {
                hiddenParentNodes[i].style.cssText += ';display: block !important';
            }
            hiddenParentNodes[i].style.height = '0';
            hiddenParentNodes[i].style.overflow = 'hidden';
            hiddenParentNodes[i].style.visibility = 'hidden';
            toggleOpenProperty(hiddenParentNodes[i]);
        }

        // Update dimension
        dimension = element[key];

        for (var j = 0; j < hiddenParentNodesLength; j++) {

            // Restore the style attribute
            hiddenParentNodes[j].style.cssText = inlineStyle[j];
            toggleOpenProperty(hiddenParentNodes[j]);
        }
    }
    return dimension;
}

/**
 * Returns the parsed float or the default if it failed.
 *
 * @param  {String}  str
 * @param  {Number}  defaultValue
 * @return {Number}
 */
function tryParseFloat(str, defaultValue) {
    var value = parseFloat(str);
    return Number.isNaN(value) ? defaultValue : value;
}

/**
 * Capitalize the first letter of string
 *
 * @param  {String} str
 * @return {String}
 */
function ucfirst(str) {
    return str.charAt(0).toUpperCase() + str.substr(1);
}

/**
 * Plugin
 * @param {String} element
 * @param {Object} options
 */
function Plugin(element, options) {
    this.$window            = $(window);
    this.$document          = $(document);
    this.$element           = $(element);
    this.options            = $.extend( {}, defaults, options );
    this.polyfill           = this.options.polyfill;
    this.orientation        = this.$element[0].getAttribute('data-orientation') || this.options.orientation;
    this.onInit             = this.options.onInit;
    this.onSlide            = this.options.onSlide;
    this.onSlideEnd         = this.options.onSlideEnd;
    this.DIMENSION          = constants.orientation[this.orientation].dimension;
    this.DIRECTION          = constants.orientation[this.orientation].direction;
    this.DIRECTION_STYLE    = constants.orientation[this.orientation].directionStyle;
    this.COORDINATE         = constants.orientation[this.orientation].coordinate;

    // Plugin should only be used as a polyfill
    if (this.polyfill) {
        // Input range support?
        if (hasInputRangeSupport) { return false; }
    }

    this.identifier = 'js-' + pluginName + '-' +(pluginIdentifier++);
    this.startEvent = this.options.startEvent.join('.' + this.identifier + ' ') + '.' + this.identifier;
    this.moveEvent  = this.options.moveEvent.join('.' + this.identifier + ' ') + '.' + this.identifier;
    this.endEvent   = this.options.endEvent.join('.' + this.identifier + ' ') + '.' + this.identifier;
    this.toFixed    = (this.step + '').replace('.', '').length - 1;
    this.$fill      = $('<div class="' + this.options.fillClass + '" />');
    this.$handle    = $('<div class="' + this.options.handleClass + '" />');
    this.$range     = $('<div class="' + this.options.rangeClass + ' ' + this.options[this.orientation + 'Class'] + '" id="' + this.identifier + '" />').insertAfter(this.$element).prepend(this.$fill, this.$handle);

    // visually hide the input
    this.$element.css({
        'position': 'absolute',
        'width': '1px',
        'height': '1px',
        'overflow': 'hidden',
        'opacity': '0'
    });

    // Store context
    this.handleDown = $.proxy(this.handleDown, this);
    this.handleMove = $.proxy(this.handleMove, this);
    this.handleEnd  = $.proxy(this.handleEnd, this);

    this.init();

    // Attach Events
    var _this = this;
    this.$window.on('resize.' + this.identifier, debounce(function() {
        // Simulate resizeEnd event.
        delay(function() { _this.update(false, false); }, 300);
    }, 20));

    this.$document.on(this.startEvent, '#' + this.identifier + ':not(.' + this.options.disabledClass + ')', this.handleDown);

    // Listen to programmatic value changes
    this.$element.on('change.' + this.identifier, function(e, data) {
        if (data && data.origin === _this.identifier) {
            return;
        }

        var value = e.target.value,
            pos = _this.getPositionFromValue(value);
        _this.setPosition(pos);
    });
}

Plugin.prototype.init = function() {
    this.update(true, false);

    if (this.onInit && typeof this.onInit === 'function') {
        this.onInit();
    }
};

Plugin.prototype.update = function(updateAttributes, triggerSlide) {
    updateAttributes = updateAttributes || false;

    if (updateAttributes) {
        this.min    = tryParseFloat(this.$element[0].getAttribute('min'), 0);
        this.max    = tryParseFloat(this.$element[0].getAttribute('max'), 100);
        this.value  = tryParseFloat(this.$element[0].value, Math.round(this.min + (this.max-this.min)/2));
        this.step   = tryParseFloat(this.$element[0].getAttribute('step'), 1);
    }

    this.handleDimension    = getDimension(this.$handle[0], 'offset' + ucfirst(this.DIMENSION));
    this.rangeDimension     = getDimension(this.$range[0], 'offset' + ucfirst(this.DIMENSION));
    this.maxHandlePos       = this.rangeDimension - this.handleDimension;
    this.grabPos            = this.handleDimension / 2;
    this.position           = this.getPositionFromValue(this.value);

    // Consider disabled state
    if (this.$element[0].disabled) {
        this.$range.addClass(this.options.disabledClass);
    } else {
        this.$range.removeClass(this.options.disabledClass);
    }

    this.setPosition(this.position, triggerSlide);
};

Plugin.prototype.handleDown = function(e) {
    e.preventDefault();

    // Only respond to mouse main button clicks (usually the left button)
    if (e.button && e.button !== 0) { return; }

    this.$document.on(this.moveEvent, this.handleMove);
    this.$document.on(this.endEvent, this.handleEnd);

    // add active class because Firefox is ignoring
    // the handle:active pseudo selector because of `e.preventDefault();`
    this.$range.addClass(this.options.activeClass);

    // If we click on the handle don't set the new position
    if ((' ' + e.target.className + ' ').replace(/[\n\t]/g, ' ').indexOf(this.options.handleClass) > -1) {
        return;
    }

    var pos         = this.getRelativePosition(e),
        rangePos    = this.$range[0].getBoundingClientRect()[this.DIRECTION],
        handlePos   = this.getPositionFromNode(this.$handle[0]) - rangePos,
        setPos      = (this.orientation === 'vertical') ? (this.maxHandlePos - (pos - this.grabPos)) : (pos - this.grabPos);

    this.setPosition(setPos);

    if (pos >= handlePos && pos < handlePos + this.handleDimension) {
        this.grabPos = pos - handlePos;
    }
};

Plugin.prototype.handleMove = function(e) {
    e.preventDefault();
    var pos = this.getRelativePosition(e);
    var setPos = (this.orientation === 'vertical') ? (this.maxHandlePos - (pos - this.grabPos)) : (pos - this.grabPos);
    this.setPosition(setPos);
};

Plugin.prototype.handleEnd = function(e) {
    e.preventDefault();
    this.$document.off(this.moveEvent, this.handleMove);
    this.$document.off(this.endEvent, this.handleEnd);

    this.$range.removeClass(this.options.activeClass);

    // Ok we're done fire the change event
    this.$element.trigger('change', { origin: this.identifier });

    if (this.onSlideEnd && typeof this.onSlideEnd === 'function') {
        this.onSlideEnd(this.position, this.value);
    }
};

Plugin.prototype.cap = function(pos, min, max) {
    if (pos < min) { return min; }
    if (pos > max) { return max; }
    return pos;
};

Plugin.prototype.setPosition = function(pos, triggerSlide) {
    var value, newPos;

    if (triggerSlide === undefined) {
        triggerSlide = true;
    }

    // Snapping steps
    value = this.getValueFromPosition(this.cap(pos, 0, this.maxHandlePos));
    newPos = this.getPositionFromValue(value);

    // Update ui
    this.$fill[0].style[this.DIMENSION] = (newPos + this.grabPos) + 'px';
    this.$handle[0].style[this.DIRECTION_STYLE] = newPos + 'px';
    this.setValue(value);

    // Update globals
    this.position = newPos;
    this.value = value;

    if (triggerSlide && this.onSlide && typeof this.onSlide === 'function') {
        this.onSlide(newPos, value);
    }
};

// Returns element position relative to the parent
Plugin.prototype.getPositionFromNode = function(node) {
    var i = 0;
    while (node !== null) {
        i += node.offsetLeft;
        node = node.offsetParent;
    }
    return i;
};

Plugin.prototype.getRelativePosition = function(e) {
    // Get the offset DIRECTION relative to the viewport
    var ucCoordinate = ucfirst(this.COORDINATE),
        rangePos = this.$range[0].getBoundingClientRect()[this.DIRECTION],
        pageCoordinate = 0;

    if (typeof e.originalEvent['client' + ucCoordinate] !== 'undefined') {
        pageCoordinate = e.originalEvent['client' + ucCoordinate];
    }
    else if (
      e.originalEvent.touches &&
      e.originalEvent.touches[0] &&
      typeof e.originalEvent.touches[0]['client' + ucCoordinate] !== 'undefined'
    ) {
        pageCoordinate = e.originalEvent.touches[0]['client' + ucCoordinate];
    }
    else if(e.currentPoint && typeof e.currentPoint[this.COORDINATE] !== 'undefined') {
        pageCoordinate = e.currentPoint[this.COORDINATE];
    }

    return pageCoordinate - rangePos;
};

Plugin.prototype.getPositionFromValue = function(value) {
    var percentage, pos;
    percentage = (value - this.min)/(this.max - this.min);
    pos = (!Number.isNaN(percentage)) ? percentage * this.maxHandlePos : 0;
    return pos;
};

Plugin.prototype.getValueFromPosition = function(pos) {
    var percentage, value;
    percentage = ((pos) / (this.maxHandlePos || 1));
    value = this.step * Math.round(percentage * (this.max - this.min) / this.step) + this.min;
    return Number((value).toFixed(this.toFixed));
};

Plugin.prototype.setValue = function(value) {
    if (value === this.value && this.$element[0].value !== '') {
        return;
    }

    // Set the new value and fire the `input` event
    this.$element
        .val(value)
        .trigger('input', { origin: this.identifier });
};

Plugin.prototype.destroy = function() {
    this.$document.off('.' + this.identifier);
    this.$window.off('.' + this.identifier);

    this.$element
        .off('.' + this.identifier)
        .removeAttr('style')
        .removeData('plugin_' + pluginName);

    // Remove the generated markup
    if (this.$range && this.$range.length) {
        this.$range[0].parentNode.removeChild(this.$range[0]);
    }
};

// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[pluginName] = function(options) {
    var args = Array.prototype.slice.call(arguments, 1);

    return this.each(function() {
        var $this = $(this),
            data  = $this.data('plugin_' + pluginName);

        // Create a new instance.
        if (!data) {
            $this.data('plugin_' + pluginName, (data = new Plugin(this, options)));
        }

        // Make it possible to access methods from public.
        // e.g `$element.rangeslider('method');`
        if (typeof options === 'string') {
            data[options].apply(data, args);
        }
    });
};

return 'rangeslider.js is available in jQuery context e.g $(selector).rangeslider(options);';

}));