diff --git a/README.md b/README.md index fc927153..0668c542 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,11 @@ noUiSlider is lightweight JavaScript range slider. - **No dependencies** -- All modern browsers and [IE9+](#browser-support) are supported +- All modern browsers and IE > 9 are supported - Fully **responsive** - **Touch support** on Android, iOS and Windows devices - Tons of [examples](https://refreshless.com/nouislider/examples) and answered [Stack Overflow questions](https://stackoverflow.com/questions/tagged/nouislider) --------- -**Quick note:** I'm out of town for a while, so support will be slower than usual. Apologies in advance. --------- - License ------- noUiSlider is licensed [WTFPL](http://www.wtfpl.net/about/). You can use it **for free** and **without any attribution**, in any personal or commercial project. You may also fork the project and re-release it under another license you prefer. @@ -31,6 +27,24 @@ npm [(package)](https://www.npmjs.com/package/nouislider) Changelog --------- +### 11.0.0 (*2018-01-12*) +noUiSlider 11 doesn't include any breaking API changes. +Unless major changes were made to the stylesheet or you specifically depend +on the handle/connect order in the DOM, there should be no issues upgrading. +- Change: Use CSS transforms for handle movement, resulting in a massive performance improvement (#718); +- Change: Support multitouch by default; +- Change: Handle stacking is now on `.noUi-origin` instead of `.noUi-handle`; +- Added: A `.noUi-connects` element holding all `.noUi-connect` elements; +- Added: `[data-value]` property for `.noUi-value` in pips (#733); +- Added: `padding` option can now take an array for different padding values at both sides of a slider (#822); +- Removed: `useRequestAnimationFrame` option. No longer needed with CSS transforms; +- Removed: `multitouch` option. Now enabled by default; +- Fixed: Slider could ignore end events it should handle (#704, #805, #834); +- Fixed: Stop depending on array type (#801); +- Fixed: `set` method might bypass margin option (#823); +- Fixed: Alignment of pips for RTL sliders (#795); +- Fixed: Several issues regarding pips (#812, #826, #832); + ### 10.1.0 (*2017-07-26*) - Added: `multitouch` option (#793); @@ -40,7 +54,7 @@ Changelog - Fixed: Content Security Policy issue with pips; - Added: `removePips` method; - Added: aria support (#685); -- Added: `ariaFormat` option (controls `aria-valuetext`); +- Added: `ariaFormat` option (controls `aria-valuetext`); - Fixed: throw a better error when mistakenly trying to initialize noUiSlider with `null` (#658); - Fixed: Made order of events consistent and documented it (#775); - Fixed: Border radius of connect bar, white space wrapping of tooltips (#773, #774); @@ -145,17 +159,3 @@ is enough: ``` import 'nouislider'; ``` - -Browser support ---------------- - -All major browsers are supported. **To support IE8** you'll need to shim several ES5 features. - -You can use [polyfill.io](https://cdn.polyfill.io/v2/docs/) to easily do so: - -```html - - -``` diff --git a/distribute/nouislider.css b/distribute/nouislider.css index 19789ade..9a97319b 100644 --- a/distribute/nouislider.css +++ b/distribute/nouislider.css @@ -1,4 +1,4 @@ -/*! nouislider - 10.1.0 - 2017-07-28 13:09:54 */ +/*! nouislider - 11.0.0 - 2018-01-12 20:37:52 */ /* Functional styling; * These styles are required for noUiSlider to function. * You don't need to change these rules to apply your design. @@ -20,45 +20,51 @@ position: relative; direction: ltr; } -.noUi-base { +.noUi-base, +.noUi-connects { width: 100%; height: 100%; position: relative; z-index: 1; - /* Fix 401 */ } -.noUi-connect { +/* Wrapper for all connect elements. + */ +.noUi-connects { + overflow: hidden; + z-index: 0; +} +.noUi-connect, +.noUi-origin { + will-change: transform; position: absolute; - right: 0; + z-index: 1; top: 0; left: 0; - bottom: 0; + height: 100%; + width: 100%; + -webkit-transform-origin: 0 0; + transform-origin: 0 0; } -.noUi-origin { - position: absolute; - height: 0; +/* Give origins 0 height/width so they don't interfere with clicking the + * connect elements. + */ +.noUi-vertical .noUi-origin { width: 0; } +.noUi-horizontal .noUi-origin { + height: 0; +} .noUi-handle { position: relative; - z-index: 1; } .noUi-state-tap .noUi-connect, .noUi-state-tap .noUi-origin { - -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; + -webkit-transition: transform 0.3s; + transition: transform 0.3s; } .noUi-state-drag * { cursor: inherit !important; } -/* Painting and performance; - * Browsers can paint handles in their own layer. - */ -.noUi-base, -.noUi-handle { - -webkit-transform: translate3d(0, 0, 0); - transform: translate3d(0, 0, 0); -} /* Slider size and handle placement; */ .noUi-horizontal { @@ -80,6 +86,7 @@ top: -17px; } /* Styling; + * Giving the connect element a border radius causes issues with using transform: scale */ .noUi-target { background: #FAFAFA; @@ -87,12 +94,11 @@ border: 1px solid #D3D3D3; box-shadow: inset 0 1px 1px #F0F0F0, 0 3px 6px -5px #BBB; } +.noUi-connects { + border-radius: 3px; +} .noUi-connect { background: #3FB8AF; - border-radius: 4px; - box-shadow: inset 0 0 3px rgba(51, 51, 51, 0.45); - -webkit-transition: background 450ms; - transition: background 450ms; } /* Handles and cursors; */ @@ -196,8 +202,12 @@ width: 100%; } .noUi-value-horizontal { - -webkit-transform: translate3d(-50%, 50%, 0); - transform: translate3d(-50%, 50%, 0); + -webkit-transform: translate(-50%, 50%); + transform: translate(-50%, 50%); +} +.noUi-rtl .noUi-value-horizontal { + -webkit-transform: translate(50%, 50%); + transform: translate(50%, 50%); } .noUi-marker-horizontal.noUi-marker { margin-left: -1px; @@ -220,10 +230,14 @@ left: 100%; } .noUi-value-vertical { - -webkit-transform: translate3d(0, 50%, 0); - transform: translate3d(0, 50%, 0); + -webkit-transform: translate(0, -50%); + transform: translate(0, -50%, 0); padding-left: 25px; } +.noUi-rtl .noUi-value-vertical { + -webkit-transform: translate(0, 50%); + transform: translate(0, 50%); +} .noUi-marker-vertical.noUi-marker { width: 5px; height: 2px; diff --git a/distribute/nouislider.js b/distribute/nouislider.js index e5d92816..51ab190b 100644 --- a/distribute/nouislider.js +++ b/distribute/nouislider.js @@ -1,4 +1,4 @@ -/*! nouislider - 10.1.0 - 2017-07-28 13:09:54 */ +/*! nouislider - 11.0.0 - 2018-01-12 20:37:52 */ (function (factory) { @@ -22,7 +22,7 @@ 'use strict'; - var VERSION = '10.1.0'; + var VERSION = '11.0.0'; function isValidFormatter ( entry ) { @@ -305,7 +305,7 @@ } // Reject any invalid input, by testing whether value is an array. - if ( Object.prototype.toString.call( value ) !== '[object Array]' ){ + if ( !Array.isArray(value) ){ throw new Error("noUiSlider (" + VERSION + "): 'range' contains invalid value."); } @@ -649,25 +649,34 @@ function testPadding ( parsed, entry ) { - if ( !isNumeric(entry) ){ - throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be numeric."); + if ( !isNumeric(entry) && !Array.isArray(entry) ){ + throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be numeric or array of exactly 2 numbers."); + } + + if ( Array.isArray(entry) && !(entry.length == 2 || isNumeric(entry[0]) || isNumeric(entry[1])) ) { + throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be numeric or array of exactly 2 numbers."); } if ( entry === 0 ) { return; } - parsed.padding = parsed.spectrum.getMargin(entry); + if ( !Array.isArray(entry) ) { + entry = [entry, entry]; + } - if ( !parsed.padding ) { + // 'getMargin' returns false for invalid values. + parsed.padding = [parsed.spectrum.getMargin(entry[0]), parsed.spectrum.getMargin(entry[1])]; + + if ( parsed.padding[0] === false || parsed.padding[1] === false ) { throw new Error("noUiSlider (" + VERSION + "): 'padding' option is only supported on linear sliders."); } - if ( parsed.padding < 0 ) { - throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be a positive number."); + if ( parsed.padding[0] < 0 || parsed.padding[1] < 0 ) { + throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be a positive number(s)."); } - if ( parsed.padding >= 50 ) { + if ( parsed.padding[0] >= 50 || parsed.padding[1] >= 50 ) { throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be less than half the range."); } } @@ -723,14 +732,6 @@ }; } - function testMultitouch ( parsed, entry ) { - parsed.multitouch = entry; - - if ( typeof entry !== 'boolean' ){ - throw new Error("noUiSlider (" + VERSION + "): 'multitouch' option must be a boolean."); - } - } - function testTooltips ( parsed, entry ) { if ( entry === false ) { @@ -800,14 +801,6 @@ } } - function testUseRaf ( parsed, entry ) { - if ( entry === true || entry === false ) { - parsed.useRequestAnimationFrame = entry; - } else { - throw new Error("noUiSlider (" + VERSION + "): 'useRequestAnimationFrame' option should be true (default) or false."); - } - } - // Test all developer settings and parse to assumption-safe values. function testOptions ( options ) { @@ -840,20 +833,17 @@ 'limit': { r: false, t: testLimit }, 'padding': { r: false, t: testPadding }, 'behaviour': { r: true, t: testBehaviour }, - 'multitouch': { r: true, t: testMultitouch }, 'ariaFormat': { r: false, t: testAriaFormat }, 'format': { r: false, t: testFormat }, 'tooltips': { r: false, t: testTooltips }, 'cssPrefix': { r: false, t: testCssPrefix }, - 'cssClasses': { r: false, t: testCssClasses }, - 'useRequestAnimationFrame': { r: false, t: testUseRaf } + 'cssClasses': { r: false, t: testCssClasses } }; var defaults = { 'connect': false, 'direction': 'ltr', 'behaviour': 'tap', - 'multitouch': false, 'orientation': 'horizontal', 'cssPrefix' : 'noUi-', 'cssClasses': { @@ -867,6 +857,7 @@ vertical: 'vertical', background: 'background', connect: 'connect', + connects: 'connects', ltr: 'ltr', rtl: 'rtl', draggable: 'draggable', @@ -889,8 +880,7 @@ valueNormal: 'value-normal', valueLarge: 'value-large', valueSub: 'value-sub' - }, - 'useRequestAnimationFrame': true + } }; // AriaFormat defaults to regular format, if any. @@ -919,11 +909,20 @@ // Forward pips options parsed.pips = options.pips; + // All recent browsers accept unprefixed transform. + // We need -ms- for IE9 and -webkit- for older Android; + // Assume use of -webkit- if unprefixed and -ms- are not supported. + // https://caniuse.com/#feat=transforms2d + var d = document.createElement("div"); + var msPrefix = d.style.msTransform !== undefined; + var noPrefix = d.style.transform !== undefined; + + parsed.transformRule = noPrefix ? 'transform' : (msPrefix ? 'msTransform' : 'webkitTransform'); + + // Pips don't move, so we can place them using left/top. var styles = [['left', 'top'], ['right', 'bottom']]; - // Pre-define the styles. parsed.style = styles[parsed.dir][parsed.ort]; - parsed.styleOposite = styles[parsed.dir?0:1][parsed.ort]; return parsed; } @@ -1005,10 +1004,12 @@ function closure ( target, options, originalOptions ){ // Add handles to the slider base. function addElements ( connectOptions, base ) { + var connectBase = addNodeTo(base, options.cssClasses.connects); + scope_Handles = []; scope_Connects = []; - scope_Connects.push(addConnect(base, connectOptions[0])); + scope_Connects.push(addConnect(connectBase, connectOptions[0])); // [::::O====O====O====] // connectOptions = [0, 1, 1, 1] @@ -1017,7 +1018,7 @@ function closure ( target, options, originalOptions ){ // Keep a list of all added handles. scope_Handles.push(addOrigin(base, i)); scope_HandleNumbers[i] = i; - scope_Connects.push(addConnect(base, connectOptions[i + 1])); + scope_Connects.push(addConnect(connectBase, connectOptions[i + 1])); } } @@ -1108,22 +1109,23 @@ function closure ( target, options, originalOptions ){ if ( mode === 'count' ) { - if ( !values ) { - throw new Error("noUiSlider (" + VERSION + "): 'values' required for mode 'count'."); + if ( values < 2 ) { + throw new Error("noUiSlider (" + VERSION + "): 'values' (>= 2) required for mode 'count'."); } // Divide 0 - 100 in 'count' parts. - var spread = ( 100 / (values - 1) ); - var v; - var i = 0; + var interval = values - 1; + var spread = ( 100 / interval ); values = []; // List these parts and have them handled as 'positions'. - while ( (v = i++ * spread) <= 100 ) { - values.push(v); + while ( interval-- ) { + values[ interval ] = ( interval * spread ); } + values.push(100); + mode = 'positions'; } @@ -1229,7 +1231,7 @@ function closure ( target, options, originalOptions ){ steps = pctDifference / density; realSteps = Math.round(steps); - // This ratio represents the ammount of percentage-space a point indicates. + // This ratio represents the amount of percentage-space a point indicates. // For a density 1 the points/percentage = 1. For density 2, that percentage needs to be re-devided. // Round the percentage offset to an even number, then divide by two // to spread the offset on both sides of the range. @@ -1316,6 +1318,7 @@ function closure ( target, options, originalOptions ){ if ( values[1] ) { node = addNodeTo(element, false); node.className = getClasses(values[1], options.cssClasses.value); + node.setAttribute('data-value', values[0]); node.style[options.style] = offset + '%'; node.innerText = formatter.to(values[0]); } @@ -1376,19 +1379,22 @@ function closure ( target, options, originalOptions ){ var method = function ( e ){ - if ( scope_Target.hasAttribute('disabled') ) { + e = fixEvent(e, data.pageOffset, data.target || element); + + // fixEvent returns false if this event has a different target + // when handling (multi-) touch events; + if ( !e ) { return false; } - // Stop if an active 'tap' transition is taking place. - if ( hasClass(scope_Target, options.cssClasses.tap) ) { + // doNotReject is passed by all end events to make sure released touches + // are not rejected, leaving the slider "stuck" to the cursor; + if ( scope_Target.hasAttribute('disabled') && !data.doNotReject ) { return false; } - e = fixEvent(e, data.pageOffset, data.target || element); - - // Handle reject of multitouch - if ( !e ) { + // Stop if an active 'tap' transition is taking place. + if ( hasClass(scope_Target, options.cssClasses.tap) && !data.doNotReject ) { return false; } @@ -1449,44 +1455,40 @@ function closure ( target, options, originalOptions ){ // In the event that multitouch is activated, the only thing one handle should be concerned // about is the touches that originated on top of it. - if ( touch && options.multitouch ) { + if ( touch ) { + // Returns true if a touch originated on the target. var isTouchOnTarget = function (touch) { return touch.target === target || target.contains(touch.target); }; + // In the case of touchstart events, we need to make sure there is still no more than one // touch on the target so we look amongst all touches. if (e.type === 'touchstart') { + var targetTouches = Array.prototype.filter.call(e.touches, isTouchOnTarget); + // Do not support more than one touch per handle. if ( targetTouches.length > 1 ) { return false; } + x = targetTouches[0].pageX; y = targetTouches[0].pageY; + } else { - // In the other cases, find on changedTouches is enough. + + // In the other cases, find on changedTouches is enough. var targetTouch = Array.prototype.find.call(e.changedTouches, isTouchOnTarget); + // Cancel if the target touch has not moved. if ( !targetTouch ) { return false; } + x = targetTouch.pageX; y = targetTouch.pageY; } - } else 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 ( e.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(scope_Document); @@ -1726,6 +1728,7 @@ function closure ( target, options, originalOptions ){ target: event.target, handle: handle, listeners: listeners, + doNotReject: true, handleNumbers: data.handleNumbers }); @@ -1733,6 +1736,7 @@ function closure ( target, options, originalOptions ){ target: event.target, handle: handle, listeners: listeners, + doNotReject: true, handleNumbers: data.handleNumbers }); @@ -1913,11 +1917,11 @@ function closure ( target, options, originalOptions ){ if ( options.padding ) { if ( handleNumber === 0 ) { - to = Math.max(to, options.padding); + to = Math.max(to, options.padding[0]); } if ( handleNumber === scope_Handles.length - 1 ) { - to = Math.min(to, 100 - options.padding); + to = Math.min(to, 100 - options.padding[1]); } } @@ -1934,10 +1938,24 @@ function closure ( target, options, originalOptions ){ return to; } + // Uses slider orientation to create CSS rules. a = base value; + function inRuleOrder ( v, a ) { + var o = options.ort; + return (o?a:v) + ', ' + (o?v:a); + } + function toPct ( pct ) { return pct + '%'; } + // Takes a base value and an offset. This offset is used for the connect bar size. + // In the initial design for this feature, the origin element was 1% wide. + // Unfortunately, a rounding bug in Chrome makes it impossible to implement this feature + // in this manner: https://bugs.chromium.org/p/chromium/issues/detail?id=798223 + function transformDirection ( a, b ) { + return options.dir ? 100 - a - b : a; + } + // Updates scope_Locations and scope_Values, updates visual state function updateHandlePosition ( handleNumber, to ) { @@ -1947,22 +1965,11 @@ function closure ( target, options, originalOptions ){ // 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); - }; + var rule = 'translate(' + inRuleOrder(toPct(transformDirection(to, 0)), '0') + ')'; + scope_Handles[handleNumber].style[options.transformRule] = rule; - // Set the handle to the new position. - // Use requestAnimationFrame for efficient painting. - // 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 { - stateUpdate(); - } + updateConnect(handleNumber); + updateConnect(handleNumber + 1); } function setZindex ( ) { @@ -1973,7 +1980,7 @@ function closure ( target, options, originalOptions ){ // [[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; + scope_Handles[handleNumber].style.zIndex = zIndex; }); } @@ -2010,31 +2017,40 @@ function closure ( target, options, originalOptions ){ h = scope_Locations[index]; } - scope_Connects[index].style[options.style] = toPct(l); - scope_Connects[index].style[options.styleOposite] = toPct(100 - h); + // We use two rules: + // 'translate' to change the left/top offset; + // 'scale' to change the width of the element; + // As the element has a width of 100%, a translation of 100% is equal to 100% of the parent (.noUi-base) + var connectWidth = h - l; + var translateRule = 'translate(' + inRuleOrder(toPct(transformDirection(l, connectWidth)), '0') + ')'; + var scaleRule = 'scale(' + inRuleOrder(connectWidth / 100, '1') + ')'; + + scope_Connects[index].style[options.transformRule] = translateRule + ' ' + scaleRule; } - // ... - function setValue ( to, handleNumber ) { + // Parses value passed to .set method. Returns current value if not parseable. + function resolveToValue ( to, handleNumber ) { // Setting with null indicates an 'ignore'. // Inputting 'false' is invalid. - if ( to === null || to === false ) { - return; + if ( to === null || to === false || to === undefined ) { + return scope_Locations[handleNumber]; } - // If a formatted number was passed, attemt to decode it. + // If a formatted number was passed, attempt to decode it. if ( typeof to === 'number' ) { to = String(to); } to = options.format.from(to); + to = scope_Spectrum.toStepping(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(handleNumber, scope_Spectrum.toStepping(to), false, false); + // If parsing the number failed, use the current value. + if ( to === false || isNaN(to) ) { + return scope_Locations[handleNumber]; } + + return to; } // Set the slider value. @@ -2046,17 +2062,20 @@ function closure ( target, options, originalOptions ){ // Event fires by default fireSetEvent = (fireSetEvent === undefined ? true : !!fireSetEvent); - values.forEach(setValue); - // Animation is optional. // Make sure the initial values were set before using animated placement. if ( options.animate && !isInit ) { addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration); } - // Now that all base values are set, apply constraints + // First pass, without lookAhead but with lookBackward. Values are set from left to right. + scope_HandleNumbers.forEach(function(handleNumber){ + setHandle(handleNumber, resolveToValue(values[handleNumber], handleNumber), true, false); + }); + + // Second pass. Now that all base values are set, apply constraints scope_HandleNumbers.forEach(function(handleNumber){ - setHandle(handleNumber, scope_Locations[handleNumber], true, false); + setHandle(handleNumber, scope_Locations[handleNumber], true, true); }); setZindex(); diff --git a/distribute/nouislider.min.css b/distribute/nouislider.min.css index 5e26604f..90b4ece3 100644 --- a/distribute/nouislider.min.css +++ b/distribute/nouislider.min.css @@ -1 +1 @@ -/*! nouislider - 10.1.0 - 2017-07-28 13:09:54 */.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;-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;border-radius:4px;box-shadow:inset 0 0 3px rgba(51,51,51,.45);-webkit-transition:background 450ms;transition:background 450ms}.noUi-draggable{cursor:ew-resize}.noUi-vertical .noUi-draggable{cursor:ns-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;white-space:nowrap;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;white-space:nowrap}.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 +/*! nouislider - 11.0.0 - 2018-01-12 20:37:52 */.noUi-target,.noUi-target *{-webkit-touch-callout:none;-webkit-tap-highlight-color:transparent;-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,.noUi-connects{width:100%;height:100%;position:relative;z-index:1}.noUi-connects{overflow:hidden;z-index:0}.noUi-connect,.noUi-origin{will-change:transform;position:absolute;z-index:1;top:0;left:0;height:100%;width:100%;-webkit-transform-origin:0 0;transform-origin:0 0}.noUi-vertical .noUi-origin{width:0}.noUi-horizontal .noUi-origin{height:0}.noUi-handle{position:relative}.noUi-state-tap .noUi-connect,.noUi-state-tap .noUi-origin{-webkit-transition:transform .3s;transition:transform .3s}.noUi-state-drag *{cursor:inherit!important}.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-connects{border-radius:3px}.noUi-connect{background:#3FB8AF}.noUi-draggable{cursor:ew-resize}.noUi-vertical .noUi-draggable{cursor:ns-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;white-space:nowrap;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:translate(-50%,50%);transform:translate(-50%,50%)}.noUi-rtl .noUi-value-horizontal{-webkit-transform:translate(50%,50%);transform:translate(50%,50%)}.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:translate(0,-50%);transform:translate(0,-50%,0);padding-left:25px}.noUi-rtl .noUi-value-vertical{-webkit-transform:translate(0,50%);transform:translate(0,50%)}.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;white-space:nowrap}.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 d4c28259..0a21d751 100644 --- a/distribute/nouislider.min.js +++ b/distribute/nouislider.min.js @@ -1,3 +1,3 @@ -/*! nouislider - 10.1.0 - 2017-07-28 13:09:54 */ +/*! nouislider - 11.0.0 - 2018-01-12 20:37:52 */ -!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"object"==typeof a&&"function"==typeof a.to&&"function"==typeof a.from}function b(a){a.parentElement.removeChild(a)}function c(a){a.preventDefault()}function d(a){return a.filter(function(a){return!this[a]&&(this[a]=!0)},{})}function e(a,b){return Math.round(a/b)*b}function f(a,b){var c=a.getBoundingClientRect(),d=a.ownerDocument,e=d.documentElement,f=o(d);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 g(a){return"number"==typeof a&&!isNaN(a)&&isFinite(a)}function h(a,b,c){c>0&&(l(a,b),setTimeout(function(){m(a,b)},c))}function i(a){return Math.max(Math.min(a,100),0)}function j(a){return Array.isArray(a)?a:[a]}function k(a){a=String(a);var b=a.split(".");return b.length>1?b[1].length:0}function l(a,b){a.classList?a.classList.add(b):a.className+=" "+b}function m(a,b){a.classList?a.classList.remove(b):a.className=a.className.replace(new RegExp("(^|\\b)"+b.split(" ").join("|")+"(\\b|$)","gi")," ")}function n(a,b){return a.classList?a.classList.contains(b):new RegExp("\\b"+b+"\\b").test(a.className)}function o(a){var b=void 0!==window.pageXOffset,c="CSS1Compat"===(a.compatMode||"");return{x:b?window.pageXOffset:c?a.documentElement.scrollLeft:a.body.scrollLeft,y:b?window.pageYOffset:c?a.documentElement.scrollTop:a.body.scrollTop}}function p(){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 q(){var a=!1;try{var b=Object.defineProperty({},"passive",{get:function(){a=!0}});window.addEventListener("test",null,b)}catch(a){}return a}function r(){return window.CSS&&CSS.supports&&CSS.supports("touch-action","none")}function s(a,b){return 100/(b-a)}function t(a,b){return 100*b/(a[1]-a[0])}function u(a,b){return t(a,a[0]<0?b+Math.abs(a[0]):b-a[0])}function v(a,b){return b*(a[1]-a[0])/100+a[0]}function w(a,b){for(var c=1;a>=b[c];)c+=1;return c}function x(a,b,c){if(c>=a.slice(-1)[0])return 100;var d,e,f,g,h=w(c,a);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],f+u([d,e],c)/s(f,g)}function y(a,b,c){if(c>=100)return a.slice(-1)[0];var d,e,f,g,h=w(c,b);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],v([d,e],(c-f)*s(f,g))}function z(a,b,c,d){if(100===d)return d;var f,g,h=w(d,a);return c?(f=a[h-1],g=a[h],d-f>(g-f)/2?g:f):b[h-1]?a[h-1]+e(d-a[h-1],b[h-1]):d}function A(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),!g(d)||!g(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 B(a,b,c){if(!b)return!0;c.xSteps[a]=t([c.xVal[a],c.xVal[a+1]],b)/s(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 C(a,b,c){this.xPct=[],this.xVal=[],this.xSteps=[c||!1],this.xNumSteps=[!1],this.xHighestCompleteStep=[],this.snap=b;var d,e=[];for(d in a)a.hasOwnProperty(d)&&e.push([a[d],d]);for(e.length&&"object"==typeof e[0][0]?e.sort(function(a,b){return a[0][0]-b[0][0]}):e.sort(function(a,b){return a[0]-b[0]}),d=0;d=50)throw new Error("noUiSlider ("+_+"): 'padding' option must be less than half the range.")}}function P(a,b){switch(b){case"ltr":a.dir=0;break;case"rtl":a.dir=1;break;default:throw new Error("noUiSlider ("+_+"): 'direction' option was not recognized.")}}function Q(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(e){if(2!==a.handles)throw new Error("noUiSlider ("+_+"): 'fixed' behaviour must be used with 2 handles");M(a,a.start[1]-a.start[0])}a.events={tap:c||f,drag:d,fixed:e,snap:f,hover:g}}function R(a,b){if(a.multitouch=b,"boolean"!=typeof b)throw new Error("noUiSlider ("+_+"): 'multitouch' option must be a boolean.")}function S(a,b){if(!1!==b)if(!0===b){a.tooltips=[];for(var c=0;c-1?1:"steps"===b?2:0,!g&&i&&(q=0),l===v&&j||(f[n.toFixed(5)]=[l,q]),k=n}}),f}function B(a,b,c){function d(a,b){var c=b===e.cssClasses.value,d=c?j:m,f=c?h:i;return b+" "+d[e.ort]+" "+f[a]}function f(a,f){f[1]=f[1]&&b?b(f[0],f[1]):f[1];var h=k(g,!1);h.className=d(f[1],e.cssClasses.marker),h.style[e.style]=a+"%",f[1]&&(h=k(g,!1),h.className=d(f[1],e.cssClasses.value),h.style[e.style]=a+"%",h.innerText=c.to(f[0]))}var g=wa.createElement("div"),h=[e.cssClasses.valueNormal,e.cssClasses.valueLarge,e.cssClasses.valueSub],i=[e.cssClasses.markerNormal,e.cssClasses.markerLarge,e.cssClasses.markerSub],j=[e.cssClasses.valueHorizontal,e.cssClasses.valueVertical],m=[e.cssClasses.markerHorizontal,e.cssClasses.markerVertical];return l(g,e.cssClasses.pips),l(g,0===e.ort?e.cssClasses.pipsHorizontal:e.cssClasses.pipsVertical),Object.keys(a).forEach(function(b){f(b,a[b])}),g}function C(){la&&(b(la),la=null)}function D(a){C();var b=a.mode,c=a.density||1,d=a.filter||!1,e=a.values||!1,f=a.stepped||!1,g=z(b,e,f),h=A(c,b,g),i=a.format||{to:Math.round};return la=pa.appendChild(B(h,d,i))}function E(){var a=ha.getBoundingClientRect(),b="offset"+["Width","Height"][e.ort];return 0===e.ort?a.width||ha[b]:a.height||ha[b]}function F(a,b,c,d){var f=function(f){return!pa.hasAttribute("disabled")&&(!n(pa,e.cssClasses.tap)&&(!!(f=G(f,d.pageOffset,d.target||b))&&(!(a===ma.start&&void 0!==f.buttons&&f.buttons>1)&&((!d.hover||!f.buttons)&&(oa||f.preventDefault(),f.calcPoint=f.points[e.ort],void c(f,d))))))},g=[];return a.split(" ").forEach(function(a){b.addEventListener(a,f,!!oa&&{passive:!0}),g.push([a,f])}),g}function G(a,b,c){var d,f,g=0===a.type.indexOf("touch"),h=0===a.type.indexOf("mouse"),i=0===a.type.indexOf("pointer");if(0===a.type.indexOf("MSPointer")&&(i=!0),g&&e.multitouch){var j=function(a){return a.target===c||c.contains(a.target)};if("touchstart"===a.type){var k=Array.prototype.filter.call(a.touches,j);if(k.length>1)return!1;d=k[0].pageX,f=k[0].pageY}else{var l=Array.prototype.find.call(a.changedTouches,j);if(!l)return!1;d=l.pageX,f=l.pageY}}else if(g){if(a.touches.length>1)return!1;d=a.changedTouches[0].pageX,f=a.changedTouches[0].pageY}return b=b||o(wa),(h||i)&&(d=a.clientX+b.x,f=a.clientY+b.y),a.pageOffset=b,a.points=[d,f],a.cursor=h||i,a}function H(a){var b=a-f(ha,e.ort),c=100*b/E();return e.dir?100-c:c}function I(a){var b=100,c=!1;return ia.forEach(function(d,e){if(!d.hasAttribute("disabled")){var f=Math.abs(qa[e]-a);f1?d.forEach(function(a,c){var d=S(e,a,e[a]+b,f[c],g[c],!1);!1===d?b=0:(b=d-e[a],e[a]=d)}):f=g=[!0];var h=!1;d.forEach(function(a,d){h=W(a,c[a]+b,f[d],g[d])||h}),h&&d.forEach(function(a){K("update",a),K("slide",a)})}function K(a,b,c){Object.keys(va).forEach(function(d){var f=d.split(".")[0];a===f&&va[d].forEach(function(a){a.call(ka,ua.map(e.format.to),b,ua.slice(),c||!1,qa.slice())})})}function L(a,b){"mouseout"===a.type&&"HTML"===a.target.nodeName&&null===a.relatedTarget&&N(a,b)}function M(a,b){if(-1===navigator.appVersion.indexOf("MSIE 9")&&0===a.buttons&&0!==b.buttonsProperty)return N(a,b);var c=(e.dir?-1:1)*(a.calcPoint-b.startCalcPoint);J(c>0,100*c/b.baseSize,b.locations,b.handleNumbers)}function N(a,b){b.handle&&(m(b.handle,e.cssClasses.active),sa-=1),b.listeners.forEach(function(a){xa.removeEventListener(a[0],a[1])}),0===sa&&(m(pa,e.cssClasses.drag),V(),a.cursor&&(ya.style.cursor="",ya.removeEventListener("selectstart",c))),b.handleNumbers.forEach(function(a){K("change",a),K("set",a),K("end",a)})}function O(a,b){var d;if(1===b.handleNumbers.length){var f=ia[b.handleNumbers[0]];if(f.hasAttribute("disabled"))return!1;d=f.children[0],sa+=1,l(d,e.cssClasses.active)}a.stopPropagation();var g=[],h=F(ma.move,xa,M,{target:a.target,handle:d,listeners:g,startCalcPoint:a.calcPoint,baseSize:E(),pageOffset:a.pageOffset,handleNumbers:b.handleNumbers,buttonsProperty:a.buttons,locations:qa.slice()}),i=F(ma.end,xa,N,{target:a.target,handle:d,listeners:g,handleNumbers:b.handleNumbers}),j=F("mouseout",xa,L,{target:a.target,handle:d,listeners:g,handleNumbers:b.handleNumbers});g.push.apply(g,h.concat(i,j)),a.cursor&&(ya.style.cursor=getComputedStyle(a.target).cursor,ia.length>1&&l(pa,e.cssClasses.drag),ya.addEventListener("selectstart",c,!1)),b.handleNumbers.forEach(function(a){K("start",a)})}function P(a){a.stopPropagation();var b=H(a.calcPoint),c=I(b);if(!1===c)return!1;e.events.snap||h(pa,e.cssClasses.tap,e.animationDuration),W(c,b,!0,!0),V(),K("slide",c,!0),K("update",c,!0),K("change",c,!0),K("set",c,!0),e.events.snap&&O(a,{handleNumbers:[c]})}function Q(a){var b=H(a.calcPoint),c=ta.getStep(b),d=ta.fromStepping(c);Object.keys(va).forEach(function(a){"hover"===a.split(".")[0]&&va[a].forEach(function(a){a.call(ka,d)})})}function R(a){a.fixed||ia.forEach(function(a,b){F(ma.start,a.children[0],O,{handleNumbers:[b]})}),a.tap&&F(ma.start,ha,P,{}),a.hover&&F(ma.move,ha,Q,{hover:!0}),a.drag&&ja.forEach(function(b,c){if(!1!==b&&0!==c&&c!==ja.length-1){var d=ia[c-1],f=ia[c],g=[b];l(b,e.cssClasses.draggable),a.fixed&&(g.push(d.children[0]),g.push(f.children[0])),g.forEach(function(a){F(ma.start,a,O,{handles:[d,f],handleNumbers:[c-1,c]})})}})}function S(a,b,c,d,f,g){return ia.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+(ia.length+b*a);ia[a].childNodes[0].style.zIndex=c})}function W(a,b,c,d){return!1!==(b=S(qa,a,b,c,d,!1))&&(U(a,b),!0)}function X(a){if(ja[a]){var b=0,c=100;0!==a&&(b=qa[a-1]),a!==ja.length-1&&(c=qa[a]),ja[a].style[e.style]=T(b),ja[a].style[e.styleOposite]=T(100-c)}}function Z(a,b){null!==a&&!1!==a&&("number"==typeof a&&(a=String(a)),!1===(a=e.format.from(a))||isNaN(a)||W(b,ta.toStepping(a),!1,!1))}function $(a,b){var c=j(a),d=void 0===qa[0];b=void 0===b||!!b,c.forEach(Z),e.animate&&!d&&h(pa,e.cssClasses.tap,e.animationDuration),ra.forEach(function(a){W(a,qa[a],!0,!1)}),V(),ra.forEach(function(a){K("update",a),null!==c[a]&&b&&K("set",a)})}function aa(a){$(e.start,a)}function ba(){var a=ua.map(e.format.to);return 1===a.length?a[0]:a}function ca(){for(var a in e.cssClasses)e.cssClasses.hasOwnProperty(a)&&m(pa,e.cssClasses[a]);for(;pa.firstChild;)pa.removeChild(pa.firstChild);delete pa.noUiSlider}function da(){return qa.map(function(a,b){var c=ta.getNearbySteps(a),d=ua[b],e=c.thisStep.step,f=null;!1!==e&&d+e>c.stepAfter.startValue&&(e=c.stepAfter.startValue-d),f=d>c.thisStep.startValue?c.thisStep.step:!1!==c.stepBefore.step&&d-c.stepBefore.highestStep,100===a?e=null:0===a&&(f=null);var g=ta.countStepDecimals();return null!==e&&!1!==e&&(e=Number(e.toFixed(g))),null!==f&&!1!==f&&(f=Number(f.toFixed(g))),[f,e]})}function ea(a,b){va[a]=va[a]||[],va[a].push(b),"update"===a.split(".")[0]&&ia.forEach(function(a,b){K("update",b)})}function fa(a){var b=a&&a.split(".")[0],c=b&&a.substring(b.length);Object.keys(va).forEach(function(a){var d=a.split(".")[0],e=a.substring(d.length);b&&b!==d||c&&c!==e||delete va[a]})}function ga(a,b){var c=ba(),d=["margin","limit","padding","range","animate","snap","step","format"];d.forEach(function(b){void 0!==a[b]&&(g[b]=a[b])});var f=Y(g);d.forEach(function(b){void 0!==a[b]&&(e[b]=f[b])}),ta=f.spectrum,e.margin=f.margin,e.limit=f.limit,e.padding=f.padding,e.pips&&D(e.pips),qa=[],$(a.start||c,b)}var ha,ia,ja,ka,la,ma=p(),na=r(),oa=na&&q(),pa=a,qa=[],ra=[],sa=0,ta=e.spectrum,ua=[],va={},wa=a.ownerDocument,xa=wa.documentElement,ya=wa.body;if(pa.noUiSlider)throw new Error("noUiSlider ("+_+"): Slider was already initialized.");return v(pa),u(e.connect,ha),ka={destroy:ca,steps:da,on:ea,off:fa,get:ba,set:$,reset:aa,__moveHandles:function(a,b,c){J(a,b,qa,c)},options:g,updateOptions:ga,target:pa,removePips:C,pips:D},R(e.events),$(e.start),e.pips&&D(e.pips),e.tooltips&&x(),y(),ka}function $(a,b){if(!a||!a.nodeName)throw new Error("noUiSlider ("+_+"): create requires a single element, got: "+a);var c=Y(b,a),d=Z(a,c,b);return a.noUiSlider=d,d}var _="10.1.0";C.prototype.getMargin=function(a){var b=this.xNumSteps[0];if(b&&a/b%1!=0)throw new Error("noUiSlider ("+_+"): 'limit', 'margin' and 'padding' must be divisible by step.");return 2===this.xPct.length&&t(this.xVal,a)},C.prototype.toStepping=function(a){return a=x(this.xVal,this.xPct,a)},C.prototype.fromStepping=function(a){return y(this.xVal,this.xPct,a)},C.prototype.getStep=function(a){return a=z(this.xPct,this.xSteps,this.snap,a)},C.prototype.getNearbySteps=function(a){var b=w(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]}}},C.prototype.countStepDecimals=function(){var a=this.xNumSteps.map(k);return Math.max.apply(null,a)},C.prototype.convert=function(a){return this.getStep(this.toStepping(a))};var aa={to:function(a){return void 0!==a&&a.toFixed(2)},from:Number};return{version:_,create:$}}); \ 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){return"object"==typeof a&&"function"==typeof a.to&&"function"==typeof a.from}function b(a){a.parentElement.removeChild(a)}function c(a){a.preventDefault()}function d(a){return a.filter(function(a){return!this[a]&&(this[a]=!0)},{})}function e(a,b){return Math.round(a/b)*b}function f(a,b){var c=a.getBoundingClientRect(),d=a.ownerDocument,e=d.documentElement,f=o(d);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 g(a){return"number"==typeof a&&!isNaN(a)&&isFinite(a)}function h(a,b,c){c>0&&(l(a,b),setTimeout(function(){m(a,b)},c))}function i(a){return Math.max(Math.min(a,100),0)}function j(a){return Array.isArray(a)?a:[a]}function k(a){a=String(a);var b=a.split(".");return b.length>1?b[1].length:0}function l(a,b){a.classList?a.classList.add(b):a.className+=" "+b}function m(a,b){a.classList?a.classList.remove(b):a.className=a.className.replace(new RegExp("(^|\\b)"+b.split(" ").join("|")+"(\\b|$)","gi")," ")}function n(a,b){return a.classList?a.classList.contains(b):new RegExp("\\b"+b+"\\b").test(a.className)}function o(a){var b=void 0!==window.pageXOffset,c="CSS1Compat"===(a.compatMode||"");return{x:b?window.pageXOffset:c?a.documentElement.scrollLeft:a.body.scrollLeft,y:b?window.pageYOffset:c?a.documentElement.scrollTop:a.body.scrollTop}}function p(){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 q(){var a=!1;try{var b=Object.defineProperty({},"passive",{get:function(){a=!0}});window.addEventListener("test",null,b)}catch(a){}return a}function r(){return window.CSS&&CSS.supports&&CSS.supports("touch-action","none")}function s(a,b){return 100/(b-a)}function t(a,b){return 100*b/(a[1]-a[0])}function u(a,b){return t(a,a[0]<0?b+Math.abs(a[0]):b-a[0])}function v(a,b){return b*(a[1]-a[0])/100+a[0]}function w(a,b){for(var c=1;a>=b[c];)c+=1;return c}function x(a,b,c){if(c>=a.slice(-1)[0])return 100;var d,e,f,g,h=w(c,a);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],f+u([d,e],c)/s(f,g)}function y(a,b,c){if(c>=100)return a.slice(-1)[0];var d,e,f,g,h=w(c,b);return d=a[h-1],e=a[h],f=b[h-1],g=b[h],v([d,e],(c-f)*s(f,g))}function z(a,b,c,d){if(100===d)return d;var f,g,h=w(d,a);return c?(f=a[h-1],g=a[h],d-f>(g-f)/2?g:f):b[h-1]?a[h-1]+e(d-a[h-1],b[h-1]):d}function A(a,b,c){var d;if("number"==typeof b&&(b=[b]),!Array.isArray(b))throw new Error("noUiSlider ("+Z+"): 'range' contains invalid value.");if(d="min"===a?0:"max"===a?100:parseFloat(a),!g(d)||!g(b[0]))throw new Error("noUiSlider ("+Z+"): '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 B(a,b,c){if(!b)return!0;c.xSteps[a]=t([c.xVal[a],c.xVal[a+1]],b)/s(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 C(a,b,c){this.xPct=[],this.xVal=[],this.xSteps=[c||!1],this.xNumSteps=[!1],this.xHighestCompleteStep=[],this.snap=b;var d,e=[];for(d in a)a.hasOwnProperty(d)&&e.push([a[d],d]);for(e.length&&"object"==typeof e[0][0]?e.sort(function(a,b){return a[0][0]-b[0][0]}):e.sort(function(a,b){return a[0]-b[0]}),d=0;d=50||a.padding[1]>=50)throw new Error("noUiSlider ("+Z+"): 'padding' option must be less than half the range.")}}function P(a,b){switch(b){case"ltr":a.dir=0;break;case"rtl":a.dir=1;break;default:throw new Error("noUiSlider ("+Z+"): 'direction' option was not recognized.")}}function Q(a,b){if("string"!=typeof b)throw new Error("noUiSlider ("+Z+"): '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(e){if(2!==a.handles)throw new Error("noUiSlider ("+Z+"): 'fixed' behaviour must be used with 2 handles");M(a,a.start[1]-a.start[0])}a.events={tap:c||f,drag:d,fixed:e,snap:f,hover:g}}function R(a,b){if(!1!==b)if(!0===b){a.tooltips=[];for(var c=0;c= 2) required for mode 'count'.");var d=b-1,e=100/d;for(b=[];d--;)b[d]=d*e;b.push(100),a="positions"}return"positions"===a?b.map(function(a){return va.fromStepping(c?va.getStep(a):a)}):"values"===a?c?b.map(function(a){return va.fromStepping(va.getStep(va.toStepping(a)))}):b:void 0}function A(a,b,c){function e(a,b){return(a+b).toFixed(7)/1}var f={},g=va.xVal[0],h=va.xVal[va.xVal.length-1],i=!1,j=!1,k=0;return c=d(c.slice().sort(function(a,b){return a-b})),c[0]!==g&&(c.unshift(g),i=!0),c[c.length-1]!==h&&(c.push(h),j=!0),c.forEach(function(d,g){var h,l,m,n,o,p,q,r,s,t,u=d,v=c[g+1];if("steps"===b&&(h=va.xNumSteps[g]),h||(h=v-u),!1!==u&&void 0!==v)for(h=Math.max(h,1e-7),l=u;l<=v;l=e(l,h)){for(n=va.toStepping(l),o=n-k,r=o/a,s=Math.round(r),t=o/s,m=1;m<=s;m+=1)p=k+m*t,f[p.toFixed(5)]=["x",0];q=c.indexOf(l)>-1?1:"steps"===b?2:0,!g&&i&&(q=0),l===v&&j||(f[n.toFixed(5)]=[l,q]),k=n}}),f}function B(a,b,c){function d(a,b){var c=b===e.cssClasses.value,d=c?j:m,f=c?h:i;return b+" "+d[e.ort]+" "+f[a]}function f(a,f){f[1]=f[1]&&b?b(f[0],f[1]):f[1];var h=k(g,!1);h.className=d(f[1],e.cssClasses.marker),h.style[e.style]=a+"%",f[1]&&(h=k(g,!1),h.className=d(f[1],e.cssClasses.value),h.setAttribute("data-value",f[0]),h.style[e.style]=a+"%",h.innerText=c.to(f[0]))}var g=ya.createElement("div"),h=[e.cssClasses.valueNormal,e.cssClasses.valueLarge,e.cssClasses.valueSub],i=[e.cssClasses.markerNormal,e.cssClasses.markerLarge,e.cssClasses.markerSub],j=[e.cssClasses.valueHorizontal,e.cssClasses.valueVertical],m=[e.cssClasses.markerHorizontal,e.cssClasses.markerVertical];return l(g,e.cssClasses.pips),l(g,0===e.ort?e.cssClasses.pipsHorizontal:e.cssClasses.pipsVertical),Object.keys(a).forEach(function(b){f(b,a[b])}),g}function C(){na&&(b(na),na=null)}function D(a){C();var b=a.mode,c=a.density||1,d=a.filter||!1,e=a.values||!1,f=a.stepped||!1,g=z(b,e,f),h=A(c,b,g),i=a.format||{to:Math.round};return na=ra.appendChild(B(h,d,i))}function E(){var a=ja.getBoundingClientRect(),b="offset"+["Width","Height"][e.ort];return 0===e.ort?a.width||ja[b]:a.height||ja[b]}function F(a,b,c,d){var f=function(f){return!!(f=G(f,d.pageOffset,d.target||b))&&(!(ra.hasAttribute("disabled")&&!d.doNotReject)&&(!(n(ra,e.cssClasses.tap)&&!d.doNotReject)&&(!(a===oa.start&&void 0!==f.buttons&&f.buttons>1)&&((!d.hover||!f.buttons)&&(qa||f.preventDefault(),f.calcPoint=f.points[e.ort],void c(f,d))))))},g=[];return a.split(" ").forEach(function(a){b.addEventListener(a,f,!!qa&&{passive:!0}),g.push([a,f])}),g}function G(a,b,c){var d,e,f=0===a.type.indexOf("touch"),g=0===a.type.indexOf("mouse"),h=0===a.type.indexOf("pointer");if(0===a.type.indexOf("MSPointer")&&(h=!0),f){var i=function(a){return a.target===c||c.contains(a.target)};if("touchstart"===a.type){var j=Array.prototype.filter.call(a.touches,i);if(j.length>1)return!1;d=j[0].pageX,e=j[0].pageY}else{var k=Array.prototype.find.call(a.changedTouches,i);if(!k)return!1;d=k.pageX,e=k.pageY}}return b=b||o(ya),(g||h)&&(d=a.clientX+b.x,e=a.clientY+b.y),a.pageOffset=b,a.points=[d,e],a.cursor=g||h,a}function H(a){var b=a-f(ja,e.ort),c=100*b/E();return e.dir?100-c:c}function I(a){var b=100,c=!1;return ka.forEach(function(d,e){if(!d.hasAttribute("disabled")){var f=Math.abs(sa[e]-a);f1?d.forEach(function(a,c){var d=S(e,a,e[a]+b,f[c],g[c],!1);!1===d?b=0:(b=d-e[a],e[a]=d)}):f=g=[!0];var h=!1;d.forEach(function(a,d){h=$(a,c[a]+b,f[d],g[d])||h}),h&&d.forEach(function(a){K("update",a),K("slide",a)})}function K(a,b,c){Object.keys(xa).forEach(function(d){var f=d.split(".")[0];a===f&&xa[d].forEach(function(a){a.call(ma,wa.map(e.format.to),b,wa.slice(),c||!1,sa.slice())})})}function L(a,b){"mouseout"===a.type&&"HTML"===a.target.nodeName&&null===a.relatedTarget&&N(a,b)}function M(a,b){if(-1===navigator.appVersion.indexOf("MSIE 9")&&0===a.buttons&&0!==b.buttonsProperty)return N(a,b);var c=(e.dir?-1:1)*(a.calcPoint-b.startCalcPoint);J(c>0,100*c/b.baseSize,b.locations,b.handleNumbers)}function N(a,b){b.handle&&(m(b.handle,e.cssClasses.active),ua-=1),b.listeners.forEach(function(a){za.removeEventListener(a[0],a[1])}),0===ua&&(m(ra,e.cssClasses.drag),Y(),a.cursor&&(Aa.style.cursor="",Aa.removeEventListener("selectstart",c))),b.handleNumbers.forEach(function(a){K("change",a),K("set",a),K("end",a)})}function O(a,b){var d;if(1===b.handleNumbers.length){var f=ka[b.handleNumbers[0]];if(f.hasAttribute("disabled"))return!1;d=f.children[0],ua+=1,l(d,e.cssClasses.active)}a.stopPropagation();var g=[],h=F(oa.move,za,M,{target:a.target,handle:d,listeners:g,startCalcPoint:a.calcPoint,baseSize:E(),pageOffset:a.pageOffset,handleNumbers:b.handleNumbers,buttonsProperty:a.buttons,locations:sa.slice()}),i=F(oa.end,za,N,{target:a.target,handle:d,listeners:g,doNotReject:!0,handleNumbers:b.handleNumbers}),j=F("mouseout",za,L,{target:a.target,handle:d,listeners:g,doNotReject:!0,handleNumbers:b.handleNumbers});g.push.apply(g,h.concat(i,j)),a.cursor&&(Aa.style.cursor=getComputedStyle(a.target).cursor,ka.length>1&&l(ra,e.cssClasses.drag),Aa.addEventListener("selectstart",c,!1)),b.handleNumbers.forEach(function(a){K("start",a)})}function P(a){a.stopPropagation();var b=H(a.calcPoint),c=I(b);if(!1===c)return!1;e.events.snap||h(ra,e.cssClasses.tap,e.animationDuration),$(c,b,!0,!0),Y(),K("slide",c,!0),K("update",c,!0),K("change",c,!0),K("set",c,!0),e.events.snap&&O(a,{handleNumbers:[c]})}function Q(a){var b=H(a.calcPoint),c=va.getStep(b),d=va.fromStepping(c);Object.keys(xa).forEach(function(a){"hover"===a.split(".")[0]&&xa[a].forEach(function(a){a.call(ma,d)})})}function R(a){a.fixed||ka.forEach(function(a,b){F(oa.start,a.children[0],O,{handleNumbers:[b]})}),a.tap&&F(oa.start,ja,P,{}),a.hover&&F(oa.move,ja,Q,{hover:!0}),a.drag&&la.forEach(function(b,c){if(!1!==b&&0!==c&&c!==la.length-1){var d=ka[c-1],f=ka[c],g=[b];l(b,e.cssClasses.draggable),a.fixed&&(g.push(d.children[0]),g.push(f.children[0])),g.forEach(function(a){F(oa.start,a,O,{handles:[d,f],handleNumbers:[c-1,c]})})}})}function S(a,b,c,d,f,g){return ka.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+(ka.length+b*a);ka[a].style.zIndex=c})}function $(a,b,c,d){return!1!==(b=S(sa,a,b,c,d,!1))&&(X(a,b),!0)}function _(a){if(la[a]){var b=0,c=100;0!==a&&(b=sa[a-1]),a!==la.length-1&&(c=sa[a]);var d=c-b,f="translate("+T(U(V(b,d)),"0")+")",g="scale("+T(d/100,"1")+")";la[a].style[e.transformRule]=f+" "+g}}function aa(a,b){return null===a||!1===a||void 0===a?sa[b]:("number"==typeof a&&(a=String(a)),a=e.format.from(a),a=va.toStepping(a),!1===a||isNaN(a)?sa[b]:a)}function ba(a,b){var c=j(a),d=void 0===sa[0];b=void 0===b||!!b,e.animate&&!d&&h(ra,e.cssClasses.tap,e.animationDuration),ta.forEach(function(a){$(a,aa(c[a],a),!0,!1)}),ta.forEach(function(a){$(a,sa[a],!0,!0)}),Y(),ta.forEach(function(a){K("update",a),null!==c[a]&&b&&K("set",a)})}function ca(a){ba(e.start,a)}function da(){var a=wa.map(e.format.to);return 1===a.length?a[0]:a}function ea(){for(var a in e.cssClasses)e.cssClasses.hasOwnProperty(a)&&m(ra,e.cssClasses[a]);for(;ra.firstChild;)ra.removeChild(ra.firstChild);delete ra.noUiSlider}function fa(){return sa.map(function(a,b){var c=va.getNearbySteps(a),d=wa[b],e=c.thisStep.step,f=null;!1!==e&&d+e>c.stepAfter.startValue&&(e=c.stepAfter.startValue-d),f=d>c.thisStep.startValue?c.thisStep.step:!1!==c.stepBefore.step&&d-c.stepBefore.highestStep,100===a?e=null:0===a&&(f=null);var g=va.countStepDecimals();return null!==e&&!1!==e&&(e=Number(e.toFixed(g))),null!==f&&!1!==f&&(f=Number(f.toFixed(g))),[f,e]})}function ga(a,b){xa[a]=xa[a]||[],xa[a].push(b),"update"===a.split(".")[0]&&ka.forEach(function(a,b){K("update",b)})}function ha(a){var b=a&&a.split(".")[0],c=b&&a.substring(b.length);Object.keys(xa).forEach(function(a){var d=a.split(".")[0],e=a.substring(d.length);b&&b!==d||c&&c!==e||delete xa[a]})}function ia(a,b){var c=da(),d=["margin","limit","padding","range","animate","snap","step","format"];d.forEach(function(b){void 0!==a[b]&&(g[b]=a[b])});var f=W(g);d.forEach(function(b){void 0!==a[b]&&(e[b]=f[b])}),va=f.spectrum,e.margin=f.margin,e.limit=f.limit,e.padding=f.padding,e.pips&&D(e.pips),sa=[],ba(a.start||c,b)}var ja,ka,la,ma,na,oa=p(),pa=r(),qa=pa&&q(),ra=a,sa=[],ta=[],ua=0,va=e.spectrum,wa=[],xa={},ya=a.ownerDocument,za=ya.documentElement,Aa=ya.body;if(ra.noUiSlider)throw new Error("noUiSlider ("+Z+"): Slider was already initialized.");return v(ra),u(e.connect,ja),ma={destroy:ea,steps:fa,on:ga,off:ha,get:da,set:ba,reset:ca,__moveHandles:function(a,b,c){J(a,b,sa,c)},options:g,updateOptions:ia,target:ra,removePips:C,pips:D},R(e.events),ba(e.start),e.pips&&D(e.pips),e.tooltips&&x(),y(),ma}function Y(a,b){if(!a||!a.nodeName)throw new Error("noUiSlider ("+Z+"): create requires a single element, got: "+a);var c=W(b,a),d=X(a,c,b);return a.noUiSlider=d,d}var Z="11.0.0";C.prototype.getMargin=function(a){var b=this.xNumSteps[0];if(b&&a/b%1!=0)throw new Error("noUiSlider ("+Z+"): 'limit', 'margin' and 'padding' must be divisible by step.");return 2===this.xPct.length&&t(this.xVal,a)},C.prototype.toStepping=function(a){return a=x(this.xVal,this.xPct,a)},C.prototype.fromStepping=function(a){return y(this.xVal,this.xPct,a)},C.prototype.getStep=function(a){return a=z(this.xPct,this.xSteps,this.snap,a)},C.prototype.getNearbySteps=function(a){var b=w(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]}}},C.prototype.countStepDecimals=function(){var a=this.xNumSteps.map(k);return Math.max.apply(null,a)},C.prototype.convert=function(a){return this.getStep(this.toStepping(a))};var $={to:function(a){return void 0!==a&&a.toFixed(2)},from:Number};return{version:Z,create:Y}}); \ No newline at end of file diff --git a/documentation/_run/helpers.php b/documentation/_run/helpers.php index b840122e..a30cac2b 100644 --- a/documentation/_run/helpers.php +++ b/documentation/_run/helpers.php @@ -27,6 +27,13 @@ function loadShowCSS ( $name ) { '
' . $content . '
'; } + function showCSS ( $name ) { + + $content = fgc($name . '.css'); + + echo "\r\n".'
' . $content . '
'; + } + function sect ( $title ) { echo '§'; } diff --git a/documentation/_run/menu.php b/documentation/_run/menu.php index ca83072a..ac6b7a85 100644 --- a/documentation/_run/menu.php +++ b/documentation/_run/menu.php @@ -1,12 +1,12 @@ diff --git a/documentation/_run/router.php b/documentation/_run/router.php index 616fc383..867ae068 100644 --- a/documentation/_run/router.php +++ b/documentation/_run/router.php @@ -30,5 +30,5 @@ ob_end_clean(); - $distribute = '/noUiSlider/distribute'; + $distribute = '/nouislider/distribute'; include '_run/index.php'; diff --git a/documentation/download.php b/documentation/download.php index d5a8aa7f..c616ad00 100644 --- a/documentation/download.php +++ b/documentation/download.php @@ -11,7 +11,7 @@

noUiSlider is open source, and you can use it for free in any personal or commercial product. No attribution required. Both the uncompressed and compressed version of noUiSlider are available in a .zip release, which is hosted by Github and available over https.

- Download noUiSlider from Github + Download noUiSlider from Github diff --git a/documentation/examples.php b/documentation/examples.php index f858c815..d3e70b20 100644 --- a/documentation/examples.php +++ b/documentation/examples.php @@ -5,10 +5,12 @@
    -
  • Colorpicker
  • +
  • Color Picker
  • Working with HTML5 input types
  • Using non linear ranges
  • Locking two sliders together
  • +
  • Moving the slider by clicking pips
  • +
  • Only showing tooltips when sliding handles
  • Colored connect elements
  • Changing the slider value by keypress
  • Skipping values on a slider
  • @@ -85,10 +87,7 @@
    -

    noUiSlider is perfectly fine serializing values to any element with a .val() method, so lets try using type="number" and <select>.

    -

    Note that if your browser doesn't support an input type, it will just assume "text". If you'd like to know more, consider reading this article.

    - -

    We'll append <option> elements to the <select> dynamically.

    +

    noUiSlider's 'update' method is useful for synchronizing with other elements, such as <input> (type="number") and <select>.

    @@ -114,7 +113,7 @@
    -
    Linking the <select> and <input>
    +
    Updating the <select> and <input>
    @@ -136,7 +135,7 @@
    -

    One of noUiSlider's core features is the ability to divide the range in a non-linear fashion. Stepping can be applied, too! The example on the right shows where the handles are on the slider range in values and percentages.

    +

    One of noUiSlider's core features is the ability to divide the range in a non-linear fashion. Stepping can be applied. The example on the right shows where the handles are on the slider range in values and percentages.

    @@ -218,6 +217,59 @@
    + +

    Moving the slider by clicking pips

    + +
    + +
    + +

    Issue #733 asks about clicking pips to move the slider to their value. noUiSlider 11 adds a data-value attribute to all .noUi-value elements that makes this easy.

    + +
    +
    + + + +
    +
    + +
    + +
    Setup
    + +
    + +
    + + +
    +
    + + + +

    Only showing tooltips when sliding handles

    + +
    + +
    + +

    Issue #836 requested a way to toggle tooltips after slider creation. This effect can be achieved by using the .noUi-active class to show and hide the tooltips. No additional JavaScript is involved.

    + +
    +
    + + +
    +
    + +
    + + +
    +
    + +

    Colored Connect Elements

    @@ -229,11 +281,19 @@
    +
    + +
    Slider setup
    + +
    + +
    +
    diff --git a/documentation/examples/click-pips-setup.js b/documentation/examples/click-pips-setup.js new file mode 100644 index 00000000..0ab60b35 --- /dev/null +++ b/documentation/examples/click-pips-setup.js @@ -0,0 +1,10 @@ +var pipsSlider = document.getElementById('slider-pips'); + +noUiSlider.create(pipsSlider, { + range: { + min: 0, + max: 100 + }, + start: [ 50 ], + pips: { mode: 'count', values: 5 } +}); diff --git a/documentation/examples/click-pips.js b/documentation/examples/click-pips.js new file mode 100644 index 00000000..50116baa --- /dev/null +++ b/documentation/examples/click-pips.js @@ -0,0 +1,13 @@ +var pips = pipsSlider.querySelectorAll('.noUi-value'); + +function clickOnPip ( ) { + var value = Number(this.getAttribute('data-value')); + pipsSlider.noUiSlider.set(value); +} + +for ( var i = 0; i < pips.length; i++ ) { + + // For this example. Do this in CSS! + pips[i].style.cursor = 'pointer'; + pips[i].addEventListener('click', clickOnPip); +} diff --git a/documentation/examples/colored-setup.js b/documentation/examples/colored-setup.js new file mode 100644 index 00000000..0e079736 --- /dev/null +++ b/documentation/examples/colored-setup.js @@ -0,0 +1,10 @@ +var slider = document.getElementById('slider-color'); + +noUiSlider.create(slider, { + start: [ 4000, 8000, 12000, 16000 ], + connect: [false, true, true, true, true], + range: { + 'min': [ 2000 ], + 'max': [ 20000 ] + } +}); diff --git a/documentation/examples/colored.js b/documentation/examples/colored.js index 443b5337..d11a7260 100644 --- a/documentation/examples/colored.js +++ b/documentation/examples/colored.js @@ -1,14 +1,3 @@ -var slider = document.getElementById('slider-color'); - -noUiSlider.create(slider, { - start: [ 4000, 8000, 12000, 16000 ], - connect: [false, true, true, true, true], - range: { - 'min': [ 2000 ], - 'max': [ 20000 ] - } -}); - var connect = slider.querySelectorAll('.noUi-connect'); var classes = ['c-1-color', 'c-2-color', 'c-3-color', 'c-4-color', 'c-5-color']; diff --git a/documentation/examples/hiding-tooltips.css b/documentation/examples/hiding-tooltips.css new file mode 100644 index 00000000..24f72333 --- /dev/null +++ b/documentation/examples/hiding-tooltips.css @@ -0,0 +1,6 @@ +.noUi-tooltip { + display: none; +} +.noUi-active .noUi-tooltip { + display: block; +} diff --git a/documentation/examples/hiding-tooltips.js b/documentation/examples/hiding-tooltips.js new file mode 100644 index 00000000..69a7a2f4 --- /dev/null +++ b/documentation/examples/hiding-tooltips.js @@ -0,0 +1,10 @@ +var hidingTooltipSlider = document.getElementById('slider-hide'); + +noUiSlider.create(hidingTooltipSlider, { + range: { + min: 0, + max: 100 + }, + start: [ 20, 80 ], + tooltips: true +}); diff --git a/documentation/more.php b/documentation/more.php index f6dc090f..06028caf 100644 --- a/documentation/more.php +++ b/documentation/more.php @@ -1,9 +1,16 @@ -

    More...

    +
    + +
    +

    Disabling a slider

    @@ -12,13 +19,15 @@
    -

    Disabling a slider is identical to disabling a checkbox or textarea; simply set the disabled attribute.

    +

    Disabling a slider is identical to disabling a checkbox or textarea; add the disabled attribute.

    + +

    A disabled slider can't be changed by user interaction (sliding, clicking or touching), but you can still change its value using the .set() method.

    -

    A disabled slider can't be changed by sliding, click or touching, but you can still change its value using the .set() method. You can use CSS to show the disabled state. The default theme also sets a not-allowed cursor.

    +

    CSS can be used to show the disabled state. The default stylesheet also sets a not-allowed cursor.

    -

    The slider below is disabled when the checkbox gets checked, and re-enabled when it is unchecked.

    +

    The slider below is disabled when the checkbox is checked, and re-enabled when it is unchecked.

    -

    Individual handles can also be disabled by setting the disabled attribute on a .noUi-origin element.

    +

    Individual handles can be disabled by adding a disabled attribute to a .noUi-origin element.

    @@ -63,9 +72,15 @@
    -

    noUiSlider has an updateOptions(newOptions, fireSetEvent) method that can change the 'margin', 'limit', 'step', 'range', 'animate' and 'snap' options. All other options require changes to the slider's HTML or event bindings.

    +

    noUiSlider has an update method that can change the 'margin', 'limit', 'step', 'range', 'animate' and 'snap' options.

    -

    To update any other option, destroy the slider using slider.noUiSlider.destroy() and create a new one. Note that events are not unbound when destroying a slider.

    +

    All other options require changes to the slider's HTML or event bindings.

    + +

    To update any other option, destroy the slider using slider.noUiSlider.destroy() and create a new one. Events are unbound when destroying a slider.

    + +

    The update method can be called as slider.noUiSlider.updateOptions(newOptions, [fireSetEvent]).

    + +

    Options that can not be updated will be ignored without errors.

    The 'update' event fires after updating the slider.

    @@ -73,8 +88,10 @@

    The 'set' event fires when the slider values are restored. If this is unwanted, you can pass false as the second parameter, fireSetEvent.

    -

    Options can be read from the slider using the slider.noUiSlider.options property. This property contains a reference to the options object passed when creating the slider. This object is modified when calling updateOptions. Note that if you initiate multiple sliders using the same options object and update a subset of them later, this will move the options property out of sync with the actual slider options.

    - +

    Options can be read from the slider using the slider.noUiSlider.options property. This property contains a reference to the options object passed when creating the slider. This object is modified when calling updateOptions.

    + +

    Note that if you initiate multiple sliders using the same options object and update a subset of them later, this will move the options property out of sync with the actual slider options.

    +
    @@ -89,7 +106,7 @@
    -
    The HTML for this example
    +
    HTML for this example
    @@ -128,17 +145,13 @@
    -

    Styling noUiSlider is easy. The default stylesheet contains helpful comments to get a head start. I strongly recommend using the default stylesheet as a starting point when re-styling noUiSlider.

    +

    Styling noUiSlider is easy. The default stylesheet contains helpful comments to get a head start.

    + +

    It is recommended to use the default stylesheet, overriding where necessary, as a starting point when re-styling noUiSlider.

    If your styling system doesn't match the convention in noUiSlider, you can use the cssPrefix and cssClasses options to reconfigure the markup.

    - -

    Things to watch out for

    - -
      -
    • If you want the handles to stay within the slider bar (instead of the default overlap), have a look at the CSS detailed to the right. Add the styles to your stylesheet and give your element the noUi-extended class for this effect.
    • -
    • The .noUi-connect and .noUi-background classes are applied to various elements. -
    • To position your handles on the center of its relative position, it should have a negative offset of half the handle width. See the default theme for reference.
    • -
    + +

    noUiSlider listens to events on the .noUi-base element. To add padding on the .noUi-target element and contain handles within the slider width, .noUi-base needs to be extended. This can be done using CSS :before and :after pseudo-elements. An example is included to the right.

    @@ -187,23 +200,18 @@
    -
    Overriding classes
    +
    Add padding to slider base
    -
    +
    -
    Containing handles within the slider bar (horizontal)
    +
    Overriding classes
    -
    +
    -
    Containing handles within the slider bar (vertical)
    - -
    -
    -
diff --git a/documentation/more/classes.js b/documentation/more/classes.js index b565a802..fdb8340e 100644 --- a/documentation/more/classes.js +++ b/documentation/more/classes.js @@ -6,7 +6,8 @@ noUiSlider.create(slider, { }, cssPrefix: 'noUi-', // defaults to 'noUi-', cssClasses: { - // Full list of classnames to override. Does NOT extend the default classes. + // Full list of classnames to override. + // Does NOT extend the default classes. // Have a look at the source for the full, current list: // https://github.com/leongersen/noUiSlider/blob/master/src/js/options.js#L398 } diff --git a/documentation/more/horizontal-contain.css b/documentation/more/horizontal-contain.css deleted file mode 100644 index 28448746..00000000 --- a/documentation/more/horizontal-contain.css +++ /dev/null @@ -1,9 +0,0 @@ -.noUi-horizontal.noUi-extended { - padding-right: 32px; -} -.noUi-horizontal.noUi-extended .noUi-handle { - left: -1px; -} -.noUi-horizontal.noUi-extended .noUi-origin { - right: -32px; -} diff --git a/documentation/more/padding.css b/documentation/more/padding.css new file mode 100644 index 00000000..8d9c8fbb --- /dev/null +++ b/documentation/more/padding.css @@ -0,0 +1,18 @@ +.noUi-target { + padding: 0 17px; +} +.noUi-base:before, +.noUi-base:after { + width: 17px; + content: ""; + position: absolute; + top: 0; + height: 100%; + display: block; +} +.noUi-base:before { + left: -17px; +} +.noUi-base:after { + left: 100%; +} diff --git a/documentation/more/update-setup.js b/documentation/more/update-setup.js index 8d628278..62010d0a 100644 --- a/documentation/more/update-setup.js +++ b/documentation/more/update-setup.js @@ -1,5 +1,5 @@ -var updateSlider = document.getElementById('slider-update'), - updateSliderValue = document.getElementById('slider-update-value'); +var updateSlider = document.getElementById('slider-update'); +var updateSliderValue = document.getElementById('slider-update-value'); noUiSlider.create(updateSlider, { range: { diff --git a/documentation/more/vertical-contain.css b/documentation/more/vertical-contain.css deleted file mode 100644 index 4200e13c..00000000 --- a/documentation/more/vertical-contain.css +++ /dev/null @@ -1,9 +0,0 @@ -.noUi-vertical.noUi-extended { - padding-bottom: 32px; -} -.noUi-vertical.noUi-extended .noUi-handle { - top: -1px; -} -.noUi-vertical.noUi-extended .noUi-origin { - bottom: -32px; -} diff --git a/documentation/pips.php b/documentation/pips.php index 69f84b3c..1f17bf10 100644 --- a/documentation/pips.php +++ b/documentation/pips.php @@ -12,6 +12,8 @@

This feature allows you to generate points along the slider.

Five options can be set: mode to determine where to place pips, values as additional options for mode, stepped to round pip values to the slider stepping, density to pre-scale the number of pips, and filter to manually modify pip size.

+

The density value controls how many pips are placed on one percent of the slider range. With the default value of 1, there is one pip per percent. For a value of 2, a pip is placed for every 2 percent. A value below one will place more than one pip per percentage.

+

All sliders on the page use the same range, as displayed to the right.

diff --git a/documentation/slider-options.php b/documentation/slider-options.php index ae75b368..02398190 100644 --- a/documentation/slider-options.php +++ b/documentation/slider-options.php @@ -9,11 +9,29 @@
-

noUiSlider can be configured with a wide variety of options, which can be use to customize the slider in to doing exactly what you want. For options regarding the slider range, see slider values.

+

noUiSlider can be configured with a wide variety of options, which can be use to customize a slider's behaviour.

+ +

For options regarding the slider range, see slider values.

+
+ +
+ +

Start

@@ -21,7 +39,9 @@
-

The start option sets the number of handles and corresponding start positions, relative to range.

+

The start option sets the number of handles and corresponding start positions.

+ +

The start option uses the slider's 'format' option to decode the input. Number input will be cast to string and decoded.

@@ -33,9 +53,9 @@
none
Accepted values -
number,
- array[number],
- array[number, number, ...] +
'string',
+ array['string'],
+ array['string', 'string', ...]
@@ -54,7 +74,7 @@
-

The connect setting can be used to control the (green) bar between the handles, or the edges of the slider.

+

The connect option can be used to control the bar between the handles or the edges of the slider.

Pass an array with a boolean for every connecting element, including the edges of the slider. The length of this array must match the handle count + 1.

@@ -192,7 +212,10 @@
0
Accepted values -
number
+
number,
+ array[number],
+ array[number, number] +
@@ -209,14 +232,13 @@ -

Step

-

By default, the slider slides fluently. In order to make the handles jump between intervals, you can use this option. The step option is relative to the values provided to range.

+

By default, the slider slides fluently. In order to make the handles jump between intervals, you can use the step option.

@@ -318,7 +340,7 @@
-

noUiSlider can provide a basic tooltip without using its events system. Set the tooltips option to true to enable. This option can also accept formatting options to format the tooltips content. In that case, pass an array with a formatter for each handle, true to use the default or false to display no tooltip.

+

noUiSlider can provide a basic tooltip using the tooltips option. This option can also accept formatting options to format the tooltips content. In that case, pass an array with a formatter for each handle, true to use the default or false to display no tooltip.

@@ -330,7 +352,7 @@
false
Accepted values -
false, true, formatter, array[formatter or false]
+
false, true, formatter, array[formatter or true or false, ...]
@@ -371,7 +393,7 @@
-

The animationDuration option can be used to set the animation speed assumed by the slider library. In addition to this, you must manually set the CSS (-webkit-)transition property for the .noUi-state-tap .noUi-origin selector.

+

The animationDuration option can be used to set the animation speed assumed by the slider library. In addition to this, you must manually alter the CSS (-webkit-)transition property for the .noUi-state-tap .noUi-origin selector.

Default @@ -387,34 +409,3 @@
- - - - -

Multitouch

- -
-
-

Set the multitouch option to true to allow simultaneous interaction with a slider and other content on the page (e.g. another slider). It is even possible to simultaneously control several handles of the same slider.

- -
-
-
-
- -
- -
- Default -
false
- - Accepted values -
true, false
-
- -
- -
- -
-
diff --git a/documentation/slider-options/multitouch.js b/documentation/slider-options/multitouch.js deleted file mode 100644 index aed840ba..00000000 --- a/documentation/slider-options/multitouch.js +++ /dev/null @@ -1,11 +0,0 @@ -[1, 2, 3].forEach(function (i) { - slider = document.getElementById('slider-multitouch-' + i); - noUiSlider.create(slider, { - start: [20, 80], - multitouch: true, - range: { - min: 0, - max: 100 - } - }); -}); diff --git a/documentation/slider-options/padding.js b/documentation/slider-options/padding.js index 978be75f..39605f0a 100644 --- a/documentation/slider-options/padding.js +++ b/documentation/slider-options/padding.js @@ -2,7 +2,7 @@ var paddingSlider = document.getElementById('slider-padding'); noUiSlider.create(paddingSlider, { start: [ 20, 80 ], - padding: 10, + padding: [ 10, 15 ], // Or just 10 range: { 'min': 0, 'max': 100 diff --git a/documentation/slider-read-write.php b/documentation/slider-read-write.php index 00d23ceb..efb5ccf8 100644 --- a/documentation/slider-read-write.php +++ b/documentation/slider-read-write.php @@ -20,7 +20,7 @@ -

For one-handle sliders, calling .get() will return the value. For two-handle sliders, an array[value, value] will be returned.

+

For one-handle sliders, calling .get() will return the value as a 'string'. For multi-handle sliders, an array['string', 'string', ...] will be returned.

@@ -38,7 +38,7 @@

Within an array, you can set one position to null if you want to leave a handle unchanged.

-

To return to the initial slider values, you can use the .reset() method. This will only reset the slider values.

+

To return to the initial slider values, you can use the .reset() method. This will only reset the slider values.

diff --git a/package.json b/package.json index d887024c..692448e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nouislider", - "version": "10.1.0", + "version": "11.0.0", "main": "distribute/nouislider", "style": "distribute/nouislider.min.css", "license": "WTFPL", @@ -30,5 +30,8 @@ "repository": { "type": "git", "url": "git://github.com/leongersen/noUiSlider.git" - } + }, + "files": [ + "distribute" + ] } diff --git a/src/js/options.js b/src/js/options.js index bd15fc45..1e05c21f 100644 --- a/src/js/options.js +++ b/src/js/options.js @@ -188,25 +188,34 @@ function testPadding ( parsed, entry ) { - if ( !isNumeric(entry) ){ - throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be numeric."); + if ( !isNumeric(entry) && !Array.isArray(entry) ){ + throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be numeric or array of exactly 2 numbers."); + } + + if ( Array.isArray(entry) && !(entry.length == 2 || isNumeric(entry[0]) || isNumeric(entry[1])) ) { + throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be numeric or array of exactly 2 numbers."); } if ( entry === 0 ) { return; } - parsed.padding = parsed.spectrum.getMargin(entry); + if ( !Array.isArray(entry) ) { + entry = [entry, entry]; + } + + // 'getMargin' returns false for invalid values. + parsed.padding = [parsed.spectrum.getMargin(entry[0]), parsed.spectrum.getMargin(entry[1])]; - if ( !parsed.padding ) { + if ( parsed.padding[0] === false || parsed.padding[1] === false ) { throw new Error("noUiSlider (" + VERSION + "): 'padding' option is only supported on linear sliders."); } - if ( parsed.padding < 0 ) { - throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be a positive number."); + if ( parsed.padding[0] < 0 || parsed.padding[1] < 0 ) { + throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be a positive number(s)."); } - if ( parsed.padding >= 50 ) { + if ( parsed.padding[0] >= 50 || parsed.padding[1] >= 50 ) { throw new Error("noUiSlider (" + VERSION + "): 'padding' option must be less than half the range."); } } @@ -262,14 +271,6 @@ }; } - function testMultitouch ( parsed, entry ) { - parsed.multitouch = entry; - - if ( typeof entry !== 'boolean' ){ - throw new Error("noUiSlider (" + VERSION + "): 'multitouch' option must be a boolean."); - } - } - function testTooltips ( parsed, entry ) { if ( entry === false ) { @@ -339,14 +340,6 @@ } } - function testUseRaf ( parsed, entry ) { - if ( entry === true || entry === false ) { - parsed.useRequestAnimationFrame = entry; - } else { - throw new Error("noUiSlider (" + VERSION + "): 'useRequestAnimationFrame' option should be true (default) or false."); - } - } - // Test all developer settings and parse to assumption-safe values. function testOptions ( options ) { @@ -379,20 +372,17 @@ 'limit': { r: false, t: testLimit }, 'padding': { r: false, t: testPadding }, 'behaviour': { r: true, t: testBehaviour }, - 'multitouch': { r: true, t: testMultitouch }, 'ariaFormat': { r: false, t: testAriaFormat }, 'format': { r: false, t: testFormat }, 'tooltips': { r: false, t: testTooltips }, 'cssPrefix': { r: false, t: testCssPrefix }, - 'cssClasses': { r: false, t: testCssClasses }, - 'useRequestAnimationFrame': { r: false, t: testUseRaf } + 'cssClasses': { r: false, t: testCssClasses } }; var defaults = { 'connect': false, 'direction': 'ltr', 'behaviour': 'tap', - 'multitouch': false, 'orientation': 'horizontal', 'cssPrefix' : 'noUi-', 'cssClasses': { @@ -406,6 +396,7 @@ vertical: 'vertical', background: 'background', connect: 'connect', + connects: 'connects', ltr: 'ltr', rtl: 'rtl', draggable: 'draggable', @@ -428,8 +419,7 @@ valueNormal: 'value-normal', valueLarge: 'value-large', valueSub: 'value-sub' - }, - 'useRequestAnimationFrame': true + } }; // AriaFormat defaults to regular format, if any. @@ -458,11 +448,20 @@ // Forward pips options parsed.pips = options.pips; + // All recent browsers accept unprefixed transform. + // We need -ms- for IE9 and -webkit- for older Android; + // Assume use of -webkit- if unprefixed and -ms- are not supported. + // https://caniuse.com/#feat=transforms2d + var d = document.createElement("div"); + var msPrefix = d.style.msTransform !== undefined; + var noPrefix = d.style.transform !== undefined; + + parsed.transformRule = noPrefix ? 'transform' : (msPrefix ? 'msTransform' : 'webkitTransform'); + + // Pips don't move, so we can place them using left/top. var styles = [['left', 'top'], ['right', 'bottom']]; - // Pre-define the styles. parsed.style = styles[parsed.dir][parsed.ort]; - parsed.styleOposite = styles[parsed.dir?0:1][parsed.ort]; return parsed; } diff --git a/src/js/pips.js b/src/js/pips.js index 68cf8cfb..b24b3065 100644 --- a/src/js/pips.js +++ b/src/js/pips.js @@ -8,22 +8,23 @@ if ( mode === 'count' ) { - if ( !values ) { - throw new Error("noUiSlider (" + VERSION + "): 'values' required for mode 'count'."); + if ( values < 2 ) { + throw new Error("noUiSlider (" + VERSION + "): 'values' (>= 2) required for mode 'count'."); } // Divide 0 - 100 in 'count' parts. - var spread = ( 100 / (values - 1) ); - var v; - var i = 0; + var interval = values - 1; + var spread = ( 100 / interval ); values = []; // List these parts and have them handled as 'positions'. - while ( (v = i++ * spread) <= 100 ) { - values.push(v); + while ( interval-- ) { + values[ interval ] = ( interval * spread ); } + values.push(100); + mode = 'positions'; } @@ -216,6 +217,7 @@ if ( values[1] ) { node = addNodeTo(element, false); node.className = getClasses(values[1], options.cssClasses.value); + node.setAttribute('data-value', values[0]); node.style[options.style] = offset + '%'; node.innerText = formatter.to(values[0]); } diff --git a/src/js/range.js b/src/js/range.js index 39922cbb..1b4dc9c8 100644 --- a/src/js/range.js +++ b/src/js/range.js @@ -118,7 +118,7 @@ } // Reject any invalid input, by testing whether value is an array. - if ( Object.prototype.toString.call( value ) !== '[object Array]' ){ + if ( !Array.isArray(value) ){ throw new Error("noUiSlider (" + VERSION + "): 'range' contains invalid value."); } diff --git a/src/js/scope.js b/src/js/scope.js index 97ee2828..ed855a20 100644 --- a/src/js/scope.js +++ b/src/js/scope.js @@ -34,11 +34,11 @@ if ( options.padding ) { if ( handleNumber === 0 ) { - to = Math.max(to, options.padding); + to = Math.max(to, options.padding[0]); } if ( handleNumber === scope_Handles.length - 1 ) { - to = Math.min(to, 100 - options.padding); + to = Math.min(to, 100 - options.padding[1]); } } @@ -55,10 +55,24 @@ return to; } + // Uses slider orientation to create CSS rules. a = base value; + function inRuleOrder ( v, a ) { + var o = options.ort; + return (o?a:v) + ', ' + (o?v:a); + } + function toPct ( pct ) { return pct + '%'; } + // Takes a base value and an offset. This offset is used for the connect bar size. + // In the initial design for this feature, the origin element was 1% wide. + // Unfortunately, a rounding bug in Chrome makes it impossible to implement this feature + // in this manner: https://bugs.chromium.org/p/chromium/issues/detail?id=798223 + function transformDirection ( a, b ) { + return options.dir ? 100 - a - b : a; + } + // Updates scope_Locations and scope_Values, updates visual state function updateHandlePosition ( handleNumber, to ) { @@ -68,22 +82,11 @@ // 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. - // Option to disable is useful for unit tests, and single-step debugging. - if ( window.requestAnimationFrame && options.useRequestAnimationFrame ) { - window.requestAnimationFrame(stateUpdate); - } else { - stateUpdate(); - } + var rule = 'translate(' + inRuleOrder(toPct(transformDirection(to, 0)), '0') + ')'; + scope_Handles[handleNumber].style[options.transformRule] = rule; + + updateConnect(handleNumber); + updateConnect(handleNumber + 1); } function setZindex ( ) { @@ -94,7 +97,7 @@ // [[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; + scope_Handles[handleNumber].style.zIndex = zIndex; }); } @@ -131,31 +134,40 @@ h = scope_Locations[index]; } - scope_Connects[index].style[options.style] = toPct(l); - scope_Connects[index].style[options.styleOposite] = toPct(100 - h); + // We use two rules: + // 'translate' to change the left/top offset; + // 'scale' to change the width of the element; + // As the element has a width of 100%, a translation of 100% is equal to 100% of the parent (.noUi-base) + var connectWidth = h - l; + var translateRule = 'translate(' + inRuleOrder(toPct(transformDirection(l, connectWidth)), '0') + ')'; + var scaleRule = 'scale(' + inRuleOrder(connectWidth / 100, '1') + ')'; + + scope_Connects[index].style[options.transformRule] = translateRule + ' ' + scaleRule; } - // ... - function setValue ( to, handleNumber ) { + // Parses value passed to .set method. Returns current value if not parseable. + function resolveToValue ( to, handleNumber ) { // Setting with null indicates an 'ignore'. // Inputting 'false' is invalid. - if ( to === null || to === false ) { - return; + if ( to === null || to === false || to === undefined ) { + return scope_Locations[handleNumber]; } - // If a formatted number was passed, attemt to decode it. + // If a formatted number was passed, attempt to decode it. if ( typeof to === 'number' ) { to = String(to); } to = options.format.from(to); + to = scope_Spectrum.toStepping(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(handleNumber, scope_Spectrum.toStepping(to), false, false); + // If parsing the number failed, use the current value. + if ( to === false || isNaN(to) ) { + return scope_Locations[handleNumber]; } + + return to; } // Set the slider value. @@ -167,17 +179,20 @@ // Event fires by default fireSetEvent = (fireSetEvent === undefined ? true : !!fireSetEvent); - values.forEach(setValue); - // Animation is optional. // Make sure the initial values were set before using animated placement. if ( options.animate && !isInit ) { addClassFor(scope_Target, options.cssClasses.tap, options.animationDuration); } - // Now that all base values are set, apply constraints + // First pass, without lookAhead but with lookBackward. Values are set from left to right. + scope_HandleNumbers.forEach(function(handleNumber){ + setHandle(handleNumber, resolveToValue(values[handleNumber], handleNumber), true, false); + }); + + // Second pass. Now that all base values are set, apply constraints scope_HandleNumbers.forEach(function(handleNumber){ - setHandle(handleNumber, scope_Locations[handleNumber], true, false); + setHandle(handleNumber, scope_Locations[handleNumber], true, true); }); setZindex(); diff --git a/src/js/scope_events.js b/src/js/scope_events.js index 2bc2bd6c..307bb248 100644 --- a/src/js/scope_events.js +++ b/src/js/scope_events.js @@ -105,6 +105,7 @@ target: event.target, handle: handle, listeners: listeners, + doNotReject: true, handleNumbers: data.handleNumbers }); @@ -112,6 +113,7 @@ target: event.target, handle: handle, listeners: listeners, + doNotReject: true, handleNumbers: data.handleNumbers }); diff --git a/src/js/scope_helpers.js b/src/js/scope_helpers.js index 4e96f0ef..1ae13f7f 100644 --- a/src/js/scope_helpers.js +++ b/src/js/scope_helpers.js @@ -13,19 +13,22 @@ var method = function ( e ){ - if ( scope_Target.hasAttribute('disabled') ) { + e = fixEvent(e, data.pageOffset, data.target || element); + + // fixEvent returns false if this event has a different target + // when handling (multi-) touch events; + if ( !e ) { return false; } - // Stop if an active 'tap' transition is taking place. - if ( hasClass(scope_Target, options.cssClasses.tap) ) { + // doNotReject is passed by all end events to make sure released touches + // are not rejected, leaving the slider "stuck" to the cursor; + if ( scope_Target.hasAttribute('disabled') && !data.doNotReject ) { return false; } - e = fixEvent(e, data.pageOffset, data.target || element); - - // Handle reject of multitouch - if ( !e ) { + // Stop if an active 'tap' transition is taking place. + if ( hasClass(scope_Target, options.cssClasses.tap) && !data.doNotReject ) { return false; } @@ -86,44 +89,40 @@ // In the event that multitouch is activated, the only thing one handle should be concerned // about is the touches that originated on top of it. - if ( touch && options.multitouch ) { + if ( touch ) { + // Returns true if a touch originated on the target. var isTouchOnTarget = function (touch) { return touch.target === target || target.contains(touch.target); }; + // In the case of touchstart events, we need to make sure there is still no more than one // touch on the target so we look amongst all touches. if (e.type === 'touchstart') { + var targetTouches = Array.prototype.filter.call(e.touches, isTouchOnTarget); + // Do not support more than one touch per handle. if ( targetTouches.length > 1 ) { return false; } + x = targetTouches[0].pageX; y = targetTouches[0].pageY; + } else { - // In the other cases, find on changedTouches is enough. + + // In the other cases, find on changedTouches is enough. var targetTouch = Array.prototype.find.call(e.changedTouches, isTouchOnTarget); + // Cancel if the target touch has not moved. if ( !targetTouch ) { return false; } + x = targetTouch.pageX; y = targetTouch.pageY; } - } else 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 ( e.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(scope_Document); diff --git a/src/js/structure.js b/src/js/structure.js index e39d57ed..1a311e10 100644 --- a/src/js/structure.js +++ b/src/js/structure.js @@ -51,10 +51,12 @@ // Add handles to the slider base. function addElements ( connectOptions, base ) { + var connectBase = addNodeTo(base, options.cssClasses.connects); + scope_Handles = []; scope_Connects = []; - scope_Connects.push(addConnect(base, connectOptions[0])); + scope_Connects.push(addConnect(connectBase, connectOptions[0])); // [::::O====O====O====] // connectOptions = [0, 1, 1, 1] @@ -63,7 +65,7 @@ // Keep a list of all added handles. scope_Handles.push(addOrigin(base, i)); scope_HandleNumbers[i] = i; - scope_Connects.push(addConnect(base, connectOptions[i + 1])); + scope_Connects.push(addConnect(connectBase, connectOptions[i + 1])); } } diff --git a/src/nouislider.core.less b/src/nouislider.core.less index 6939c29a..5196889c 100644 --- a/src/nouislider.core.less +++ b/src/nouislider.core.less @@ -20,46 +20,53 @@ position: relative; direction: ltr; } -.@{noUi-css-prefix}-base { +.@{noUi-css-prefix}-base, +.@{noUi-css-prefix}-connects { width: 100%; height: 100%; position: relative; - z-index: 1; /* Fix 401 */ + z-index: 1; } -.@{noUi-css-prefix}-connect { +/* Wrapper for all connect elements. + */ +.@{noUi-css-prefix}-connects { + overflow: hidden; + z-index: 0; +} +.@{noUi-css-prefix}-connect, +.@{noUi-css-prefix}-origin { + will-change: transform; position: absolute; - right: 0; + z-index: 1; top: 0; left: 0; - bottom: 0; + height: 100%; + width: 100%; + -webkit-transform-origin: 0 0; + transform-origin: 0 0; } -.@{noUi-css-prefix}-origin { - position: absolute; - height: 0; + +/* Give origins 0 height/width so they don't interfere with clicking the + * connect elements. + */ +.@{noUi-css-prefix}-vertical .@{noUi-css-prefix}-origin { width: 0; } +.@{noUi-css-prefix}-horizontal .@{noUi-css-prefix}-origin { + height: 0; +} .@{noUi-css-prefix}-handle { position: relative; - z-index: 1; } .@{noUi-css-prefix}-state-tap .@{noUi-css-prefix}-connect, .@{noUi-css-prefix}-state-tap .@{noUi-css-prefix}-origin { --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; +-webkit-transition: transform 0.3s; + transition: transform 0.3s; } .@{noUi-css-prefix}-state-drag * { cursor: inherit !important; } -/* Painting and performance; - * Browsers can paint handles in their own layer. - */ -.@{noUi-css-prefix}-base, -.@{noUi-css-prefix}-handle { - -webkit-transform: translate3d(0,0,0); - transform: translate3d(0,0,0); -} - /* Slider size and handle placement; */ .@{noUi-css-prefix}-horizontal { @@ -82,6 +89,7 @@ } /* Styling; + * Giving the connect element a border radius causes issues with using transform: scale */ .@{noUi-css-prefix}-target { background: #FAFAFA; @@ -89,12 +97,11 @@ border: 1px solid #D3D3D3; box-shadow: inset 0 1px 1px #F0F0F0, 0 3px 6px -5px #BBB; } +.@{noUi-css-prefix}-connects { + border-radius: 3px; +} .@{noUi-css-prefix}-connect { background: #3FB8AF; - border-radius: 4px; - box-shadow: inset 0 0 3px rgba(51,51,51,0.45); --webkit-transition: background 450ms; - transition: background 450ms; } /* Handles and cursors; diff --git a/src/nouislider.pips.less b/src/nouislider.pips.less index 5f23f24b..4f737b9d 100644 --- a/src/nouislider.pips.less +++ b/src/nouislider.pips.less @@ -50,8 +50,13 @@ width: 100%; } .@{noUi-css-prefix}-value-horizontal { - -webkit-transform: translate3d(-50%,50%,0); - transform: translate3d(-50%,50%,0); + -webkit-transform: translate(-50%, 50%); + transform: translate(-50%, 50%); + + .@{noUi-css-prefix}-rtl & { + -webkit-transform: translate(50%, 50%); + transform: translate(50%, 50%); + } } .@{noUi-css-prefix}-marker-horizontal.@{noUi-css-prefix}-marker { @@ -76,9 +81,14 @@ left: 100%; } .@{noUi-css-prefix}-value-vertical { - -webkit-transform: translate3d(0,50%,0); - transform: translate3d(0,50%,0); + -webkit-transform: translate(0, -50%); + transform: translate(0,-50%,0); padding-left: 25px; + + .@{noUi-css-prefix}-rtl & { + -webkit-transform: translate(0, 50%); + transform: translate(0, 50%); + } } .@{noUi-css-prefix}-marker-vertical.@{noUi-css-prefix}-marker { diff --git a/tests/addon_pips.js b/tests/addon_pips.js index 5a8c0f0f..ae8de751 100644 --- a/tests/addon_pips.js +++ b/tests/addon_pips.js @@ -24,7 +24,7 @@ // RANGE - QUnit.test( "Range", function( assert ){ + QUnit.test( "Pips: Range", function( assert ){ var slider = test_slider({ mode: 'range', @@ -45,7 +45,7 @@ assert.equal( Q.querySelector('.noUi-value').innerHTML, '0.00' ); }); - QUnit.test( "Steps", function( assert ){ + QUnit.test( "Pips: Steps", function( assert ){ var slider = test_slider({ mode: 'steps', @@ -60,7 +60,7 @@ }); - QUnit.test( "Positions", function( assert ){ + QUnit.test( "Pips: Positions", function( assert ){ var slider = test_slider({ mode: 'positions', @@ -81,7 +81,7 @@ }); - QUnit.test( "Positions, stepped", function( assert ){ + QUnit.test( "Pips: Positions, stepped", function( assert ){ expect(0); // TODO @@ -94,27 +94,36 @@ // POSITIONS (STEPPED) }); - QUnit.test( "Count", function( assert ){ + QUnit.test( "Pips: Count", function( assert ){ var slider = test_slider({ mode: 'count', - values: 8 + values: 12 }); // COUNT - assert.equal( Q.querySelectorAll('.noUi-value').length, 8, 'Placed requested number of values' ); + assert.equal( Q.querySelectorAll('.noUi-value').length, 12, 'Placed requested number of values' ); var pos2 = []; Array.prototype.forEach.call(Q.querySelectorAll('.noUi-value'), function( el ){ pos2.push(parseInt(el.style.left)); }); - assert.deepEqual(pos2, [0, Math.floor((100/7)*1), Math.floor((100/7)*2), Math.floor((100/7)*3), Math.floor((100/7)*4), Math.floor((100/7)*5), Math.floor((100/7)*6), 100], 'Values spread evenly'); + assert.deepEqual( pos2, [0, Math.floor( (100 / 11) * 1 ), Math.floor( (100 / 11) * 2 ), Math.floor( (100 / 11) * 3 ), Math.floor( (100 / 11) * 4 ), Math.floor( (100 / 11) * 5 ), Math.floor( (100 / 11) * 6 ), Math.floor( (100 / 11) * 7 ), Math.floor( (100 / 11) * 8 ), Math.floor( (100 / 11) * 9 ), Math.floor( (100 / 11) * 10 ), 100], 'Values spread evenly' ); + + } ); + + QUnit.test( "Pips: Count, values >= 2", function (assert) { + + assert.throws( function() { test_slider( { + mode: 'count', + values: 1 + } ) }, 'Checks minimum number of values' ); }); - QUnit.test( "Count, stepped", function( assert ){ + QUnit.test( "Pips: Count, stepped", function( assert ){ expect(0); // TODO @@ -127,7 +136,7 @@ // VALUES - QUnit.test( "Values", function( assert ){ + QUnit.test( "Pips: Values", function( assert ){ // #357 var slider = test_slider({ @@ -140,7 +149,7 @@ // VALUES (STEPPED) - QUnit.test( "Values, stepped", function( assert ){ + QUnit.test( "Pips: Values, stepped", function( assert ){ var slider = test_slider({ mode: 'values', @@ -153,7 +162,7 @@ // #528, #532 - QUnit.test( "Values, stepped", function( assert ){ + QUnit.test( "Pips: Values, stepped", function( assert ){ Q.innerHTML = '
'; var slider = Q.querySelector('.slider'); diff --git a/tests/slider.html b/tests/slider.html index 864f172d..111e9fc7 100644 --- a/tests/slider.html +++ b/tests/slider.html @@ -20,7 +20,7 @@ - + @@ -68,6 +68,7 @@ + diff --git a/tests/slider_errors.js b/tests/slider_errors.js index 2df5b3f7..9a00238d 100644 --- a/tests/slider_errors.js +++ b/tests/slider_errors.js @@ -74,17 +74,6 @@ }); }); - assert.throws(function(){ - noUiSlider.create(slider, { - start: [ 1 ], - range: { - 'min': 0, - 'max': 10 - }, - useRequestAnimationFrame: 'Hello' - }); - }, "Should error if useRequestAnimationFrame not a boolean."); - assert.throws(function(){ noUiSlider.create(slider, { start: 10, diff --git a/tests/slider_lookaround.js b/tests/slider_lookaround.js new file mode 100644 index 00000000..ec64fdf4 --- /dev/null +++ b/tests/slider_lookaround.js @@ -0,0 +1,137 @@ + + QUnit.test( "Padding and margin (lookaround)", function( assert ){ + + Q.innerHTML = '
'; + + var slider = Q.querySelector('#slider1'); + var slider2 = Q.querySelector('#slider2'); + var slider3 = Q.querySelector('#slider3'); + + noUiSlider.create(slider, { + start: [ 20, 23 ], + step: 1, + padding: 5, + margin: 3, + range: { + 'min': 10, + 'max': 30 + } + }); + + function m ( a ) { + var values = slider.noUiSlider.get(); + values[0] = parseInt(values[0]) + a; + values[1] = parseInt(values[1]) + a; + slider.noUiSlider.set(values); + } + + function up ( ) { m(1); } + function down ( ) { m(-1); } + + assert.deepEqual( slider.noUiSlider.get(), ['20.00', '23.00']); + + up(); + assert.deepEqual( slider.noUiSlider.get(), ['21.00', '24.00']); + + up(); + assert.deepEqual( slider.noUiSlider.get(), ['22.00', '25.00'], 'Highest possible values'); + + up(); + assert.deepEqual( slider.noUiSlider.get(), ['22.00', '25.00'], 'Nothing should have happened'); + + down(); + assert.deepEqual( slider.noUiSlider.get(), ['21.00', '24.00']); + + down(); + assert.deepEqual( slider.noUiSlider.get(), ['20.00', '23.00']); + + slider.noUiSlider.set([10, 12]); + assert.deepEqual( slider.noUiSlider.get(), ['15.00', '18.00'], 'lower bound by padding, upper by margin'); + + slider.noUiSlider.set([15, 17]); + assert.deepEqual( slider.noUiSlider.get(), ['15.00', '18.00'], 'lower bound ok, but apply margin'); + + + + + noUiSlider.create(slider2, { + start: [ 20, 23, 27 ], + step: 1, + padding: 3, + limit: 4, + margin: 3, + range: { + 'min': 10, + 'max': 40 + } + }); + + slider2.noUiSlider.set([20, 24, 27]); + assert.deepEqual(slider2.noUiSlider.get(), ['20.00', '24.00', '27.00']); + + slider2.noUiSlider.set([19, 24, 27]); + assert.deepEqual(slider2.noUiSlider.get(), ['19.00', '23.00', '27.00'], 'Cannot do 24, exceeds limit'); + + slider2.noUiSlider.set([18, 19, 23]); + assert.deepEqual(slider2.noUiSlider.get(), ['18.00', '21.00', '24.00'], 'Cannot do 19, exceeds margin. Same for 23 after 19 becomes 21.'); + + slider2.noUiSlider.set([18, 19, 29]); + assert.deepEqual(slider2.noUiSlider.get(), ['18.00', '21.00', '25.00'], 'Cannot do 25, exceeds limit.'); + + slider2.noUiSlider.set([12, 15, 40]); + assert.deepEqual(slider2.noUiSlider.get(), ['13.00', '16.00', '20.00'], 'Padding, margin and limit'); + + + + + noUiSlider.create(slider3, { + start: [ 20, 23, 27 ], + step: 3, + padding: 6, + limit: 9, + behaviour: 'drag', + margin: 6, + range: { + 'min': 0, + 'max': 100 + } + }); + + slider3.noUiSlider.set([20, 24, 27]); + assert.deepEqual(slider3.noUiSlider.get(), ['21.00', '27.00', '33.00'], 'Margin and step'); + + slider3.noUiSlider.set([21, 27, 36]); + assert.deepEqual(slider3.noUiSlider.get(), ['21.00', '27.00', '36.00'], 'Limit'); + + slider3.noUiSlider.__moveHandles(true, 10, [0, 1]); // Drag the first two handles up by 10 pct + assert.deepEqual(slider3.noUiSlider.get(), ['24.00', '30.00', '36.00'], 'Margin and limit for two handles'); + + slider3.noUiSlider.__moveHandles(true, 20, [0, 1]); // Try to push up further + assert.deepEqual(slider3.noUiSlider.get(), ['24.00', '30.00', '36.00'], 'Nothing should change'); + + slider3.noUiSlider.__moveHandles(true, 10, [1, 2]); // Move the last two handles + assert.deepEqual(slider3.noUiSlider.get(), ['24.00', '33.00', '39.00'], 'Can slide once'); + + slider3.noUiSlider.__moveHandles(true, 10, [1, 2]); // Move the last two handles + assert.deepEqual(slider3.noUiSlider.get(), ['24.00', '33.00', '39.00'], 'Not again, blocked by limit on the 2nd handle.'); + + slider3.noUiSlider.set([76, 85, 91]); + assert.deepEqual(slider3.noUiSlider.get(), ['75.00', '84.00', '90.00']); + + slider3.noUiSlider.set([78, 85, 91]); + assert.deepEqual(slider3.noUiSlider.get(), ['78.00', '84.00', '90.00']); + + slider3.noUiSlider.__moveHandles(true, 10, [1, 2]); // Move the last two handles + assert.deepEqual(slider3.noUiSlider.get(), ['78.00', '87.00', '93.00']); + + slider3.noUiSlider.__moveHandles(false, -10, [1, 2]); // Back down + assert.deepEqual(slider3.noUiSlider.get(), ['78.00', '84.00', '90.00']); + + slider3.noUiSlider.__moveHandles(false, -10, [0, 1]); // Back down + assert.deepEqual(slider3.noUiSlider.get(), ['75.00', '81.00', '90.00']); + + slider3.noUiSlider.__moveHandles(false, -10, [0, 1]); // Back down + assert.deepEqual(slider3.noUiSlider.get(), ['75.00', '81.00', '90.00'], 'Cannot, limited by limit between last handles'); + + + }); diff --git a/tests/slider_padding.js b/tests/slider_padding.js index 147596af..29a19871 100644 --- a/tests/slider_padding.js +++ b/tests/slider_padding.js @@ -35,3 +35,33 @@ slider.noUiSlider.set( [ 3, 10 ] ); assert.deepEqual( slider.noUiSlider.get(), ['3.00', '9.00'], 'RTL set.' ); }); + + QUnit.test( "Padding option", function( assert ){ + + Q.innerHTML = '
'; + + var settings = { + start: [ 0, 100 ], + padding: [ 10, 5 ], + range: { + 'min': 0, + 'max': 100 + } + }; + + var slider = Q.querySelector('.slider'); + + noUiSlider.create(slider, settings); + + assert.deepEqual( slider.noUiSlider.get(), ['10.00', '95.00'], 'Different paddings applied' ); + + // ============= + + slider.noUiSlider.destroy(); + + settings.padding = [0, 10]; + + noUiSlider.create(slider, settings); + assert.deepEqual( slider.noUiSlider.get(), ['0.00', '90.00'], 'One of the padding values is 0' ); + + }); diff --git a/tests/slider_setting-getting.js b/tests/slider_setting-getting.js index 5838bc76..6aafc029 100644 --- a/tests/slider_setting-getting.js +++ b/tests/slider_setting-getting.js @@ -10,7 +10,6 @@ start: [ 0, 10 ], behaviour: 'drag', connect: true, - useRequestAnimationFrame: false, format: { to: function(x){ return x.toFixed(1); diff --git a/tests/slider_three_or_more_handles.js b/tests/slider_three_or_more_handles.js index 424b9ac7..e750d505 100644 --- a/tests/slider_three_or_more_handles.js +++ b/tests/slider_three_or_more_handles.js @@ -49,8 +49,7 @@ 'max': [20] }, animate: false, - animationDuration: 0, - useRequestAnimationFrame: false + animationDuration: 0 }); var handles2 = slider2.querySelectorAll('.noUi-handle'), @@ -62,7 +61,7 @@ rightmostHandlePos = rightmostHandle.getBoundingClientRect(); assert.deepEqual(middleHandlePos, rightmostHandlePos, "Two handles in the same location should have the same on-screen position"); - assert.notDeepEqual(middleHandlePos, leftmostHandlePos, "Handles at different ends of the slider should have different positions. This might mean requestAnimationFrame is waiting for a repaint before moving the handles, or the box you're drawing into is off screen."); + assert.notDeepEqual(middleHandlePos, leftmostHandlePos, "Handles at different ends of the slider should have different positions."); var middleHandleX = (middleHandlePos.right+middleHandlePos.left)/2, middleHandleY = (middleHandlePos.top+middleHandlePos.bottom)/2, @@ -79,7 +78,7 @@ clickY = middleHandleY, click1x = leftmostHandlePos.right*0.75+middleHandlePos.left*0.25, click2x = leftmostHandlePos.right*0.25+middleHandlePos.left*0.75; - + assert.deepEqual(slider2.noUiSlider.get(), [ "10.00", "20.00", "20.00" ], "Checking initial state"); simulateMousedown(clickTarget, click1x, clickY); diff --git a/tests/slider_update.js b/tests/slider_update.js index ad0ac0de..c11c9448 100644 --- a/tests/slider_update.js +++ b/tests/slider_update.js @@ -17,7 +17,7 @@ slider.noUiSlider.destroy(); - equal(slider.innerHTML, '', 'Slider was cleared'); + assert.equal(slider.innerHTML, '', 'Slider was cleared'); var settings = { range: { min: 30, max: 70 }, @@ -35,14 +35,14 @@ slider.noUiSlider.set(40); assert.deepEqual(slider.noUiSlider.get(), ['40', '70']); - equal ( slider.querySelectorAll('.noUi-connect').length, 0, 'Slider uses no connection' ); + assert.equal ( slider.querySelectorAll('.noUi-connect').length, 0, 'Slider uses no connection' ); settings.connect = true; slider.noUiSlider.destroy(); noUiSlider.create(slider, settings); - equal ( slider.querySelectorAll('.noUi-connect').length, 1, 'Slider now connects' ); + assert.equal ( slider.querySelectorAll('.noUi-connect').length, 1, 'Slider now connects' ); assert.deepEqual(slider.noUiSlider.get(), ['30', '60'], 'Value was unchanged');