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 @@
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
+
+
+
+
+
+
+
+
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);
+ });