diff --git a/README.md b/README.md index 7f335e4b..cbec0ada 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ An extensive documentation, including **examples**, **options** and **configurat Changelog --------- -### 9.0.0 +### 9.0.0 (*2016-09-26*) - Added: Support for **more than 2 handles**; - Added: `format` option can be updated (#641); - Added: `reset` method the return slider to start values (#673); diff --git a/distribute/nouislider.css b/distribute/nouislider.css index 12bfd8fd..ea91ca0e 100644 --- a/distribute/nouislider.css +++ b/distribute/nouislider.css @@ -1,4 +1,4 @@ -/*! nouislider - 8.5.1 - 2016-04-24 16:26:42 */ +/*! nouislider - 9.0.0 - 2016-09-29 21:44:02 */ /* Functional styling; @@ -27,25 +27,26 @@ position: relative; z-index: 1; /* Fix 401 */ } -.noUi-origin { +.noUi-connect { position: absolute; right: 0; top: 0; left: 0; bottom: 0; } +.noUi-origin { + position: absolute; + height: 0; + width: 0; +} .noUi-handle { position: relative; z-index: 1; } -.noUi-stacking .noUi-handle { -/* This class is applied to the lower origin when - its values is > 50%. */ - z-index: 10; -} +.noUi-state-tap .noUi-connect, .noUi-state-tap .noUi-origin { --webkit-transition: left 0.3s, top 0.3s; - transition: left 0.3s, top 0.3s; +-webkit-transition: top 0.3s, right 0.3s, bottom 0.3s, left 0.3s; + transition: top 0.3s, right 0.3s, bottom 0.3s, left 0.3s; } .noUi-state-drag * { cursor: inherit !important; @@ -83,9 +84,11 @@ /* Styling; */ -.noUi-background { +.noUi-target { background: #FAFAFA; - box-shadow: inset 0 1px 1px #f0f0f0; + border-radius: 4px; + border: 1px solid #D3D3D3; + box-shadow: inset 0 1px 1px #F0F0F0, 0 3px 6px -5px #BBB; } .noUi-connect { background: #3FB8AF; @@ -93,17 +96,6 @@ -webkit-transition: background 450ms; transition: background 450ms; } -.noUi-origin { - border-radius: 2px; -} -.noUi-target { - border-radius: 4px; - border: 1px solid #D3D3D3; - box-shadow: inset 0 1px 1px #F0F0F0, 0 3px 6px -5px #BBB; -} -.noUi-target.noUi-connect { - box-shadow: inset 0 0 3px rgba(51,51,51,0.45), 0 3px 6px -5px #BBB; -} /* Handles and cursors; */ @@ -157,11 +149,12 @@ /* Disabled state; */ -[disabled].noUi-connect, + [disabled] .noUi-connect { background: #B8B8B8; } -[disabled].noUi-origin, +[disabled].noUi-target, +[disabled].noUi-handle, [disabled] .noUi-handle { cursor: not-allowed; } @@ -243,8 +236,8 @@ left: 100%; } .noUi-value-vertical { - -webkit-transform: translate3d(0,-50%,0); - transform: translate3d(0,-50%,0); + -webkit-transform: translate3d(0,50%,0); + transform: translate3d(0,50%,0); padding-left: 25px; } @@ -266,19 +259,19 @@ border: 1px solid #D9D9D9; border-radius: 3px; background: #fff; + color: #000; padding: 5px; text-align: center; } - -.noUi-horizontal .noUi-handle-lower .noUi-tooltip { - top: -32px; -} -.noUi-horizontal .noUi-handle-upper .noUi-tooltip { - bottom: -32px; -} -.noUi-vertical .noUi-handle-lower .noUi-tooltip { - left: 120%; -} -.noUi-vertical .noUi-handle-upper .noUi-tooltip { - right: 120%; +.noUi-horizontal .noUi-tooltip { +-webkit-transform: translate(-50%, 0); + transform: translate(-50%, 0); + left: 50%; + bottom: 120%; +} +.noUi-vertical .noUi-tooltip { +-webkit-transform: translate(0, -50%); + transform: translate(0, -50%); + top: 50%; + right: 120%; } diff --git a/distribute/nouislider.js b/distribute/nouislider.js index 4b6f6e27..cd360fb4 100644 --- a/distribute/nouislider.js +++ b/distribute/nouislider.js @@ -1,4 +1,4 @@ -/*! nouislider - 8.5.1 - 2016-04-24 16:00:29 */ +/*! nouislider - 9.0.0 - 2016-09-29 21:44:02 */ (function (factory) { @@ -23,8 +23,16 @@ 'use strict'; + // Creates a node, adds it to target, returns the new node. + function addNodeTo ( target, className ) { + var div = document.createElement('div'); + addClass(div, className); + target.appendChild(div); + return div; + } + // Removes duplicates from an array. - function unique(array) { + function unique ( array ) { return array.filter(function(a){ return !this[a] ? this[a] = true : false; }, {}); @@ -36,7 +44,7 @@ } // Current position of an element relative to the document. - function offset ( elem ) { + function offset ( elem, orientation ) { var rect = elem.getBoundingClientRect(), doc = elem.ownerDocument, @@ -50,10 +58,7 @@ pageOffset.x = 0; } - return { - top: rect.top + pageOffset.y - docElem.clientTop, - left: rect.left + pageOffset.x - docElem.clientLeft - }; + return orientation ? (rect.top + pageOffset.y - docElem.clientTop) : (rect.left + pageOffset.x - docElem.clientLeft); } // Checks whether a value is numerical. @@ -63,10 +68,12 @@ // Sets a class and removes it after [duration] ms. function addClassFor ( element, className, duration ) { + if (duration > 0) { addClass(element, className); - setTimeout(function(){ - removeClass(element, className); - }, duration); + setTimeout(function(){ + removeClass(element, className); + }, duration); + } } // Limits a value to 0 - 100 @@ -75,12 +82,14 @@ } // Wraps a variable as an array, if it isn't one yet. + // Note that an input array is returned by reference! function asArray ( a ) { return Array.isArray(a) ? a : [a]; } // Counts decimals function countDecimals ( numStr ) { + numStr = String(numStr); var pieces = numStr.split("."); return pieces.length > 1 ? pieces[1].length : 0; } @@ -296,6 +305,8 @@ } else { that.xSteps.push( isNaN(value[1]) ? false : value[1] ); } + + that.xHighestCompleteStep.push(0); } function handleStepPoint ( i, n, that ) { @@ -312,6 +323,12 @@ ], n) / subRangeRatio ( that.xPct[i], that.xPct[i+1] ); + + var totalSteps = (that.xVal[i+1] - that.xVal[i]) / that.xNumSteps[i]; + var highestStep = Math.ceil(Number(totalSteps.toFixed(3)) - 1); + var step = that.xVal[i] + (that.xNumSteps[i] * highestStep); + + that.xHighestCompleteStep[i] = step; } @@ -326,6 +343,7 @@ this.xVal = []; this.xSteps = [ singleStep || false ]; this.xNumSteps = [ false ]; + this.xHighestCompleteStep = []; this.snap = snap; this.direction = direction; @@ -363,6 +381,13 @@ } Spectrum.prototype.getMargin = function ( value ) { + + var step = this.xNumSteps[0]; + + if ( step && (value % step) ) { + throw new Error("noUiSlider: 'limit' and 'margin' must be divisible by step."); + } + return this.xPct.length === 2 ? fromPercentage(this.xVal, value) : false; }; @@ -370,48 +395,37 @@ value = toStepping( this.xVal, this.xPct, value ); - // Invert the value if this is a right-to-left slider. - if ( this.direction ) { - value = 100 - value; - } - return value; }; Spectrum.prototype.fromStepping = function ( value ) { - // Invert the value if this is a right-to-left slider. - if ( this.direction ) { - value = 100 - value; - } - return fromStepping( this.xVal, this.xPct, value ); }; Spectrum.prototype.getStep = function ( value ) { - // Find the proper step for rtl sliders by search in inverse direction. - // Fixes issue #262. - if ( this.direction ) { - value = 100 - value; - } - value = getStep(this.xPct, this.xSteps, this.snap, value ); - if ( this.direction ) { - value = 100 - value; - } - return value; }; - Spectrum.prototype.getApplicableStep = function ( value ) { + Spectrum.prototype.getNearbySteps = function ( value ) { - // If the value is 100%, return the negative step twice. - var j = getJ(value, this.xPct), offset = value === 100 ? 2 : 1; - return [this.xNumSteps[j-2], this.xVal[j-offset], this.xNumSteps[j-offset]]; + var j = getJ(value, this.xPct); + + return { + stepBefore: { startValue: this.xVal[j-2], step: this.xNumSteps[j-2], highestStep: this.xHighestCompleteStep[j-2] }, + thisStep: { startValue: this.xVal[j-1], step: this.xNumSteps[j-1], highestStep: this.xHighestCompleteStep[j-1] }, + stepAfter: { startValue: this.xVal[j-0], step: this.xNumSteps[j-0], highestStep: this.xHighestCompleteStep[j-0] } + }; }; + Spectrum.prototype.countStepDecimals = function () { + var stepDecimals = this.xNumSteps.map(countDecimals); + return Math.max.apply(null, stepDecimals); + }; + // Outside testing Spectrum.prototype.convert = function ( value ) { return this.getStep(this.toStepping(value)); @@ -471,7 +485,7 @@ // Validate input. Values aren't tested, as the public .val method // will always provide a valid location. - if ( !Array.isArray( entry ) || !entry.length || entry.length > 2 ) { + if ( !Array.isArray( entry ) || !entry.length ) { throw new Error("noUiSlider: 'start' option is incorrect."); } @@ -514,17 +528,27 @@ function testConnect ( parsed, entry ) { - if ( entry === 'lower' && parsed.handles === 1 ) { - parsed.connect = 1; - } else if ( entry === 'upper' && parsed.handles === 1 ) { - parsed.connect = 2; - } else if ( entry === true && parsed.handles === 2 ) { - parsed.connect = 3; - } else if ( entry === false ) { - parsed.connect = 0; - } else { + var connect = [false]; + var i; + + if ( entry === true || entry === false ) { + + for ( i = 1; i < parsed.handles; i++ ) { + connect.push(entry); + } + + connect.push(false); + } + + else if ( !Array.isArray( entry ) || !entry.length || entry.length !== parsed.handles + 1 ) { throw new Error("noUiSlider: 'connect' option doesn't match handle count."); } + + else { + connect = entry; + } + + parsed.connect = connect; } function testOrientation ( parsed, entry ) { @@ -569,8 +593,8 @@ parsed.limit = parsed.spectrum.getMargin(entry); - if ( !parsed.limit ) { - throw new Error("noUiSlider: 'limit' option is only supported on linear sliders."); + if ( !parsed.limit || parsed.handles < 2 ) { + throw new Error("noUiSlider: 'limit' option is only supported on linear sliders with 2 or more handles."); } } @@ -585,7 +609,6 @@ break; case 'rtl': parsed.dir = 1; - parsed.connect = [0,2,1,3][parsed.connect]; break; default: throw new Error("noUiSlider: 'direction' option was not recognized."); @@ -601,15 +624,20 @@ // Check if the string contains any keywords. // None are required. - var tap = entry.indexOf('tap') >= 0, - drag = entry.indexOf('drag') >= 0, - fixed = entry.indexOf('fixed') >= 0, - snap = entry.indexOf('snap') >= 0, - hover = entry.indexOf('hover') >= 0; + var tap = entry.indexOf('tap') >= 0; + var drag = entry.indexOf('drag') >= 0; + var fixed = entry.indexOf('fixed') >= 0; + var snap = entry.indexOf('snap') >= 0; + var hover = entry.indexOf('hover') >= 0; + + if ( fixed ) { - // Fix #472 - if ( drag && !parsed.connect ) { - throw new Error("noUiSlider: 'drag' behaviour must be used with 'connect': true."); + if ( parsed.handles !== 2 ) { + throw new Error("noUiSlider: 'fixed' behaviour must be used with 2 handles"); + } + + // Use margin to enforce fixed state + testMargin(parsed, parsed.start[1] - parsed.start[0]); } parsed.events = { @@ -623,19 +651,20 @@ function testTooltips ( parsed, entry ) { - var i; - if ( entry === false ) { return; - } else if ( entry === true ) { + } + + else if ( entry === true ) { parsed.tooltips = []; - for ( i = 0; i < parsed.handles; i++ ) { + for ( var i = 0; i < parsed.handles; i++ ) { parsed.tooltips.push(true); } + } - } else { + else { parsed.tooltips = asArray(entry); @@ -691,6 +720,14 @@ } } + function testUseRaf ( parsed, entry ) { + if ( entry === true || entry === false ) { + parsed.useRequestAnimationFrame = entry; + } else { + throw new Error("noUiSlider: 'useRequestAnimationFrame' option should be true (default) or false."); + } + } + // Test all developer settings and parse to assumption-safe values. function testOptions ( options ) { @@ -723,7 +760,8 @@ 'format': { r: false, t: testFormat }, 'tooltips': { r: false, t: testTooltips }, 'cssPrefix': { r: false, t: testCssPrefix }, - 'cssClasses': { r: false, t: testCssClasses } + 'cssClasses': { r: false, t: testCssClasses }, + 'useRequestAnimationFrame': { r: false, t: testUseRaf } }; var defaults = { @@ -737,8 +775,6 @@ base: 'base', origin: 'origin', handle: 'handle', - handleLower: 'handle-lower', - handleUpper: 'handle-upper', horizontal: 'horizontal', vertical: 'vertical', background: 'background', @@ -749,7 +785,6 @@ drag: 'state-drag', tap: 'state-tap', active: 'active', - stacking: 'stacking', tooltip: 'tooltip', pips: 'pips', pipsHorizontal: 'pips-horizontal', @@ -766,7 +801,8 @@ valueNormal: 'value-normal', valueLarge: 'value-large', valueSub: 'value-sub' - } + }, + 'useRequestAnimationFrame': true }; // Run all options through a testing mechanism to ensure correct @@ -790,201 +826,120 @@ // Forward pips options parsed.pips = options.pips; + var styles = [['left', 'top'], ['right', 'bottom']]; + // Pre-define the styles. - parsed.style = parsed.ort ? 'top' : 'left'; + parsed.style = styles[parsed.dir][parsed.ort]; + parsed.styleOposite = styles[parsed.dir?0:1][parsed.ort]; return parsed; } function closure ( target, options, originalOptions ){ - var - actions = getActions( ), - // All variables local to 'closure' are prefixed with 'scope_' - scope_Target = target, - scope_Locations = [-1, -1], - scope_Base, - scope_Handles, - scope_Spectrum = options.spectrum, - scope_Values = [], - scope_Events = {}, - scope_Self; - - - // Delimit proposed values for handle positions. - function getPositions ( a, b, delimit ) { - - // Add movement to current position. - var c = a + b[0], d = a + b[1]; - - // Only alter the other position on drag, - // not on standard sliding. - if ( delimit ) { - if ( c < 0 ) { - d += Math.abs(c); - } - if ( d > 100 ) { - c -= ( d - 100 ); - } - - // Limit values to 0 and 100. - return [limit(c), limit(d)]; - } - - return [c,d]; - } - - // Provide a clean event with standardized offset values. - function fixEvent ( e, pageOffset ) { - - // Prevent scrolling and panning on touch events, while - // attempting to slide. The tap event also depends on this. - e.preventDefault(); - - // Filter the event to register the type, which can be - // touch, mouse or pointer. Offset changes need to be - // made on an event specific basis. - var touch = e.type.indexOf('touch') === 0, - mouse = e.type.indexOf('mouse') === 0, - pointer = e.type.indexOf('pointer') === 0, - x,y, event = e; - - // IE10 implemented pointer events with a prefix; - if ( e.type.indexOf('MSPointer') === 0 ) { - pointer = true; - } - - if ( touch ) { - // noUiSlider supports one movement at a time, - // so we can select the first 'changedTouch'. - x = e.changedTouches[0].pageX; - y = e.changedTouches[0].pageY; - } - - pageOffset = pageOffset || getPageOffset(); - - if ( mouse || pointer ) { - x = e.clientX + pageOffset.x; - y = e.clientY + pageOffset.y; - } - - event.pageOffset = pageOffset; - event.points = [x, y]; - event.cursor = mouse || pointer; // Fix #435 - - return event; - } - - // Append a handle to the base. - function addHandle ( direction, index ) { - - var origin = document.createElement('div'), - handle = document.createElement('div'), - classModifier = [options.cssClasses.handleLower, options.cssClasses.handleUpper]; - - if ( direction ) { - classModifier.reverse(); - } - - addClass(handle, options.cssClasses.handle); - addClass(handle, classModifier[index]); - - addClass(origin, options.cssClasses.origin); - origin.appendChild(handle); + var actions = getActions( ); + + // All variables local to 'closure' are prefixed with 'scope_' + var scope_Target = target; + var scope_Locations = []; + var scope_Base; + var scope_Handles; + var scope_HandleNumbers = []; + var scope_Connects; + var scope_Spectrum = options.spectrum; + var scope_Values = []; + var scope_Events = {}; + var scope_Self; + + + // Append a origin to the base + function addOrigin ( base, handleNumber ) { + var origin = addNodeTo(base, options.cssClasses.origin); + var handle = addNodeTo(origin, options.cssClasses.handle); + handle.setAttribute('data-handle', handleNumber); return origin; } - // Add the proper connection classes. - function addConnection ( connect, target, handles ) { + // Insert nodes for connect elements + function addConnect ( base, add ) { - // Apply the required connection classes to the elements - // that need them. Some classes are made up for several - // segments listed in the class list, to allow easy - // renaming and provide a minor compression benefit. - switch ( connect ) { - case 1: addClass(target, options.cssClasses.connect); - addClass(handles[0], options.cssClasses.background); - break; - case 3: addClass(handles[1], options.cssClasses.background); - /* falls through */ - case 2: addClass(handles[0], options.cssClasses.connect); - /* falls through */ - case 0: addClass(target, options.cssClasses.background); - break; + if ( !add ) { + return false; } + + return addNodeTo(base, options.cssClasses.connect); } // Add handles to the slider base. - function addHandles ( nrHandles, direction, base ) { + function addElements ( connectOptions, base ) { - var index, handles = []; + scope_Handles = []; + scope_Connects = []; - // Append handles. - for ( index = 0; index < nrHandles; index += 1 ) { + scope_Connects.push(addConnect(base, connectOptions[0])); + // [::::O====O====O====] + // connectOptions = [0, 1, 1, 1] + + for ( var i = 0; i < options.handles; i++ ) { // Keep a list of all added handles. - handles.push( base.appendChild(addHandle( direction, index )) ); + scope_Handles.push(addOrigin(base, i)); + scope_HandleNumbers[i] = i; + scope_Connects.push(addConnect(base, connectOptions[i + 1])); } - - return handles; } // Initialize a single slider. - function addSlider ( direction, orientation, target ) { + function addSlider ( target ) { // Apply classes and data to the target. addClass(target, options.cssClasses.target); - if ( direction === 0 ) { + if ( options.dir === 0 ) { addClass(target, options.cssClasses.ltr); } else { addClass(target, options.cssClasses.rtl); } - if ( orientation === 0 ) { + if ( options.ort === 0 ) { addClass(target, options.cssClasses.horizontal); } else { addClass(target, options.cssClasses.vertical); } - var div = document.createElement('div'); - addClass(div, options.cssClasses.base); - target.appendChild(div); - return div; + scope_Base = addNodeTo(target, options.cssClasses.base); } - function addTooltip ( handle, index ) { + function addTooltip ( handle, handleNumber ) { - if ( !options.tooltips[index] ) { + if ( !options.tooltips[handleNumber] ) { return false; } - var element = document.createElement('div'); - element.className = options.cssClasses.tooltip; - return handle.firstChild.appendChild(element); + return addNodeTo(handle.firstChild, options.cssClasses.tooltip); } // The tooltips option is a shorthand for using the 'update' event. function tooltips ( ) { - if ( options.dir ) { - options.tooltips.reverse(); - } - // Tooltips are added with options.tooltips in original order. var tips = scope_Handles.map(addTooltip); - if ( options.dir ) { - tips.reverse(); - options.tooltips.reverse(); - } + bindEvent('update', function(values, handleNumber, unencoded) { + + if ( !tips[handleNumber] ) { + return; + } - bindEvent('update', function(f, o, r) { - if ( tips[o] ) { - tips[o].innerHTML = options.tooltips[o] === true ? f[o] : options.tooltips[o].to(r[o]); + var formattedValue = values[handleNumber]; + + if ( options.tooltips[handleNumber] !== true ) { + formattedValue = options.tooltips[handleNumber].to(unencoded[handleNumber]); } + + tips[handleNumber].innerHTML = formattedValue; }); } @@ -1043,19 +998,13 @@ function closure ( target, options, originalOptions ){ return (value + increment).toFixed(7) / 1; } - var originalSpectrumDirection = scope_Spectrum.direction, - indexes = {}, + var indexes = {}, firstInRange = scope_Spectrum.xVal[0], lastInRange = scope_Spectrum.xVal[scope_Spectrum.xVal.length-1], ignoreFirst = false, ignoreLast = false, prevPct = 0; - // This function loops the spectrum in an ltr linear fashion, - // while the toStepping method is direction aware. Trick it into - // believing it is ltr. - scope_Spectrum.direction = 0; - // Create a copy of the group, sort it and filter away all duplicates. group = unique(group.slice().sort(function(a, b){ return a - b; })); @@ -1097,6 +1046,9 @@ function closure ( target, options, originalOptions ){ return; } + // Make sure step isn't 0, which would cause an infinite loop (#654) + step = Math.max(step, 0.0000001); + // Find all steps in the subrange. for ( i = low; i <= high; i = safeIncrement(i, step) ) { @@ -1144,9 +1096,6 @@ function closure ( target, options, originalOptions ){ } }); - // Reset the spectrum. - scope_Spectrum.direction = originalSpectrumDirection; - return indexes; } @@ -1190,10 +1139,6 @@ function closure ( target, options, originalOptions ){ function addSpread ( offset, values ){ - if ( scope_Spectrum.direction ) { - offset = 100 - offset; - } - // Apply the filter function, if it is set. values[1] = (values[1] && filterFunc) ? filterFunc(values[0], values[1]) : values[1]; @@ -1243,66 +1188,8 @@ function closure ( target, options, originalOptions ){ return options.ort === 0 ? (rect.width||scope_Base[alt]) : (rect.height||scope_Base[alt]); } - // External event handling - function fireEvent ( event, handleNumber, tap ) { - - var i; - - // During initialization, do not fire events. - for ( i = 0; i < options.handles; i++ ) { - if ( scope_Locations[i] === -1 ) { - return; - } - } - - if ( handleNumber !== undefined && options.handles !== 1 ) { - handleNumber = Math.abs(handleNumber - options.dir); - } - - Object.keys(scope_Events).forEach(function( targetEvent ) { - - var eventType = targetEvent.split('.')[0]; - - if ( event === eventType ) { - scope_Events[targetEvent].forEach(function( callback ) { - - callback.call( - // Use the slider public API as the scope ('this') - scope_Self, - // Return values as array, so arg_1[arg_2] is always valid. - asArray(valueGet()), - // Handle index, 0 or 1 - handleNumber, - // Unformatted slider values - asArray(inSliderOrder(Array.prototype.slice.call(scope_Values))), - // Event is fired by tap, true or false - tap || false, - // Left offset of the handle, in relation to the slider - scope_Locations - ); - }); - } - }); - } - - // Returns the input array, respecting the slider direction configuration. - function inSliderOrder ( values ) { - - // If only one handle is used, return a single value. - if ( values.length === 1 ){ - return values[0]; - } - - if ( options.dir ) { - return values.reverse(); - } - - return values; - } - - // Handler for attaching events trough a proxy. - function attach ( events, element, callback, data ) { + function attachEvent ( events, element, callback, data ) { // This function can be used to 'filter' events to the slider. // element is a node, not a nodeList @@ -1334,8 +1221,9 @@ function closure ( target, options, originalOptions ){ // Call the event handler with the event [ and additional data ]. callback ( e, data ); + }; - }, methods = []; + var methods = []; // Bind a closure on the target for every event type. events.split(' ').forEach(function( eventName ){ @@ -1346,8 +1234,180 @@ function closure ( target, options, originalOptions ){ return methods; } + // Provide a clean event with standardized offset values. + function fixEvent ( e, pageOffset ) { + + // Prevent scrolling and panning on touch events, while + // attempting to slide. The tap event also depends on this. + e.preventDefault(); + + // Filter the event to register the type, which can be + // touch, mouse or pointer. Offset changes need to be + // made on an event specific basis. + var touch = e.type.indexOf('touch') === 0, + mouse = e.type.indexOf('mouse') === 0, + pointer = e.type.indexOf('pointer') === 0, + x,y, event = e; + + // IE10 implemented pointer events with a prefix; + if ( e.type.indexOf('MSPointer') === 0 ) { + pointer = true; + } + + if ( touch ) { + + // Fix bug when user touches with two or more fingers on mobile devices. + // It's useful when you have two or more sliders on one page, + // that can be touched simultaneously. + // #649, #663, #668 + if ( event.touches.length > 1 ) { + return false; + } + + // noUiSlider supports one movement at a time, + // so we can select the first 'changedTouch'. + x = e.changedTouches[0].pageX; + y = e.changedTouches[0].pageY; + } + + pageOffset = pageOffset || getPageOffset(); + + if ( mouse || pointer ) { + x = e.clientX + pageOffset.x; + y = e.clientY + pageOffset.y; + } + + event.pageOffset = pageOffset; + event.points = [x, y]; + event.cursor = mouse || pointer; // Fix #435 + + return event; + } + + function calcPointToPercentage ( calcPoint ) { + var location = calcPoint - offset(scope_Base, options.ort); + var proposal = ( location * 100 ) / baseSize(); + return options.dir ? 100 - proposal : proposal; + } + + function getClosestHandle ( proposal ) { + + var closest = 100; + var handleNumber = false; + + scope_Handles.forEach(function(handle, index){ + + // Disabled handles are ignored + if ( handle.hasAttribute('disabled') ) { + return; + } + + var pos = Math.abs(scope_Locations[index] - proposal); + + if ( pos < closest ) { + handleNumber = index; + closest = pos; + } + }); + + return handleNumber; + } + + // Moves handle(s) by a percentage + // (bool, % to move, [% where handle started, ...], [index in scope_Handles, ...]) + function moveHandles ( upward, proposal, locations, handleNumbers ) { + + var proposals = locations.slice(); + + var b = [!upward, upward]; + var f = [upward, !upward]; + + // Copy handleNumbers so we don't change the dataset + handleNumbers = handleNumbers.slice(); + + // Check to see which handle is 'leading'. + // If that one can't move the second can't either. + if ( upward ) { + handleNumbers.reverse(); + } + + // Step 1: get the maximum percentage that any of the handles can move + if ( handleNumbers.length > 1 ) { + + handleNumbers.forEach(function(handleNumber, o) { + + var to = checkHandlePosition(proposals, handleNumber, proposals[handleNumber] + proposal, b[o], f[o]); + + // Stop if one of the handles can't move. + if ( to === false ) { + proposal = 0; + } else { + proposal = to - proposals[handleNumber]; + proposals[handleNumber] = to; + } + }); + } + + // If using one handle, check backward AND forward + else { + b = f = [true]; + } + + var state = false; + + // Step 2: Try to set the handles with the found percentage + handleNumbers.forEach(function(handleNumber, o) { + state = setHandle(handleNumber, locations[handleNumber] + proposal, b[o], f[o]) || state; + }); + + // Step 3: If a handle moved, fire events + if ( state ) { + handleNumbers.forEach(function(handleNumber){ + fireEvent('update', handleNumber); + fireEvent('slide', handleNumber); + }); + } + } + + // External event handling + function fireEvent ( eventName, handleNumber, tap ) { + + Object.keys(scope_Events).forEach(function( targetEvent ) { + + var eventType = targetEvent.split('.')[0]; + + if ( eventName === eventType ) { + scope_Events[targetEvent].forEach(function( callback ) { + + callback.call( + // Use the slider public API as the scope ('this') + scope_Self, + // Return values as array, so arg_1[arg_2] is always valid. + scope_Values.map(options.format.to), + // Handle index, 0 or 1 + handleNumber, + // Unformatted slider values + scope_Values.slice(), + // Event is fired by tap, true or false + tap || false, + // Left offset of the handle, in relation to the slider + scope_Locations.slice() + ); + }); + } + }); + } + + + // Fire 'end' when a mouse or pen leaves the document. + function documentLeave ( event, data ) { + if ( event.type === "mouseout" && event.target.nodeName === "HTML" && event.relatedTarget === null ){ + eventEnd (event, data); + } + } + // Handle movement on document for handle and range drag. - function move ( event, data ) { + function eventMove ( event, data ) { // Fix #498 // Check value of .buttons in 'start' to work around a bug in IE10 mobile (data.buttonsProperty). @@ -1355,40 +1415,23 @@ function closure ( target, options, originalOptions ){ // IE9 has .buttons and .which zero on mousemove. // Firefox breaks the spec MDN defines. if ( navigator.appVersion.indexOf("MSIE 9") === -1 && event.buttons === 0 && data.buttonsProperty !== 0 ) { - return end(event, data); + return eventEnd(event, data); } - var handles = data.handles || scope_Handles, positions, state = false, - proposal = ((event.calcPoint - data.start) * 100) / data.baseSize, - handleNumber = handles[0] === scope_Handles[0] ? 0 : 1, i; - - // Calculate relative positions for the handles. - positions = getPositions( proposal, data.positions, handles.length > 1); - - state = setHandle ( handles[0], positions[handleNumber], handles.length === 1 ); + // Check if we are moving up or down + var movement = (options.dir ? -1 : 1) * (event.calcPoint - data.startCalcPoint); - if ( handles.length > 1 ) { + // Convert the movement into a percentage of the slider width/height + var proposal = (movement * 100) / data.baseSize; - state = setHandle ( handles[1], positions[handleNumber?0:1], false ) || state; - - if ( state ) { - // fire for both handles - for ( i = 0; i < data.handles.length; i++ ) { - fireEvent('slide', i); - } - } - } else if ( state ) { - // Fire for a single handle - fireEvent('slide', handleNumber); - } + moveHandles(movement > 0, proposal, data.locations, data.handleNumbers); } // Unbind move events on document, call callbacks. - function end ( event, data ) { + function eventEnd ( event, data ) { // The handle is no longer active, so remove the class. - var active = scope_Base.querySelector( '.' + options.cssClasses.active ), - handleNumber = data.handles[0] === scope_Handles[0] ? 0 : 1; + var active = scope_Base.querySelector( '.' + options.cssClasses.active ); if ( active !== null ) { removeClass(active, options.cssClasses.active); @@ -1400,46 +1443,37 @@ function closure ( target, options, originalOptions ){ document.body.removeEventListener('selectstart', document.body.noUiListener); } - var d = document.documentElement; - // Unbind the move and end events, which are added on 'start'. - d.noUiListeners.forEach(function( c ) { - d.removeEventListener(c[0], c[1]); + document.documentElement.noUiListeners.forEach(function( c ) { + document.documentElement.removeEventListener(c[0], c[1]); }); // Remove dragging class. removeClass(scope_Target, options.cssClasses.drag); - // Fire the change and set events. - fireEvent('set', handleNumber); - fireEvent('change', handleNumber); + setZindex(); - // If this is a standard handle movement, fire the end event. - if ( data.handleNumber !== undefined ) { - fireEvent('end', data.handleNumber); - } - } - - // Fire 'end' when a mouse or pen leaves the document. - function documentLeave ( event, data ) { - if ( event.type === "mouseout" && event.target.nodeName === "HTML" && event.relatedTarget === null ){ - end ( event, data ); - } + data.handleNumbers.forEach(function(handleNumber){ + fireEvent('set', handleNumber); + fireEvent('change', handleNumber); + fireEvent('end', handleNumber); + }); } // Bind move events on document. - function start ( event, data ) { - - var d = document.documentElement; + function eventStart ( event, data ) { // Mark the handle as 'active' so it can be styled. - if ( data.handles.length === 1 ) { - // Support 'disabled' handles - if ( data.handles[0].hasAttribute('disabled') ) { + if ( data.handleNumbers.length === 1 ) { + + var handle = scope_Handles[data.handleNumbers[0]]; + + // Ignore 'disabled' handles + if ( handle.hasAttribute('disabled') ) { return false; } - addClass(data.handles[0].children[0], options.cssClasses.active); + addClass(handle.children[0], options.cssClasses.active); } // Fix #551, where a handle gets selected instead of dragged. @@ -1449,28 +1483,24 @@ function closure ( target, options, originalOptions ){ event.stopPropagation(); // Attach the move and end events. - var moveEvent = attach(actions.move, d, move, { - start: event.calcPoint, + var moveEvent = attachEvent(actions.move, document.documentElement, eventMove, { + startCalcPoint: event.calcPoint, baseSize: baseSize(), pageOffset: event.pageOffset, - handles: data.handles, - handleNumber: data.handleNumber, + handleNumbers: data.handleNumbers, buttonsProperty: event.buttons, - positions: [ - scope_Locations[0], - scope_Locations[scope_Handles.length - 1] - ] - }), endEvent = attach(actions.end, d, end, { - handles: data.handles, - handleNumber: data.handleNumber + locations: scope_Locations.slice() }); - var outEvent = attach("mouseout", d, documentLeave, { - handles: data.handles, - handleNumber: data.handleNumber + var endEvent = attachEvent(actions.end, document.documentElement, eventEnd, { + handleNumbers: data.handleNumbers }); - d.noUiListeners = moveEvent.concat(endEvent, outEvent); + var outEvent = attachEvent("mouseout", document.documentElement, documentLeave, { + handleNumbers: data.handleNumbers + }); + + document.documentElement.noUiListeners = moveEvent.concat(endEvent, outEvent); // Text selection isn't an issue on touch devices, // so adding cursor styles can be skipped. @@ -1494,67 +1524,52 @@ function closure ( target, options, originalOptions ){ document.body.addEventListener('selectstart', f, false); } - if ( data.handleNumber !== undefined ) { - fireEvent('start', data.handleNumber); - } + data.handleNumbers.forEach(function(handleNumber){ + fireEvent('start', handleNumber); + }); } // Move closest handle to tapped location. - function tap ( event ) { + function eventTap ( event ) { - var location = event.calcPoint, total = 0, handleNumber, to; - - // The tap event shouldn't propagate up and cause 'edge' to run. + // The tap event shouldn't propagate up event.stopPropagation(); - // Add up the handle offsets. - scope_Handles.forEach(function(a){ - total += offset(a)[ options.style ]; - }); - - // Find the handle closest to the tapped position. - handleNumber = ( location < total/2 || scope_Handles.length === 1 ) ? 0 : 1; + var proposal = calcPointToPercentage(event.calcPoint); + var handleNumber = getClosestHandle(proposal); - // Check if handler is not disablet if yes set number to the next handler - if (scope_Handles[handleNumber].hasAttribute('disabled')) { - handleNumber = handleNumber ? 0 : 1; + // Tackle the case that all handles are 'disabled'. + if ( handleNumber === false ) { + return false; } - location -= offset(scope_Base)[ options.style ]; - - // Calculate the new position. - to = ( location * 100 ) / baseSize(); - + // Flag the slider as it is now in a transitional state. + // Transition takes a configurable amount of ms (default 300). Re-enable the slider after that. if ( !options.events.snap ) { - // Flag the slider as it is now in a transitional state. - // Transition takes a configurable amount of ms (default 300). Re-enable the slider after that. - addClassFor( scope_Target, options.cssClasses.tap, options.animationDuration ); + addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration); } - // Support 'disabled' handles - if ( scope_Handles[handleNumber].hasAttribute('disabled') ) { - return false; - } + setHandle(handleNumber, proposal, true, true); - // Find the closest handle and calculate the tapped point. - // The set handle to the new position. - setHandle( scope_Handles[handleNumber], to ); + setZindex(); fireEvent('slide', handleNumber, true); fireEvent('set', handleNumber, true); fireEvent('change', handleNumber, true); + fireEvent('update', handleNumber, true); if ( options.events.snap ) { - start(event, { handles: [scope_Handles[handleNumber]] }); + eventStart(event, { handleNumbers: [handleNumber] }); } } // Fires a 'hover' event for a hovered mouse/pen position. - function hover ( event ) { + function eventHover ( event ) { - var location = event.calcPoint - offset(scope_Base)[ options.style ], - to = scope_Spectrum.getStep(( location * 100 ) / baseSize()), - value = scope_Spectrum.fromStepping( to ); + var proposal = calcPointToPercentage(event.calcPoint); + + var to = scope_Spectrum.getStep(proposal); + var value = scope_Spectrum.fromStepping(to); Object.keys(scope_Events).forEach(function( targetEvent ) { if ( 'hover' === targetEvent.split('.')[0] ) { @@ -1566,7 +1581,7 @@ function closure ( target, options, originalOptions ){ } // Attach events to several slider parts. - function events ( behaviour ) { + function bindSliderEvents ( behaviour ) { // Attach the standard drag event to the handles. if ( !behaviour.fixed ) { @@ -1575,205 +1590,253 @@ function closure ( target, options, originalOptions ){ // These events are only bound to the visual handle // element, not the 'real' origin element. - attach ( actions.start, handle.children[0], start, { - handles: [ handle ], - handleNumber: index + attachEvent ( actions.start, handle.children[0], eventStart, { + handleNumbers: [index] }); }); } // Attach the tap event to the slider base. if ( behaviour.tap ) { - - attach ( actions.start, scope_Base, tap, { - handles: scope_Handles - }); + attachEvent (actions.start, scope_Base, eventTap, {}); } // Fire hover events if ( behaviour.hover ) { - attach ( actions.move, scope_Base, hover, { hover: true } ); + attachEvent (actions.move, scope_Base, eventHover, { hover: true }); } // Make the range draggable. if ( behaviour.drag ){ - var drag = [scope_Base.querySelector( '.' + options.cssClasses.connect )]; - addClass(drag[0], options.cssClasses.draggable); + scope_Connects.forEach(function( connect, index ){ - // When the range is fixed, the entire range can - // be dragged by the handles. The handle in the first - // origin will propagate the start event upward, - // but it needs to be bound manually on the other. - if ( behaviour.fixed ) { - drag.push(scope_Handles[(drag[0] === scope_Handles[0] ? 1 : 0)].children[0]); - } + if ( connect === false || index === 0 || index === scope_Connects.length - 1 ) { + return; + } - drag.forEach(function( element ) { - attach ( actions.start, element, start, { - handles: scope_Handles + var handleBefore = scope_Handles[index - 1]; + var handleAfter = scope_Handles[index]; + var eventHolders = [connect]; + + addClass(connect, options.cssClasses.draggable); + + // When the range is fixed, the entire range can + // be dragged by the handles. The handle in the first + // origin will propagate the start event upward, + // but it needs to be bound manually on the other. + if ( behaviour.fixed ) { + eventHolders.push(handleBefore.children[0]); + eventHolders.push(handleAfter.children[0]); + } + + eventHolders.forEach(function( eventHolder ) { + attachEvent ( actions.start, eventHolder, eventStart, { + handles: [handleBefore, handleAfter], + handleNumbers: [index - 1, index] + }); }); }); } } - // Test suggested values and apply margin, step. - function setHandle ( handle, to, noLimitOption ) { - - var trigger = handle !== scope_Handles[0] ? 1 : 0, - lowerMargin = scope_Locations[0] + options.margin, - upperMargin = scope_Locations[1] - options.margin, - lowerLimit = scope_Locations[0] + options.limit, - upperLimit = scope_Locations[1] - options.limit; + // Split out the handle positioning logic so the Move event can use it, too + function checkHandlePosition ( reference, handleNumber, to, lookBackward, lookForward ) { - // For sliders with multiple handles, - // limit movement to the other handle. + // For sliders with multiple handles, limit movement to the other handle. // Apply the margin option by adding it to the handle positions. if ( scope_Handles.length > 1 ) { - to = trigger ? Math.max( to, lowerMargin ) : Math.min( to, upperMargin ); + + if ( lookBackward && handleNumber > 0 ) { + to = Math.max(to, reference[handleNumber - 1] + options.margin); + } + + if ( lookForward && handleNumber < scope_Handles.length - 1 ) { + to = Math.min(to, reference[handleNumber + 1] - options.margin); + } } // The limit option has the opposite effect, limiting handles to a // maximum distance from another. Limit must be > 0, as otherwise - // handles would be unmoveable. 'noLimitOption' is set to 'false' - // for the .val() method, except for pass 4/4. - if ( noLimitOption !== false && options.limit && scope_Handles.length > 1 ) { - to = trigger ? Math.min ( to, lowerLimit ) : Math.max( to, upperLimit ); + // handles would be unmoveable. + if ( scope_Handles.length > 1 && options.limit ) { + + if ( lookBackward && handleNumber > 0 ) { + to = Math.min(to, reference[handleNumber - 1] + options.limit); + } + + if ( lookForward && handleNumber < scope_Handles.length - 1 ) { + to = Math.max(to, reference[handleNumber + 1] - options.limit); + } } - // Handle the step option. - to = scope_Spectrum.getStep( to ); + to = scope_Spectrum.getStep(to); // Limit percentage to the 0 - 100 range to = limit(to); // Return false if handle can't move - if ( to === scope_Locations[trigger] ) { + if ( to === reference[handleNumber] ) { return false; } + return to; + } + + function toPct ( pct ) { + return pct + '%'; + } + + // Updates scope_Locations and scope_Values, updates visual state + function updateHandlePosition ( handleNumber, to ) { + + // Update locations. + scope_Locations[handleNumber] = to; + + // Convert the value to the slider stepping/range. + scope_Values[handleNumber] = scope_Spectrum.fromStepping(to); + + // Called synchronously or on the next animationFrame + var stateUpdate = function() { + scope_Handles[handleNumber].style[options.style] = toPct(to); + updateConnect(handleNumber); + updateConnect(handleNumber + 1); + }; + // Set the handle to the new position. // Use requestAnimationFrame for efficient painting. - // No significant effect in Chrome, Edge sees dramatic - // performace improvements. - if ( window.requestAnimationFrame ) { - window.requestAnimationFrame(function(){ - handle.style[options.style] = to + '%'; - }); + // No significant effect in Chrome, Edge sees dramatic performace improvements. + // Option to disable is useful for unit tests, and single-step debugging. + if ( window.requestAnimationFrame && options.useRequestAnimationFrame ) { + window.requestAnimationFrame(stateUpdate); } else { - handle.style[options.style] = to + '%'; + stateUpdate(); } + } - // Force proper handle stacking - if ( !handle.previousSibling ) { - removeClass(handle, options.cssClasses.stacking); - if ( to > 50 ) { - addClass(handle, options.cssClasses.stacking); - } - } + function setZindex ( ) { - // Update locations. - scope_Locations[trigger] = to; + scope_HandleNumbers.forEach(function(handleNumber){ + // Handles before the slider middle are stacked later = higher, + // Handles after the middle later is lower + // [[7] [8] .......... | .......... [5] [4] + var dir = (scope_Locations[handleNumber] > 50 ? -1 : 1); + var zIndex = 3 + (scope_Handles.length + (dir * handleNumber)); + scope_Handles[handleNumber].childNodes[0].style.zIndex = zIndex; + }); + } - // Convert the value to the slider stepping/range. - scope_Values[trigger] = scope_Spectrum.fromStepping( to ); + // Test suggested values and apply margin, step. + function setHandle ( handleNumber, to, lookBackward, lookForward ) { - fireEvent('update', trigger); + to = checkHandlePosition(scope_Locations, handleNumber, to, lookBackward, lookForward); + + if ( to === false ) { + return false; + } + + updateHandlePosition(handleNumber, to); return true; } - // Loop values from value method and apply them. - function setValues ( count, values ) { + // Updates style attribute for connect nodes + function updateConnect ( index ) { - var i, trigger, to; + // Skip connects set to false + if ( !scope_Connects[index] ) { + return; + } + + var l = 0; + var h = 100; - // With the limit option, we'll need another limiting pass. - if ( options.limit ) { - count += 1; + if ( index !== 0 ) { + l = scope_Locations[index - 1]; } - // If there are multiple handles to be set run the setting - // mechanism twice for the first handle, to make sure it - // can be bounced of the second one properly. - for ( i = 0; i < count; i += 1 ) { + if ( index !== scope_Connects.length - 1 ) { + h = scope_Locations[index]; + } - trigger = i%2; + scope_Connects[index].style[options.style] = toPct(l); + scope_Connects[index].style[options.styleOposite] = toPct(100 - h); + } - // Get the current argument from the array. - to = values[trigger]; + // ... + function setValue ( to, handleNumber ) { - // Setting with null indicates an 'ignore'. - // Inputting 'false' is invalid. - if ( to !== null && to !== false ) { + // Setting with null indicates an 'ignore'. + // Inputting 'false' is invalid. + if ( to === null || to === false ) { + return; + } - // If a formatted number was passed, attemt to decode it. - if ( typeof to === 'number' ) { - to = String(to); - } + // If a formatted number was passed, attemt to decode it. + if ( typeof to === 'number' ) { + to = String(to); + } - to = options.format.from( to ); + to = options.format.from(to); - // Request an update for all links if the value was invalid. - // Do so too if setting the handle fails. - if ( to === false || isNaN(to) || setHandle( scope_Handles[trigger], scope_Spectrum.toStepping( to ), i === (3 - options.dir) ) === false ) { - fireEvent('update', trigger); - } - } + // Request an update for all links if the value was invalid. + // Do so too if setting the handle fails. + if ( to !== false && !isNaN(to) ) { + setHandle(handleNumber, scope_Spectrum.toStepping(to), false, false); } } // Set the slider value. function valueSet ( input, fireSetEvent ) { - var count, values = asArray( input ), i; + var values = asArray(input); + var isInit = scope_Locations[0] === undefined; // Event fires by default fireSetEvent = (fireSetEvent === undefined ? true : !!fireSetEvent); - // The RTL settings is implemented by reversing the front-end, - // internal mechanisms are the same. - if ( options.dir && options.handles > 1 ) { - values.reverse(); - } + values.forEach(setValue); // Animation is optional. - // Make sure the initial values where set before using animated placement. - if ( options.animate && scope_Locations[0] !== -1 ) { - addClassFor( scope_Target, options.cssClasses.tap, options.animationDuration ); + // Make sure the initial values were set before using animated placement. + if ( options.animate && !isInit ) { + addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration); } - // Determine how often to set the handles. - count = scope_Handles.length > 1 ? 3 : 1; + // Now that all base values are set, apply constraints + scope_HandleNumbers.forEach(function(handleNumber){ + setHandle(handleNumber, scope_Locations[handleNumber], true, false); + }); - if ( values.length === 1 ) { - count = 1; - } + setZindex(); - setValues ( count, values ); + scope_HandleNumbers.forEach(function(handleNumber){ - // Fire the 'set' event for both handles. - for ( i = 0; i < scope_Handles.length; i++ ) { + fireEvent('update', handleNumber); // Fire the event only for handles that received a new value, as per #579 - if ( values[i] !== null && fireSetEvent ) { - fireEvent('set', i); + if ( values[handleNumber] !== null && fireSetEvent ) { + fireEvent('set', handleNumber); } - } + }); + } + + function valueReset ( fireSetEvent ) { + valueSet(options.start, fireSetEvent); } // Get the slider value. function valueGet ( ) { - var i, retour = []; + var values = scope_Values.map(options.format.to); - // Get the value from all handles. - for ( i = 0; i < options.handles; i += 1 ){ - retour[i] = options.format.to( scope_Values[i] ); + // If only one handle is used, return a single value. + if ( values.length === 1 ){ + return values[0]; } - return inSliderOrder( retour ); + return values; } // Removes classes from the root and empties it. @@ -1796,34 +1859,58 @@ function closure ( target, options, originalOptions ){ // Check all locations, map them to their stepping point. // Get the step point, then find it in the input list. - var retour = scope_Locations.map(function( location, index ){ + return scope_Locations.map(function( location, index ){ + + var nearbySteps = scope_Spectrum.getNearbySteps( location ); + var value = scope_Values[index]; + var increment = nearbySteps.thisStep.step; + var decrement = null; + + // If the next value in this step moves into the next step, + // the increment is the start of the next step - the current value + if ( increment !== false ) { + if ( value + increment > nearbySteps.stepAfter.startValue ) { + increment = nearbySteps.stepAfter.startValue - value; + } + } - var step = scope_Spectrum.getApplicableStep( location ), + // If the value is beyond the starting point + if ( value > nearbySteps.thisStep.startValue ) { + decrement = nearbySteps.thisStep.step; + } - // As per #391, the comparison for the decrement step can have some rounding issues. - // Round the value to the precision used in the step. - stepDecimals = countDecimals(String(step[2])), + else if ( nearbySteps.stepBefore.step === false ) { + decrement = false; + } - // Get the current numeric value - value = scope_Values[index], + // If a handle is at the start of a step, it always steps back into the previous step first + else { + decrement = value - nearbySteps.stepBefore.highestStep; + } - // To move the slider 'one step up', the current step value needs to be added. - // Use null if we are at the maximum slider value. - increment = location === 100 ? null : step[2], + // Now, if at the slider edges, there is not in/decrement + if ( location === 100 ) { + increment = null; + } + + else if ( location === 0 ) { + decrement = null; + } - // Going 'one step down' might put the slider in a different sub-range, so we - // need to switch between the current or the previous step. - prev = Number((value - step[2]).toFixed(stepDecimals)), + // As per #391, the comparison for the decrement step can have some rounding issues. + var stepDecimals = scope_Spectrum.countStepDecimals(); - // If the value fits the step, return the current step value. Otherwise, use the - // previous step. Return null if the slider is at its minimum value. - decrement = location === 0 ? null : (prev >= step[1]) ? step[2] : (step[0] || false); + // Round per #391 + if ( increment !== null && increment !== false ) { + increment = Number(increment.toFixed(stepDecimals)); + } + + if ( decrement !== null && decrement !== false ) { + decrement = Number(decrement.toFixed(stepDecimals)); + } return [decrement, increment]; }); - - // Return values in the proper order. - return inSliderOrder( retour ); } // Attach an event to this slider, possibly including a namespace @@ -1862,21 +1949,23 @@ function closure ( target, options, originalOptions ){ // Spectrum is created using the range, snap, direction and step options. // 'snap' and 'step' can be updated, 'direction' cannot, due to event binding. // If 'snap' and 'step' are not passed, they should remain unchanged. - var v = valueGet(), newOptions = testOptions({ - start: [0, 0], - margin: optionsToUpdate.margin, - limit: optionsToUpdate.limit, - step: optionsToUpdate.step === undefined ? options.singleStep : optionsToUpdate.step, - range: optionsToUpdate.range, - animate: optionsToUpdate.animate, - snap: optionsToUpdate.snap === undefined ? options.snap : optionsToUpdate.snap + var v = valueGet(); + + var updateAble = ['margin', 'limit', 'range', 'animate', 'snap', 'step', 'format']; + + // Only change options that we're actually passed to update. + updateAble.forEach(function(name){ + if ( optionsToUpdate[name] !== undefined ) { + originalOptions[name] = optionsToUpdate[name]; + } }); - ['margin', 'limit', 'range', 'animate'].forEach(function(name){ + var newOptions = testOptions(originalOptions); - // Only change options that we're actually passed to update. + // Load new options into the slider state + updateAble.forEach(function(name){ if ( optionsToUpdate[name] !== undefined ) { - options[name] = optionsToUpdate[name]; + options[name] = newOptions[name]; } }); @@ -1885,32 +1974,24 @@ function closure ( target, options, originalOptions ){ newOptions.spectrum.direction = scope_Spectrum.direction; scope_Spectrum = newOptions.spectrum; + // Limit and margin depend on the spectrum but are stored outside of it. (#677) + options.margin = newOptions.margin; + options.limit = newOptions.limit; + // Invalidate the current positioning so valueSet forces an update. - scope_Locations = [-1, -1]; + scope_Locations = []; valueSet(optionsToUpdate.start || v, fireSetEvent); } - // Throw an error if the slider was already initialized. if ( scope_Target.noUiSlider ) { throw new Error('Slider was already initialized.'); } // Create the base element, initialise HTML and set classes. - // Add handles and links. - scope_Base = addSlider( options.dir, options.ort, scope_Target ); - scope_Handles = addHandles( options.handles, options.dir, scope_Base ); - - // Set the connect classes. - addConnection ( options.connect, scope_Target, scope_Handles ); - - if ( options.pips ) { - pips(options.pips); - } - - if ( options.tooltips ) { - tooltips(); - } + // Add handles and connect elements. + addSlider(scope_Target); + addElements(options.connect, scope_Base); scope_Self = { destroy: destroy, @@ -1919,14 +2000,28 @@ function closure ( target, options, originalOptions ){ off: removeEvent, get: valueGet, set: valueSet, + reset: valueReset, + // Exposed for unit testing, don't use this in your application. + __moveHandles: function(a, b, c) { moveHandles(a, b, scope_Locations, c); }, + options: originalOptions, // Issue #600, #678 updateOptions: updateOptions, - options: originalOptions, // Issue #600 target: scope_Target, // Issue #597 pips: pips // Issue #594 }; // Attach user events. - events( options.events ); + bindSliderEvents(options.events); + + // Use the public value method to set the start values. + valueSet(options.start); + + if ( options.pips ) { + pips(options.pips); + } + + if ( options.tooltips ) { + tooltips(); + } return scope_Self; @@ -1941,14 +2036,12 @@ function closure ( target, options, originalOptions ){ } // Test the options and create the slider environment; - var options = testOptions( originalOptions, target ), - slider = closure( target, options, originalOptions ); + var options = testOptions( originalOptions, target ); + var api = closure( target, options, originalOptions ); - // Use the public value method to set the start values. - slider.set(options.start); + target.noUiSlider = api; - target.noUiSlider = slider; - return slider; + return api; } // Use an object instead of a function for future expansibility; diff --git a/distribute/nouislider.min.css b/distribute/nouislider.min.css index e1da5f09..0b1e1f33 100644 --- a/distribute/nouislider.min.css +++ b/distribute/nouislider.min.css @@ -1,4 +1,4 @@ -/*! nouislider - 8.5.1 - 2016-04-24 16:00:30 */ +/*! nouislider - 9.0.0 - 2016-09-29 21:44:03 */ -.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-user-select:none;-ms-touch-action:none;touch-action:none;-ms-user-select:none;-moz-user-select:none;user-select:none;-moz-box-sizing:border-box;box-sizing:border-box}.noUi-target{position:relative;direction:ltr}.noUi-base{width:100%;height:100%;position:relative;z-index:1}.noUi-origin{position:absolute;right:0;top:0;left:0;bottom:0}.noUi-handle{position:relative;z-index:1}.noUi-stacking .noUi-handle{z-index:10}.noUi-state-tap .noUi-origin{-webkit-transition:left .3s,top .3s;transition:left .3s,top .3s}.noUi-state-drag *{cursor:inherit!important}.noUi-base,.noUi-handle{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.noUi-horizontal{height:18px}.noUi-horizontal .noUi-handle{width:34px;height:28px;left:-17px;top:-6px}.noUi-vertical{width:18px}.noUi-vertical .noUi-handle{width:28px;height:34px;left:-6px;top:-17px}.noUi-background{background:#FAFAFA;box-shadow:inset 0 1px 1px #f0f0f0}.noUi-connect{background:#3FB8AF;box-shadow:inset 0 0 3px rgba(51,51,51,.45);-webkit-transition:background 450ms;transition:background 450ms}.noUi-origin{border-radius:2px}.noUi-target{border-radius:4px;border:1px solid #D3D3D3;box-shadow:inset 0 1px 1px #F0F0F0,0 3px 6px -5px #BBB}.noUi-target.noUi-connect{box-shadow:inset 0 0 3px rgba(51,51,51,.45),0 3px 6px -5px #BBB}.noUi-draggable{cursor:w-resize}.noUi-vertical .noUi-draggable{cursor:n-resize}.noUi-handle{border:1px solid #D9D9D9;border-radius:3px;background:#FFF;cursor:default;box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #EBEBEB,0 3px 6px -3px #BBB}.noUi-active{box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #DDD,0 3px 6px -3px #BBB}.noUi-handle:after,.noUi-handle:before{content:"";display:block;position:absolute;height:14px;width:1px;background:#E8E7E6;left:14px;top:6px}.noUi-handle:after{left:17px}.noUi-vertical .noUi-handle:after,.noUi-vertical .noUi-handle:before{width:14px;height:1px;left:6px;top:14px}.noUi-vertical .noUi-handle:after{top:17px}[disabled] .noUi-connect,[disabled].noUi-connect{background:#B8B8B8}[disabled] .noUi-handle,[disabled].noUi-origin{cursor:not-allowed}.noUi-pips,.noUi-pips *{-moz-box-sizing:border-box;box-sizing:border-box}.noUi-pips{position:absolute;color:#999}.noUi-value{position:absolute;text-align:center}.noUi-value-sub{color:#ccc;font-size:10px}.noUi-marker{position:absolute;background:#CCC}.noUi-marker-large,.noUi-marker-sub{background:#AAA}.noUi-pips-horizontal{padding:10px 0;height:80px;top:100%;left:0;width:100%}.noUi-value-horizontal{-webkit-transform:translate3d(-50%,50%,0);transform:translate3d(-50%,50%,0)}.noUi-marker-horizontal.noUi-marker{margin-left:-1px;width:2px;height:5px}.noUi-marker-horizontal.noUi-marker-sub{height:10px}.noUi-marker-horizontal.noUi-marker-large{height:15px}.noUi-pips-vertical{padding:0 10px;height:100%;top:0;left:100%}.noUi-value-vertical{-webkit-transform:translate3d(0,-50%,0);transform:translate3d(0,-50%,0);padding-left:25px}.noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.noUi-marker-vertical.noUi-marker-sub{width:10px}.noUi-marker-vertical.noUi-marker-large{width:15px}.noUi-tooltip{display:block;position:absolute;border:1px solid #D9D9D9;border-radius:3px;background:#fff;padding:5px;text-align:center}.noUi-horizontal .noUi-handle-lower .noUi-tooltip{top:-32px}.noUi-horizontal .noUi-handle-upper .noUi-tooltip{bottom:-32px}.noUi-vertical .noUi-handle-lower .noUi-tooltip{left:120%}.noUi-vertical .noUi-handle-upper .noUi-tooltip{right:120%} \ No newline at end of file +.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-user-select:none;-ms-touch-action:none;touch-action:none;-ms-user-select:none;-moz-user-select:none;user-select:none;-moz-box-sizing:border-box;box-sizing:border-box}.noUi-target{position:relative;direction:ltr}.noUi-base{width:100%;height:100%;position:relative;z-index:1}.noUi-connect{position:absolute;right:0;top:0;left:0;bottom:0}.noUi-origin{position:absolute;height:0;width:0}.noUi-handle{position:relative;z-index:1}.noUi-state-tap .noUi-connect,.noUi-state-tap .noUi-origin{-webkit-transition:top .3s,right .3s,bottom .3s,left .3s;transition:top .3s,right .3s,bottom .3s,left .3s}.noUi-state-drag *{cursor:inherit!important}.noUi-base,.noUi-handle{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.noUi-horizontal{height:18px}.noUi-horizontal .noUi-handle{width:34px;height:28px;left:-17px;top:-6px}.noUi-vertical{width:18px}.noUi-vertical .noUi-handle{width:28px;height:34px;left:-6px;top:-17px}.noUi-target{background:#FAFAFA;border-radius:4px;border:1px solid #D3D3D3;box-shadow:inset 0 1px 1px #F0F0F0,0 3px 6px -5px #BBB}.noUi-connect{background:#3FB8AF;box-shadow:inset 0 0 3px rgba(51,51,51,.45);-webkit-transition:background 450ms;transition:background 450ms}.noUi-draggable{cursor:w-resize}.noUi-vertical .noUi-draggable{cursor:n-resize}.noUi-handle{border:1px solid #D9D9D9;border-radius:3px;background:#FFF;cursor:default;box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #EBEBEB,0 3px 6px -3px #BBB}.noUi-active{box-shadow:inset 0 0 1px #FFF,inset 0 1px 7px #DDD,0 3px 6px -3px #BBB}.noUi-handle:after,.noUi-handle:before{content:"";display:block;position:absolute;height:14px;width:1px;background:#E8E7E6;left:14px;top:6px}.noUi-handle:after{left:17px}.noUi-vertical .noUi-handle:after,.noUi-vertical .noUi-handle:before{width:14px;height:1px;left:6px;top:14px}.noUi-vertical .noUi-handle:after{top:17px}[disabled] .noUi-connect{background:#B8B8B8}[disabled] .noUi-handle,[disabled].noUi-handle,[disabled].noUi-target{cursor:not-allowed}.noUi-pips,.noUi-pips *{-moz-box-sizing:border-box;box-sizing:border-box}.noUi-pips{position:absolute;color:#999}.noUi-value{position:absolute;text-align:center}.noUi-value-sub{color:#ccc;font-size:10px}.noUi-marker{position:absolute;background:#CCC}.noUi-marker-large,.noUi-marker-sub{background:#AAA}.noUi-pips-horizontal{padding:10px 0;height:80px;top:100%;left:0;width:100%}.noUi-value-horizontal{-webkit-transform:translate3d(-50%,50%,0);transform:translate3d(-50%,50%,0)}.noUi-marker-horizontal.noUi-marker{margin-left:-1px;width:2px;height:5px}.noUi-marker-horizontal.noUi-marker-sub{height:10px}.noUi-marker-horizontal.noUi-marker-large{height:15px}.noUi-pips-vertical{padding:0 10px;height:100%;top:0;left:100%}.noUi-value-vertical{-webkit-transform:translate3d(0,50%,0);transform:translate3d(0,50%,0);padding-left:25px}.noUi-marker-vertical.noUi-marker{width:5px;height:2px;margin-top:-1px}.noUi-marker-vertical.noUi-marker-sub{width:10px}.noUi-marker-vertical.noUi-marker-large{width:15px}.noUi-tooltip{display:block;position:absolute;border:1px solid #D9D9D9;border-radius:3px;background:#fff;color:#000;padding:5px;text-align:center}.noUi-horizontal .noUi-tooltip{-webkit-transform:translate(-50%,0);transform:translate(-50%,0);left:50%;bottom:120%}.noUi-vertical .noUi-tooltip{-webkit-transform:translate(0,-50%);transform:translate(0,-50%);top:50%;right:120%} \ No newline at end of file diff --git a/distribute/nouislider.min.js b/distribute/nouislider.min.js index 13702105..5e005bae 100644 --- a/distribute/nouislider.min.js +++ b/distribute/nouislider.min.js @@ -1,3 +1,3 @@ -/*! nouislider - 8.5.1 - 2016-04-24 16:00:29 */ +/*! nouislider - 9.0.0 - 2016-09-29 21:44:02 */ -!function(a){"function"==typeof define&&define.amd?define([],a):"object"==typeof exports?module.exports=a():window.noUiSlider=a()}(function(){"use strict";function a(a){return a.filter(function(a){return this[a]?!1:this[a]=!0},{})}function b(a,b){return Math.round(a/b)*b}function c(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.documentElement,e=l();return/webkit.*Chrome.*Mobile/i.test(navigator.userAgent)&&(e.x=0),{top:b.top+e.y-d.clientTop,left:b.left+e.x-d.clientLeft}}function d(a){return"number"==typeof a&&!isNaN(a)&&isFinite(a)}function e(a,b,c){i(a,b),setTimeout(function(){j(a,b)},c)}function f(a){return Math.max(Math.min(a,100),0)}function g(a){return Array.isArray(a)?a:[a]}function h(a){var b=a.split(".");return b.length>1?b[1].length:0}function i(a,b){a.classList?a.classList.add(b):a.className+=" "+b}function j(a,b){a.classList?a.classList.remove(b):a.className=a.className.replace(new RegExp("(^|\\b)"+b.split(" ").join("|")+"(\\b|$)","gi")," ")}function k(a,b){return a.classList?a.classList.contains(b):new RegExp("\\b"+b+"\\b").test(a.className)}function l(){var a=void 0!==window.pageXOffset,b="CSS1Compat"===(document.compatMode||""),c=a?window.pageXOffset:b?document.documentElement.scrollLeft:document.body.scrollLeft,d=a?window.pageYOffset:b?document.documentElement.scrollTop:document.body.scrollTop;return{x:c,y:d}}function m(){return window.navigator.pointerEnabled?{start:"pointerdown",move:"pointermove",end:"pointerup"}:window.navigator.msPointerEnabled?{start:"MSPointerDown",move:"MSPointerMove",end:"MSPointerUp"}:{start:"mousedown touchstart",move:"mousemove touchmove",end:"mouseup touchend"}}function n(a,b){return 100/(b-a)}function o(a,b){return 100*b/(a[1]-a[0])}function p(a,b){return o(a,a[0]<0?b+Math.abs(a[0]):b-a[0])}function q(a,b){return b*(a[1]-a[0])/100+a[0]}function r(a,b){for(var c=1;a>=b[c];)c+=1;return c}function s(a,b,c){if(c>=a.slice(-1)[0])return 100;var d,e,f,g,h=r(c,a);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],f+p([d,e],c)/n(f,g)}function t(a,b,c){if(c>=100)return a.slice(-1)[0];var d,e,f,g,h=r(c,b);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],q([d,e],(c-f)*n(f,g))}function u(a,c,d,e){if(100===e)return e;var f,g,h=r(e,a);return d?(f=a[h-1],g=a[h],e-f>(g-f)/2?g:f):c[h-1]?a[h-1]+b(e-a[h-1],c[h-1]):e}function v(a,b,c){var e;if("number"==typeof b&&(b=[b]),"[object Array]"!==Object.prototype.toString.call(b))throw new Error("noUiSlider: 'range' contains invalid value.");if(e="min"===a?0:"max"===a?100:parseFloat(a),!d(e)||!d(b[0]))throw new Error("noUiSlider: 'range' value isn't numeric.");c.xPct.push(e),c.xVal.push(b[0]),e?c.xSteps.push(isNaN(b[1])?!1:b[1]):isNaN(b[1])||(c.xSteps[0]=b[1])}function w(a,b,c){return b?void(c.xSteps[a]=o([c.xVal[a],c.xVal[a+1]],b)/n(c.xPct[a],c.xPct[a+1])):!0}function x(a,b,c,d){this.xPct=[],this.xVal=[],this.xSteps=[d||!1],this.xNumSteps=[!1],this.snap=b,this.direction=c;var e,f=[];for(e in a)a.hasOwnProperty(e)&&f.push([a[e],e]);for(f.length&&"object"==typeof f[0][0]?f.sort(function(a,b){return a[0][0]-b[0][0]}):f.sort(function(a,b){return a[0]-b[0]}),e=0;e2)throw new Error("noUiSlider: 'start' option is incorrect.");a.handles=b.length,a.start=b}function B(a,b){if(a.snap=b,"boolean"!=typeof b)throw new Error("noUiSlider: 'snap' option must be a boolean.")}function C(a,b){if(a.animate=b,"boolean"!=typeof b)throw new Error("noUiSlider: 'animate' option must be a boolean.")}function D(a,b){if(a.animationDuration=b,"number"!=typeof b)throw new Error("noUiSlider: 'animationDuration' option must be a number.")}function E(a,b){if("lower"===b&&1===a.handles)a.connect=1;else if("upper"===b&&1===a.handles)a.connect=2;else if(b===!0&&2===a.handles)a.connect=3;else{if(b!==!1)throw new Error("noUiSlider: 'connect' option doesn't match handle count.");a.connect=0}}function F(a,b){switch(b){case"horizontal":a.ort=0;break;case"vertical":a.ort=1;break;default:throw new Error("noUiSlider: 'orientation' option is invalid.")}}function G(a,b){if(!d(b))throw new Error("noUiSlider: 'margin' option must be numeric.");if(0!==b&&(a.margin=a.spectrum.getMargin(b),!a.margin))throw new Error("noUiSlider: 'margin' option is only supported on linear sliders.")}function H(a,b){if(!d(b))throw new Error("noUiSlider: 'limit' option must be numeric.");if(a.limit=a.spectrum.getMargin(b),!a.limit)throw new Error("noUiSlider: 'limit' option is only supported on linear sliders.")}function I(a,b){switch(b){case"ltr":a.dir=0;break;case"rtl":a.dir=1,a.connect=[0,2,1,3][a.connect];break;default:throw new Error("noUiSlider: 'direction' option was not recognized.")}}function J(a,b){if("string"!=typeof b)throw new Error("noUiSlider: 'behaviour' must be a string containing options.");var c=b.indexOf("tap")>=0,d=b.indexOf("drag")>=0,e=b.indexOf("fixed")>=0,f=b.indexOf("snap")>=0,g=b.indexOf("hover")>=0;if(d&&!a.connect)throw new Error("noUiSlider: 'drag' behaviour must be used with 'connect': true.");a.events={tap:c||f,drag:d,fixed:e,snap:f,hover:g}}function K(a,b){var c;if(b!==!1)if(b===!0)for(a.tooltips=[],c=0;cd&&(e+=Math.abs(d)),e>100&&(d-=e-100),[f(d),f(e)]):[d,e]}function p(a,b){a.preventDefault();var c,d,e=0===a.type.indexOf("touch"),f=0===a.type.indexOf("mouse"),g=0===a.type.indexOf("pointer"),h=a;return 0===a.type.indexOf("MSPointer")&&(g=!0),e&&(c=a.changedTouches[0].pageX,d=a.changedTouches[0].pageY),b=b||l(),(f||g)&&(c=a.clientX+b.x,d=a.clientY+b.y),h.pageOffset=b,h.points=[c,d],h.cursor=f||g,h}function q(a,b){var c=document.createElement("div"),e=document.createElement("div"),f=[d.cssClasses.handleLower,d.cssClasses.handleUpper];return a&&f.reverse(),i(e,d.cssClasses.handle),i(e,f[b]),i(c,d.cssClasses.origin),c.appendChild(e),c}function r(a,b,c){switch(a){case 1:i(b,d.cssClasses.connect),i(c[0],d.cssClasses.background);break;case 3:i(c[1],d.cssClasses.background);case 2:i(c[0],d.cssClasses.connect);case 0:i(b,d.cssClasses.background)}}function s(a,b,c){var d,e=[];for(d=0;a>d;d+=1)e.push(c.appendChild(q(b,d)));return e}function t(a,b,c){i(c,d.cssClasses.target),0===a?i(c,d.cssClasses.ltr):i(c,d.cssClasses.rtl),0===b?i(c,d.cssClasses.horizontal):i(c,d.cssClasses.vertical);var e=document.createElement("div");return i(e,d.cssClasses.base),c.appendChild(e),e}function u(a,b){if(!d.tooltips[b])return!1;var c=document.createElement("div");return c.className=d.cssClasses.tooltip,a.firstChild.appendChild(c)}function v(){d.dir&&d.tooltips.reverse();var a=W.map(u);d.dir&&(a.reverse(),d.tooltips.reverse()),S("update",function(b,c,e){a[c]&&(a[c].innerHTML=d.tooltips[c]===!0?b[c]:d.tooltips[c].to(e[c]))})}function w(a,b,c){if("range"===a||"steps"===a)return _.xVal;if("count"===a){var d,e=100/(b-1),f=0;for(b=[];(d=f++*e)<=100;)b.push(d);a="positions"}return"positions"===a?b.map(function(a){return _.fromStepping(c?_.getStep(a):a)}):"values"===a?c?b.map(function(a){return _.fromStepping(_.getStep(_.toStepping(a)))}):b:void 0}function x(b,c,d){function e(a,b){return(a+b).toFixed(7)/1}var f=_.direction,g={},h=_.xVal[0],i=_.xVal[_.xVal.length-1],j=!1,k=!1,l=0;return _.direction=0,d=a(d.slice().sort(function(a,b){return a-b})),d[0]!==h&&(d.unshift(h),j=!0),d[d.length-1]!==i&&(d.push(i),k=!0),d.forEach(function(a,f){var h,i,m,n,o,p,q,r,s,t,u=a,v=d[f+1];if("steps"===c&&(h=_.xNumSteps[f]),h||(h=v-u),u!==!1&&void 0!==v)for(i=u;v>=i;i=e(i,h)){for(n=_.toStepping(i),o=n-l,r=o/b,s=Math.round(r),t=o/s,m=1;s>=m;m+=1)p=l+m*t,g[p.toFixed(5)]=["x",0];q=d.indexOf(i)>-1?1:"steps"===c?2:0,!f&&j&&(q=0),i===v&&k||(g[n.toFixed(5)]=[i,q]),l=n}}),_.direction=f,g}function y(a,b,c){function e(a,b){var c=b===d.cssClasses.value,e=c?m:n,f=c?k:l;return b+" "+e[d.ort]+" "+f[a]}function f(a,b,c){return'class="'+e(c[1],b)+'" style="'+d.style+": "+a+'%"'}function g(a,e){_.direction&&(a=100-a),e[1]=e[1]&&b?b(e[0],e[1]):e[1],j+="
",e[1]&&(j+="
"+c.to(e[0])+"
")}var h=document.createElement("div"),j="",k=[d.cssClasses.valueNormal,d.cssClasses.valueLarge,d.cssClasses.valueSub],l=[d.cssClasses.markerNormal,d.cssClasses.markerLarge,d.cssClasses.markerSub],m=[d.cssClasses.valueHorizontal,d.cssClasses.valueVertical],n=[d.cssClasses.markerHorizontal,d.cssClasses.markerVertical];return i(h,d.cssClasses.pips),i(h,0===d.ort?d.cssClasses.pipsHorizontal:d.cssClasses.pipsVertical),Object.keys(a).forEach(function(b){g(b,a[b])}),h.innerHTML=j,h}function z(a){var b=a.mode,c=a.density||1,d=a.filter||!1,e=a.values||!1,f=a.stepped||!1,g=w(b,e,f),h=x(c,b,g),i=a.format||{to:Math.round};return Z.appendChild(y(h,d,i))}function A(){var a=V.getBoundingClientRect(),b="offset"+["Width","Height"][d.ort];return 0===d.ort?a.width||V[b]:a.height||V[b]}function B(a,b,c){var e;for(e=0;e1?!1:e.hover&&b.buttons?!1:(b.calcPoint=b.points[d.ort],void c(b,e)))},g=[];return a.split(" ").forEach(function(a){b.addEventListener(a,f,!1),g.push([a,f])}),g}function E(a,b){if(-1===navigator.appVersion.indexOf("MSIE 9")&&0===a.buttons&&0!==b.buttonsProperty)return F(a,b);var c,d,e=b.handles||W,f=!1,g=100*(a.calcPoint-b.start)/b.baseSize,h=e[0]===W[0]?0:1;if(c=o(g,b.positions,e.length>1),f=L(e[0],c[h],1===e.length),e.length>1){if(f=L(e[1],c[h?0:1],!1)||f)for(d=0;d1&&i(Z,d.cssClasses.drag);var h=function(){return!1};document.body.noUiListener=h,document.body.addEventListener("selectstart",h,!1)}void 0!==b.handleNumber&&B("start",b.handleNumber)}function I(a){var b,f,g=a.calcPoint,h=0;return a.stopPropagation(),W.forEach(function(a){h+=c(a)[d.style]}),b=h/2>g||1===W.length?0:1,W[b].hasAttribute("disabled")&&(b=b?0:1),g-=c(V)[d.style],f=100*g/A(),d.events.snap||e(Z,d.cssClasses.tap,d.animationDuration),W[b].hasAttribute("disabled")?!1:(L(W[b],f),B("slide",b,!0),B("set",b,!0),B("change",b,!0),void(d.events.snap&&H(a,{handles:[W[b]]})))}function J(a){var b=a.calcPoint-c(V)[d.style],e=_.getStep(100*b/A()),f=_.fromStepping(e);Object.keys(ba).forEach(function(a){"hover"===a.split(".")[0]&&ba[a].forEach(function(a){a.call(X,f)})})}function K(a){if(a.fixed||W.forEach(function(a,b){D(Y.start,a.children[0],H,{handles:[a],handleNumber:b})}),a.tap&&D(Y.start,V,I,{handles:W}),a.hover&&D(Y.move,V,J,{hover:!0}),a.drag){var b=[V.querySelector("."+d.cssClasses.connect)];i(b[0],d.cssClasses.draggable),a.fixed&&b.push(W[b[0]===W[0]?1:0].children[0]),b.forEach(function(a){D(Y.start,a,H,{handles:W})})}}function L(a,b,c){var e=a!==W[0]?1:0,g=$[0]+d.margin,h=$[1]-d.margin,k=$[0]+d.limit,l=$[1]-d.limit;return W.length>1&&(b=e?Math.max(b,g):Math.min(b,h)),c!==!1&&d.limit&&W.length>1&&(b=e?Math.min(b,k):Math.max(b,l)),b=_.getStep(b),b=f(b),b===$[e]?!1:(window.requestAnimationFrame?window.requestAnimationFrame(function(){a.style[d.style]=b+"%"}):a.style[d.style]=b+"%",a.previousSibling||(j(a,d.cssClasses.stacking),b>50&&i(a,d.cssClasses.stacking)),$[e]=b,aa[e]=_.fromStepping(b),B("update",e),!0)}function M(a,b){var c,e,f;for(d.limit&&(a+=1),c=0;a>c;c+=1)e=c%2,f=b[e],null!==f&&f!==!1&&("number"==typeof f&&(f=String(f)),f=d.format.from(f),(f===!1||isNaN(f)||L(W[e],_.toStepping(f),c===3-d.dir)===!1)&&B("update",e))}function N(a,b){var c,f,h=g(a);for(b=void 0===b?!0:!!b,d.dir&&d.handles>1&&h.reverse(),d.animate&&-1!==$[0]&&e(Z,d.cssClasses.tap,d.animationDuration),c=W.length>1?3:1,1===h.length&&(c=1),M(c,h),f=0;f=c[1]?c[2]:c[0]||!1;return[i,f]});return C(a)}function S(a,b){ba[a]=ba[a]||[],ba[a].push(b),"update"===a.split(".")[0]&&W.forEach(function(a,b){B("update",b)})}function T(a){var b=a&&a.split(".")[0],c=b&&a.substring(b.length);Object.keys(ba).forEach(function(a){var d=a.split(".")[0],e=a.substring(d.length);b&&b!==d||c&&c!==e||delete ba[a]})}function U(a,b){var c=P(),e=O({start:[0,0],margin:a.margin,limit:a.limit,step:void 0===a.step?d.singleStep:a.step,range:a.range,animate:a.animate,snap:void 0===a.snap?d.snap:a.snap});["margin","limit","range","animate"].forEach(function(b){void 0!==a[b]&&(d[b]=a[b])}),e.spectrum.direction=_.direction,_=e.spectrum,$=[-1,-1],N(a.start||c,b)}var V,W,X,Y=m(),Z=b,$=[-1,-1],_=d.spectrum,aa=[],ba={};if(Z.noUiSlider)throw new Error("Slider was already initialized.");return V=t(d.dir,d.ort,Z),W=s(d.handles,d.dir,V),r(d.connect,Z,W),d.pips&&z(d.pips),d.tooltips&&v(),X={destroy:Q,steps:R,on:S,off:T,get:P,set:N,updateOptions:U,options:n,target:Z,pips:z},K(d.events),X}function Q(a,b){if(!a.nodeName)throw new Error("noUiSlider.create requires a single element.");var c=O(b,a),d=P(a,c,b);return d.set(c.start),a.noUiSlider=d,d}x.prototype.getMargin=function(a){return 2===this.xPct.length?o(this.xVal,a):!1},x.prototype.toStepping=function(a){return a=s(this.xVal,this.xPct,a),this.direction&&(a=100-a),a},x.prototype.fromStepping=function(a){return this.direction&&(a=100-a),t(this.xVal,this.xPct,a)},x.prototype.getStep=function(a){return this.direction&&(a=100-a),a=u(this.xPct,this.xSteps,this.snap,a),this.direction&&(a=100-a),a},x.prototype.getApplicableStep=function(a){var b=r(a,this.xPct),c=100===a?2:1;return[this.xNumSteps[b-2],this.xVal[b-c],this.xNumSteps[b-c]]},x.prototype.convert=function(a){return this.getStep(this.toStepping(a))};var R={to:function(a){return void 0!==a&&a.toFixed(2)},from:Number};return{create:Q}}); \ No newline at end of file +!function(a){"function"==typeof define&&define.amd?define([],a):"object"==typeof exports?module.exports=a():window.noUiSlider=a()}(function(){"use strict";function a(a,b){var c=document.createElement("div");return j(c,b),a.appendChild(c),c}function b(a){return a.filter(function(a){return!this[a]&&(this[a]=!0)},{})}function c(a,b){return Math.round(a/b)*b}function d(a,b){var c=a.getBoundingClientRect(),d=a.ownerDocument,e=d.documentElement,f=m();return/webkit.*Chrome.*Mobile/i.test(navigator.userAgent)&&(f.x=0),b?c.top+f.y-e.clientTop:c.left+f.x-e.clientLeft}function e(a){return"number"==typeof a&&!isNaN(a)&&isFinite(a)}function f(a,b,c){c>0&&(j(a,b),setTimeout(function(){k(a,b)},c))}function g(a){return Math.max(Math.min(a,100),0)}function h(a){return Array.isArray(a)?a:[a]}function i(a){a=String(a);var b=a.split(".");return b.length>1?b[1].length:0}function j(a,b){a.classList?a.classList.add(b):a.className+=" "+b}function k(a,b){a.classList?a.classList.remove(b):a.className=a.className.replace(new RegExp("(^|\\b)"+b.split(" ").join("|")+"(\\b|$)","gi")," ")}function l(a,b){return a.classList?a.classList.contains(b):new RegExp("\\b"+b+"\\b").test(a.className)}function m(){var a=void 0!==window.pageXOffset,b="CSS1Compat"===(document.compatMode||""),c=a?window.pageXOffset:b?document.documentElement.scrollLeft:document.body.scrollLeft,d=a?window.pageYOffset:b?document.documentElement.scrollTop:document.body.scrollTop;return{x:c,y:d}}function n(){return window.navigator.pointerEnabled?{start:"pointerdown",move:"pointermove",end:"pointerup"}:window.navigator.msPointerEnabled?{start:"MSPointerDown",move:"MSPointerMove",end:"MSPointerUp"}:{start:"mousedown touchstart",move:"mousemove touchmove",end:"mouseup touchend"}}function o(a,b){return 100/(b-a)}function p(a,b){return 100*b/(a[1]-a[0])}function q(a,b){return p(a,a[0]<0?b+Math.abs(a[0]):b-a[0])}function r(a,b){return b*(a[1]-a[0])/100+a[0]}function s(a,b){for(var c=1;a>=b[c];)c+=1;return c}function t(a,b,c){if(c>=a.slice(-1)[0])return 100;var d,e,f,g,h=s(c,a);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],f+q([d,e],c)/o(f,g)}function u(a,b,c){if(c>=100)return a.slice(-1)[0];var d,e,f,g,h=s(c,b);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],r([d,e],(c-f)*o(f,g))}function v(a,b,d,e){if(100===e)return e;var f,g,h=s(e,a);return d?(f=a[h-1],g=a[h],e-f>(g-f)/2?g:f):b[h-1]?a[h-1]+c(e-a[h-1],b[h-1]):e}function w(a,b,c){var d;if("number"==typeof b&&(b=[b]),"[object Array]"!==Object.prototype.toString.call(b))throw new Error("noUiSlider: 'range' contains invalid value.");if(d="min"===a?0:"max"===a?100:parseFloat(a),!e(d)||!e(b[0]))throw new Error("noUiSlider: 'range' value isn't numeric.");c.xPct.push(d),c.xVal.push(b[0]),d?c.xSteps.push(!isNaN(b[1])&&b[1]):isNaN(b[1])||(c.xSteps[0]=b[1]),c.xHighestCompleteStep.push(0)}function x(a,b,c){if(!b)return!0;c.xSteps[a]=p([c.xVal[a],c.xVal[a+1]],b)/o(c.xPct[a],c.xPct[a+1]);var d=(c.xVal[a+1]-c.xVal[a])/c.xNumSteps[a],e=Math.ceil(Number(d.toFixed(3))-1),f=c.xVal[a]+c.xNumSteps[a]*e;c.xHighestCompleteStep[a]=f}function y(a,b,c,d){this.xPct=[],this.xVal=[],this.xSteps=[d||!1],this.xNumSteps=[!1],this.xHighestCompleteStep=[],this.snap=b,this.direction=c;var e,f=[];for(e in a)a.hasOwnProperty(e)&&f.push([a[e],e]);for(f.length&&"object"==typeof f[0][0]?f.sort(function(a,b){return a[0][0]-b[0][0]}):f.sort(function(a,b){return a[0]-b[0]}),e=0;e=0,d=b.indexOf("drag")>=0,e=b.indexOf("fixed")>=0,f=b.indexOf("snap")>=0,g=b.indexOf("hover")>=0;if(e){if(2!==a.handles)throw new Error("noUiSlider: 'fixed' behaviour must be used with 2 handles");H(a,a.start[1]-a.start[0])}a.events={tap:c||f,drag:d,fixed:e,snap:f,hover:g}}function L(a,b){if(b!==!1)if(b===!0){a.tooltips=[];for(var c=0;c-1?1:"steps"===c?2:0,!g&&i&&(q=0),l===v&&j||(f[n.toFixed(5)]=[l,q]),k=n}}),f}function w(a,b,c){function d(a,b){var c=b===e.cssClasses.value,d=c?m:n,f=c?k:l;return b+" "+d[e.ort]+" "+f[a]}function f(a,b,c){return'class="'+d(c[1],b)+'" style="'+e.style+": "+a+'%"'}function g(a,d){d[1]=d[1]&&b?b(d[0],d[1]):d[1],i+="
",d[1]&&(i+="
"+c.to(d[0])+"
")}var h=document.createElement("div"),i="",k=[e.cssClasses.valueNormal,e.cssClasses.valueLarge,e.cssClasses.valueSub],l=[e.cssClasses.markerNormal,e.cssClasses.markerLarge,e.cssClasses.markerSub],m=[e.cssClasses.valueHorizontal,e.cssClasses.valueVertical],n=[e.cssClasses.markerHorizontal,e.cssClasses.markerVertical];return j(h,e.cssClasses.pips),j(h,0===e.ort?e.cssClasses.pipsHorizontal:e.cssClasses.pipsVertical),Object.keys(a).forEach(function(b){g(b,a[b])}),h.innerHTML=i,h}function x(a){var b=a.mode,c=a.density||1,d=a.filter||!1,e=a.values||!1,f=a.stepped||!1,g=u(b,e,f),h=v(c,b,g),i=a.format||{to:Math.round};return fa.appendChild(w(h,d,i))}function y(){var a=aa.getBoundingClientRect(),b="offset"+["Width","Height"][e.ort];return 0===e.ort?a.width||aa[b]:a.height||aa[b]}function z(a,b,c,d){var f=function(b){return!fa.hasAttribute("disabled")&&(!l(fa,e.cssClasses.tap)&&(b=A(b,d.pageOffset),!(a===ea.start&&void 0!==b.buttons&&b.buttons>1)&&((!d.hover||!b.buttons)&&(b.calcPoint=b.points[e.ort],void c(b,d)))))},g=[];return a.split(" ").forEach(function(a){b.addEventListener(a,f,!1),g.push([a,f])}),g}function A(a,b){a.preventDefault();var c,d,e=0===a.type.indexOf("touch"),f=0===a.type.indexOf("mouse"),g=0===a.type.indexOf("pointer"),h=a;if(0===a.type.indexOf("MSPointer")&&(g=!0),e){if(h.touches.length>1)return!1;c=a.changedTouches[0].pageX,d=a.changedTouches[0].pageY}return b=b||m(),(f||g)&&(c=a.clientX+b.x,d=a.clientY+b.y),h.pageOffset=b,h.points=[c,d],h.cursor=f||g,h}function B(a){var b=a-d(aa,e.ort),c=100*b/y();return e.dir?100-c:c}function C(a){var b=100,c=!1;return ba.forEach(function(d,e){if(!d.hasAttribute("disabled")){var f=Math.abs(ga[e]-a);f1?d.forEach(function(a,c){var d=M(e,a,e[a]+b,f[c],g[c]);d===!1?b=0:(b=d-e[a],e[a]=d)}):f=g=[!0];var h=!1;d.forEach(function(a,d){h=R(a,c[a]+b,f[d],g[d])||h}),h&&d.forEach(function(a){E("update",a),E("slide",a)})}function E(a,b,c){Object.keys(ka).forEach(function(d){var f=d.split(".")[0];a===f&&ka[d].forEach(function(a){a.call(da,ja.map(e.format.to),b,ja.slice(),c||!1,ga.slice())})})}function F(a,b){"mouseout"===a.type&&"HTML"===a.target.nodeName&&null===a.relatedTarget&&H(a,b)}function G(a,b){if(navigator.appVersion.indexOf("MSIE 9")===-1&&0===a.buttons&&0!==b.buttonsProperty)return H(a,b);var c=(e.dir?-1:1)*(a.calcPoint-b.startCalcPoint),d=100*c/b.baseSize;D(c>0,d,b.locations,b.handleNumbers)}function H(a,b){var c=aa.querySelector("."+e.cssClasses.active);null!==c&&k(c,e.cssClasses.active),a.cursor&&(document.body.style.cursor="",document.body.removeEventListener("selectstart",document.body.noUiListener)),document.documentElement.noUiListeners.forEach(function(a){document.documentElement.removeEventListener(a[0],a[1])}),k(fa,e.cssClasses.drag),P(),b.handleNumbers.forEach(function(a){E("set",a),E("change",a),E("end",a)})}function I(a,b){if(1===b.handleNumbers.length){var c=ba[b.handleNumbers[0]];if(c.hasAttribute("disabled"))return!1;j(c.children[0],e.cssClasses.active)}a.preventDefault(),a.stopPropagation();var d=z(ea.move,document.documentElement,G,{startCalcPoint:a.calcPoint,baseSize:y(),pageOffset:a.pageOffset,handleNumbers:b.handleNumbers,buttonsProperty:a.buttons,locations:ga.slice()}),f=z(ea.end,document.documentElement,H,{handleNumbers:b.handleNumbers}),g=z("mouseout",document.documentElement,F,{handleNumbers:b.handleNumbers});if(document.documentElement.noUiListeners=d.concat(f,g),a.cursor){document.body.style.cursor=getComputedStyle(a.target).cursor,ba.length>1&&j(fa,e.cssClasses.drag);var h=function(){return!1};document.body.noUiListener=h,document.body.addEventListener("selectstart",h,!1)}b.handleNumbers.forEach(function(a){E("start",a)})}function J(a){a.stopPropagation();var b=B(a.calcPoint),c=C(b);return c!==!1&&(e.events.snap||f(fa,e.cssClasses.tap,e.animationDuration),R(c,b,!0,!0),P(),E("slide",c,!0),E("set",c,!0),E("change",c,!0),E("update",c,!0),void(e.events.snap&&I(a,{handleNumbers:[c]})))}function K(a){var b=B(a.calcPoint),c=ia.getStep(b),d=ia.fromStepping(c);Object.keys(ka).forEach(function(a){"hover"===a.split(".")[0]&&ka[a].forEach(function(a){a.call(da,d)})})}function L(a){a.fixed||ba.forEach(function(a,b){z(ea.start,a.children[0],I,{handleNumbers:[b]})}),a.tap&&z(ea.start,aa,J,{}),a.hover&&z(ea.move,aa,K,{hover:!0}),a.drag&&ca.forEach(function(b,c){if(b!==!1&&0!==c&&c!==ca.length-1){var d=ba[c-1],f=ba[c],g=[b];j(b,e.cssClasses.draggable),a.fixed&&(g.push(d.children[0]),g.push(f.children[0])),g.forEach(function(a){z(ea.start,a,I,{handles:[d,f],handleNumbers:[c-1,c]})})}})}function M(a,b,c,d,f){return ba.length>1&&(d&&b>0&&(c=Math.max(c,a[b-1]+e.margin)),f&&b1&&e.limit&&(d&&b>0&&(c=Math.min(c,a[b-1]+e.limit)),f&&b50?-1:1,c=3+(ba.length+b*a);ba[a].childNodes[0].style.zIndex=c})}function R(a,b,c,d){return b=M(ga,a,b,c,d),b!==!1&&(O(a,b),!0)}function S(a){if(ca[a]){var b=0,c=100;0!==a&&(b=ga[a-1]),a!==ca.length-1&&(c=ga[a]),ca[a].style[e.style]=N(b),ca[a].style[e.styleOposite]=N(100-c)}}function T(a,b){null!==a&&a!==!1&&("number"==typeof a&&(a=String(a)),a=e.format.from(a),a===!1||isNaN(a)||R(b,ia.toStepping(a),!1,!1))}function U(a,b){var c=h(a),d=void 0===ga[0];b=void 0===b||!!b,c.forEach(T),e.animate&&!d&&f(fa,e.cssClasses.tap,e.animationDuration),ha.forEach(function(a){R(a,ga[a],!0,!1)}),P(),ha.forEach(function(a){E("update",a),null!==c[a]&&b&&E("set",a)})}function V(a){U(e.start,a)}function W(){var a=ja.map(e.format.to);return 1===a.length?a[0]:a}function X(){for(var a in e.cssClasses)e.cssClasses.hasOwnProperty(a)&&k(fa,e.cssClasses[a]);for(;fa.firstChild;)fa.removeChild(fa.firstChild);delete fa.noUiSlider}function Y(){return ga.map(function(a,b){var c=ia.getNearbySteps(a),d=ja[b],e=c.thisStep.step,f=null;e!==!1&&d+e>c.stepAfter.startValue&&(e=c.stepAfter.startValue-d),f=d>c.thisStep.startValue?c.thisStep.step:c.stepBefore.step!==!1&&d-c.stepBefore.highestStep,100===a?e=null:0===a&&(f=null);var g=ia.countStepDecimals();return null!==e&&e!==!1&&(e=Number(e.toFixed(g))),null!==f&&f!==!1&&(f=Number(f.toFixed(g))),[f,e]})}function Z(a,b){ka[a]=ka[a]||[],ka[a].push(b),"update"===a.split(".")[0]&&ba.forEach(function(a,b){E("update",b)})}function $(a){var b=a&&a.split(".")[0],c=b&&a.substring(b.length);Object.keys(ka).forEach(function(a){var d=a.split(".")[0],e=a.substring(d.length);b&&b!==d||c&&c!==e||delete ka[a]})}function _(a,b){var c=W(),d=["margin","limit","range","animate","snap","step","format"];d.forEach(function(b){void 0!==a[b]&&(i[b]=a[b])});var f=Q(i);d.forEach(function(b){void 0!==a[b]&&(e[b]=f[b])}),f.spectrum.direction=ia.direction,ia=f.spectrum,e.margin=f.margin,e.limit=f.limit,ga=[],U(a.start||c,b)}var aa,ba,ca,da,ea=n(),fa=c,ga=[],ha=[],ia=e.spectrum,ja=[],ka={};if(fa.noUiSlider)throw new Error("Slider was already initialized.");return r(fa),q(e.connect,aa),da={destroy:X,steps:Y,on:Z,off:$,get:W,set:U,reset:V,__moveHandles:function(a,b,c){D(a,b,ga,c)},options:i,updateOptions:_,target:fa,pips:x},L(e.events),U(e.start),e.pips&&x(e.pips),e.tooltips&&t(),da}function S(a,b){if(!a.nodeName)throw new Error("noUiSlider.create requires a single element.");var c=Q(b,a),d=R(a,c,b);return a.noUiSlider=d,d}y.prototype.getMargin=function(a){var b=this.xNumSteps[0];if(b&&a%b)throw new Error("noUiSlider: 'limit' and 'margin' must be divisible by step.");return 2===this.xPct.length&&p(this.xVal,a)},y.prototype.toStepping=function(a){return a=t(this.xVal,this.xPct,a)},y.prototype.fromStepping=function(a){return u(this.xVal,this.xPct,a)},y.prototype.getStep=function(a){return a=v(this.xPct,this.xSteps,this.snap,a)},y.prototype.getNearbySteps=function(a){var b=s(a,this.xPct);return{stepBefore:{startValue:this.xVal[b-2],step:this.xNumSteps[b-2],highestStep:this.xHighestCompleteStep[b-2]},thisStep:{startValue:this.xVal[b-1],step:this.xNumSteps[b-1],highestStep:this.xHighestCompleteStep[b-1]},stepAfter:{startValue:this.xVal[b-0],step:this.xNumSteps[b-0],highestStep:this.xHighestCompleteStep[b-0]}}},y.prototype.countStepDecimals=function(){var a=this.xNumSteps.map(i);return Math.max.apply(null,a)},y.prototype.convert=function(a){return this.getStep(this.toStepping(a))};var T={to:function(a){return void 0!==a&&a.toFixed(2)},from:Number};return{create:S}}); \ No newline at end of file diff --git a/package.json b/package.json index 820f4d7a..f4d0f116 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nouislider", - "version": "8.5.1", + "version": "9.0.0", "main": "distribute/nouislider", "style": "distribute/nouislider.min.css", "license": "WTFPL",