diff --git a/Gruntfile.js b/Gruntfile.js index b05ef950..50bc7641 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -26,8 +26,7 @@ module.exports = function(grunt) { } var releaseFiles = [ - { src: ['**/*'], dest: '', cwd: 'distribute/', expand: true }, - { src: ['**/*.css'], dest: '', cwd: 'src/', expand: true } + { src: ['**/*'], dest: '', cwd: 'distribute/', expand: true } ]; grunt.initConfig({ diff --git a/README.md b/README.md index cbec0ada..0bfe655e 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,17 @@ An extensive documentation, including **examples**, **options** and **configurat Changelog --------- + +### 9.1.0 (*2016-12-10*) +- Fixed: Slider not properly handling multitouch (#700, #704); +- Fixed: Removed a querySelector for the currently active handle (#720); +- Fixed: Removed iOS/webkit flashes on tap; +- Fixed: Incorrect error when using margin/limit with a step smaller than 0 (#736); +- Fixed: Drag option using incorrect cursor arrows (#681); +- Added: New `padding` option (#711); +- Added: Re-introduced `.noUi-handle-lower` and `.noUi-handle-upper` classes removed in 9.0.0; +- Added: Compatibility for legacy `connect` options removed in 9.0.0; + ### 9.0.0 (*2016-09-26*) - Added: Support for **more than 2 handles**; - Added: `format` option can be updated (#641); diff --git a/documentation/download.php b/documentation/download.php index c4b67164..7a1eecc4 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/slider-options.php b/documentation/slider-options.php index 746c62d1..686a85b8 100644 --- a/documentation/slider-options.php +++ b/documentation/slider-options.php @@ -170,6 +170,45 @@ + +

Padding

+ +
+ +
+ +

Padding limits how close to the slider edges handles can be.

+ +
+
+ + + + +
+ +
+ Default +
0
+ + Accepted values +
number
+
+
+ +
+ + +
Show the slider value
+ +
+ +
+
+ +
+ +

Step

diff --git a/documentation/slider-options/padding-link.js b/documentation/slider-options/padding-link.js new file mode 100644 index 00000000..82148697 --- /dev/null +++ b/documentation/slider-options/padding-link.js @@ -0,0 +1,10 @@ +var paddingMin = document.getElementById('slider-padding-value-min'), + paddingMax = document.getElementById('slider-padding-value-max'); + +paddingSlider.noUiSlider.on('update', function ( values, handle ) { + if ( handle ) { + paddingMax.innerHTML = values[handle]; + } else { + paddingMin.innerHTML = values[handle]; + } +}); diff --git a/documentation/slider-options/padding.js b/documentation/slider-options/padding.js new file mode 100644 index 00000000..978be75f --- /dev/null +++ b/documentation/slider-options/padding.js @@ -0,0 +1,10 @@ +var paddingSlider = document.getElementById('slider-padding'); + +noUiSlider.create(paddingSlider, { + start: [ 20, 80 ], + padding: 10, + range: { + 'min': 0, + 'max': 100 + } +}); diff --git a/package.json b/package.json index a2b5d871..f8ba21e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nouislider", - "version": "9.0.0", + "version": "9.1.0", "main": "distribute/nouislider", "style": "distribute/nouislider.min.css", "license": "WTFPL", diff --git a/src/js/options.js b/src/js/options.js index 9480ec15..3caf0734 100644 --- a/src/js/options.js +++ b/src/js/options.js @@ -98,6 +98,16 @@ var connect = [false]; var i; + // Map legacy options + if ( entry === 'lower' ) { + entry = [true, false]; + } + + else if ( entry === 'upper' ) { + entry = [false, true]; + } + + // Handle boolean options if ( entry === true || entry === false ) { for ( i = 1; i < parsed.handles; i++ ) { @@ -107,6 +117,7 @@ connect.push(false); } + // Reject invalid input else if ( !Array.isArray( entry ) || !entry.length || entry.length !== parsed.handles + 1 ) { throw new Error("noUiSlider: 'connect' option doesn't match handle count."); } @@ -165,6 +176,31 @@ } } + function testPadding ( parsed, entry ) { + + if ( !isNumeric(entry) ){ + throw new Error("noUiSlider: 'padding' option must be numeric."); + } + + if ( entry === 0 ) { + return; + } + + parsed.padding = parsed.spectrum.getMargin(entry); + + if ( !parsed.padding ) { + throw new Error("noUiSlider: 'padding' option is only supported on linear sliders."); + } + + if ( parsed.padding < 0 ) { + throw new Error("noUiSlider: 'padding' option must be a positive number."); + } + + if ( parsed.padding >= 50 ) { + throw new Error("noUiSlider: 'padding' option must be less than half the range."); + } + } + function testDirection ( parsed, entry ) { // Set direction as a numerical value for easy parsing. @@ -305,13 +341,14 @@ var parsed = { margin: 0, limit: 0, + padding: 0, animate: true, animationDuration: 300, format: defaultFormatter - }, tests; + }; // Tests are executed in the order they are presented here. - tests = { + var tests = { 'step': { r: false, t: testStep }, 'start': { r: true, t: testStart }, 'connect': { r: true, t: testConnect }, @@ -323,6 +360,7 @@ 'orientation': { r: false, t: testOrientation }, 'margin': { r: false, t: testMargin }, 'limit': { r: false, t: testLimit }, + 'padding': { r: false, t: testPadding }, 'behaviour': { r: true, t: testBehaviour }, 'format': { r: false, t: testFormat }, 'tooltips': { r: false, t: testTooltips }, @@ -342,6 +380,8 @@ base: 'base', origin: 'origin', handle: 'handle', + handleLower: 'handle-lower', + handleUpper: 'handle-upper', horizontal: 'horizontal', vertical: 'vertical', background: 'background', diff --git a/src/js/range.js b/src/js/range.js index 0eea18f2..178a1ede 100644 --- a/src/js/range.js +++ b/src/js/range.js @@ -229,8 +229,8 @@ var step = this.xNumSteps[0]; - if ( step && (value % step) ) { - throw new Error("noUiSlider: 'limit' and 'margin' must be divisible by step."); + if ( step && ((value / step) % 1) !== 0 ) { + throw new Error("noUiSlider: 'limit', 'margin' and 'padding' must be divisible by step."); } return this.xPct.length === 2 ? fromPercentage(this.xVal, value) : false; diff --git a/src/js/scope.js b/src/js/scope.js index 69ac2b42..72141e1d 100644 --- a/src/js/scope.js +++ b/src/js/scope.js @@ -29,6 +29,19 @@ } } + // The padding option keeps the handles a certain distance from the + // edges of the slider. Padding must be > 0. + if ( options.padding ) { + + if ( handleNumber === 0 ) { + to = Math.max(to, options.padding); + } + + if ( handleNumber === scope_Handles.length - 1 ) { + to = Math.min(to, 100 - options.padding); + } + } + to = scope_Spectrum.getStep(to); // Limit percentage to the 0 - 100 range @@ -180,6 +193,7 @@ }); } + // Reset slider to initial values function valueReset ( fireSetEvent ) { valueSet(options.start, fireSetEvent); } @@ -232,6 +246,7 @@ } } + // If the value is beyond the starting point if ( value > nearbySteps.thisStep.startValue ) { decrement = nearbySteps.thisStep.step; @@ -246,6 +261,7 @@ decrement = value - nearbySteps.stepBefore.highestStep; } + // Now, if at the slider edges, there is not in/decrement if ( location === 100 ) { increment = null; @@ -287,8 +303,8 @@ // Undo attachment of event function removeEvent ( namespacedEvent ) { - var event = namespacedEvent && namespacedEvent.split('.')[0], - namespace = event && namespacedEvent.substring(event.length); + var event = namespacedEvent && namespacedEvent.split('.')[0]; + var namespace = event && namespacedEvent.substring(event.length); Object.keys(scope_Events).forEach(function( bind ){ @@ -301,7 +317,7 @@ }); } - // Updateable: margin, limit, step, range, animate, snap + // Updateable: margin, limit, padding, step, range, animate, snap function updateOptions ( optionsToUpdate, fireSetEvent ) { // Spectrum is created using the range, snap, direction and step options. @@ -309,7 +325,7 @@ // If 'snap' and 'step' are not passed, they should remain unchanged. var v = valueGet(); - var updateAble = ['margin', 'limit', 'range', 'animate', 'snap', 'step', 'format']; + var updateAble = ['margin', 'limit', 'padding', 'range', 'animate', 'snap', 'step', 'format']; // Only change options that we're actually passed to update. updateAble.forEach(function(name){ @@ -332,9 +348,10 @@ newOptions.spectrum.direction = scope_Spectrum.direction; scope_Spectrum = newOptions.spectrum; - // Limit and margin depend on the spectrum but are stored outside of it. (#677) + // Limit, margin and padding depend on the spectrum but are stored outside of it. (#677) options.margin = newOptions.margin; options.limit = newOptions.limit; + options.padding = newOptions.padding; // Invalidate the current positioning so valueSet forces an update. scope_Locations = []; @@ -359,7 +376,7 @@ get: valueGet, set: valueSet, reset: valueReset, - // Exposed for unit testing, don't use this in your application. + // Exposed for unit testing, don't use this in your application. __moveHandles: function(a, b, c) { moveHandles(a, b, scope_Locations, c); }, options: originalOptions, // Issue #600, #678 updateOptions: updateOptions, diff --git a/src/js/scope_events.js b/src/js/scope_events.js index aac71d23..6d4a182b 100644 --- a/src/js/scope_events.js +++ b/src/js/scope_events.js @@ -31,10 +31,9 @@ function eventEnd ( event, data ) { // The handle is no longer active, so remove the class. - var active = scope_Base.querySelector( '.' + options.cssClasses.active ); - - if ( active !== null ) { - removeClass(active, options.cssClasses.active); + if ( scope_ActiveHandle ) { + removeClass(scope_ActiveHandle, options.cssClasses.active); + scope_ActiveHandle = false; } // Remove cursor styles and text-selection events bound to the body. @@ -63,7 +62,6 @@ // Bind move events on document. function eventStart ( event, data ) { - // Mark the handle as 'active' so it can be styled. if ( data.handleNumbers.length === 1 ) { var handle = scope_Handles[data.handleNumbers[0]]; @@ -73,7 +71,9 @@ return false; } - addClass(handle.children[0], options.cssClasses.active); + // Mark the handle as 'active' so it can be styled. + scope_ActiveHandle = handle.children[0]; + addClass(scope_ActiveHandle, options.cssClasses.active); } // Fix #551, where a handle gets selected instead of dragged. diff --git a/src/js/scope_helpers.js b/src/js/scope_helpers.js index eca75793..81165fe1 100644 --- a/src/js/scope_helpers.js +++ b/src/js/scope_helpers.js @@ -24,6 +24,11 @@ e = fixEvent(e, data.pageOffset); + // Handle reject of multitouch + if ( !e ) { + return false; + } + // Ignore right or middle clicks on start #454 if ( events === actions.start && e.buttons !== undefined && e.buttons > 1 ) { return false; @@ -61,10 +66,11 @@ // Filter the event to register the type, which can be // touch, mouse or pointer. Offset changes need to be // made on an event specific basis. - var touch = e.type.indexOf('touch') === 0, - mouse = e.type.indexOf('mouse') === 0, - pointer = e.type.indexOf('pointer') === 0, - x,y, event = e; + var touch = e.type.indexOf('touch') === 0; + var mouse = e.type.indexOf('mouse') === 0; + var pointer = e.type.indexOf('pointer') === 0; + var x; + var y; // IE10 implemented pointer events with a prefix; if ( e.type.indexOf('MSPointer') === 0 ) { @@ -77,7 +83,7 @@ // It's useful when you have two or more sliders on one page, // that can be touched simultaneously. // #649, #663, #668 - if ( event.touches.length > 1 ) { + if ( e.touches.length > 1 ) { return false; } @@ -94,19 +100,21 @@ y = e.clientY + pageOffset.y; } - event.pageOffset = pageOffset; - event.points = [x, y]; - event.cursor = mouse || pointer; // Fix #435 + e.pageOffset = pageOffset; + e.points = [x, y]; + e.cursor = mouse || pointer; // Fix #435 - return event; + return e; } + // Translate a coordinate in the document to a percentage on the slider function calcPointToPercentage ( calcPoint ) { var location = calcPoint - offset(scope_Base, options.ort); var proposal = ( location * 100 ) / baseSize(); return options.dir ? 100 - proposal : proposal; } + // Find handle closest to a certain percentage on the slider function getClosestHandle ( proposal ) { var closest = 100; diff --git a/src/js/scope_start.js b/src/js/scope_start.js index c042a3cc..0b9dcf12 100644 --- a/src/js/scope_start.js +++ b/src/js/scope_start.js @@ -9,6 +9,7 @@ function closure ( target, options, originalOptions ){ var scope_Base; var scope_Handles; var scope_HandleNumbers = []; + var scope_ActiveHandle = false; var scope_Connects; var scope_Spectrum = options.spectrum; var scope_Values = []; diff --git a/src/js/structure.js b/src/js/structure.js index 4c187cda..d7456c38 100644 --- a/src/js/structure.js +++ b/src/js/structure.js @@ -1,9 +1,20 @@ // Append a origin to the base function addOrigin ( base, handleNumber ) { + var origin = addNodeTo(base, options.cssClasses.origin); var handle = addNodeTo(origin, options.cssClasses.handle); + handle.setAttribute('data-handle', handleNumber); + + if ( handleNumber === 0 ) { + addClass(handle, options.cssClasses.handleLower); + } + + else if ( handleNumber === options.handles - 1 ) { + addClass(handle, options.cssClasses.handleUpper); + } + return origin; } diff --git a/src/nouislider.css b/src/nouislider.css index 4f4dd021..d9930da1 100644 --- a/src/nouislider.css +++ b/src/nouislider.css @@ -6,6 +6,7 @@ .noUi-target, .noUi-target * { -webkit-touch-callout: none; +-webkit-tap-highlight-color: rgba(0,0,0,0); -webkit-user-select: none; -ms-touch-action: none; touch-action: none; @@ -98,10 +99,10 @@ /* Handles and cursors; */ .noUi-draggable { - cursor: w-resize; + cursor: ew-resize; } .noUi-vertical .noUi-draggable { - cursor: n-resize; + cursor: ns-resize; } .noUi-handle { border: 1px solid #D9D9D9; diff --git a/tests/slider.html b/tests/slider.html index 944bcdad..ddf8faeb 100644 --- a/tests/slider.html +++ b/tests/slider.html @@ -53,10 +53,12 @@ + + diff --git a/tests/slider_padding.js b/tests/slider_padding.js new file mode 100644 index 00000000..147596af --- /dev/null +++ b/tests/slider_padding.js @@ -0,0 +1,37 @@ + + QUnit.test( "Padding option", function( assert ){ + + Q.innerHTML = '
'; + + var settings = { + start: [ 0, 10 ], + padding: 1, + range: { + 'min': 0, + 'max': 10 + } + }; + + var slider = Q.querySelector('.slider'); + + noUiSlider.create(slider, settings); + + assert.deepEqual( slider.noUiSlider.get(), ['1.00', '9.00'], 'Padding applied on init.' ); + + slider.noUiSlider.set( [ 2, 10 ] ); + assert.deepEqual( slider.noUiSlider.get(), ['2.00', '9.00'], 'Handle can\'t pass padding.' ); + + slider.noUiSlider.set( [ 0, 10 ] ); + assert.deepEqual( slider.noUiSlider.get(), ['1.00', '9.00'], 'Multiple set outside of padding' ); + + // Rebuild with new settings; + settings.direction = 'rtl'; + slider.noUiSlider.destroy(); + + noUiSlider.create(slider, settings); + + assert.deepEqual( slider.noUiSlider.get(), ['1.00', '9.00'], 'RTL on init.' ); + + slider.noUiSlider.set( [ 3, 10 ] ); + assert.deepEqual( slider.noUiSlider.get(), ['3.00', '9.00'], 'RTL set.' ); + }); diff --git a/tests/slider_step_margin.js b/tests/slider_step_margin.js new file mode 100644 index 00000000..bd053e83 --- /dev/null +++ b/tests/slider_step_margin.js @@ -0,0 +1,22 @@ + + QUnit.test( "Margin divisible by step", function( assert ){ + + expect(0); + + Q.innerHTML = '
'; + + var settings = { + start: 10, + margin: 5, + step: 0.2, + range: { + 'min': 1, + 'max': 100 + } + }; + + var slider = Q.querySelector('.slider'); + + // Should not throw on divisibility + noUiSlider.create(slider, settings); + });