diff --git a/.gitattributes b/.gitattributes index 6bb829f6..e69de29b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +0,0 @@ -/distribute/* -diff diff --git a/.gitignore b/.gitignore index 8e428dc4..551af95c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,2 @@ /node_modules -*.log -*.zip .idea diff --git a/.npmignore b/.npmignore index 55173414..2d7ddbc1 100644 --- a/.npmignore +++ b/.npmignore @@ -1,3 +1 @@ documentation/ -*.zip -*.log diff --git a/README.md b/README.md index 84ff16b9..d7dfd97a 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ An extensive documentation, including **examples**, **options** and **configurat Changelog --------- +### 12.1.0 (*2018-10-25*) +- Added: `unconstrained` behaviour (#747, #815, #913) +- Added: `setHandle` API (#917) +- Changed: point to `nouislider.js` in `package.json`.`main` (#921) + ### 12.0.0 (*2018-09-14*) - Change: License changed to MIT; - Change: Build process is now based on NPM scripts, phasing out the Grunt task runner. diff --git a/distribute/nouislider.css b/distribute/nouislider.css index 2d71e9eb..cb9d5ff3 100644 --- a/distribute/nouislider.css +++ b/distribute/nouislider.css @@ -1,4 +1,4 @@ -/*! nouislider - 12.0.0 - 9/14/2018 */ +/*! nouislider - 12.1.0 - 10/25/2018 */ /* Functional styling; * These styles are required for noUiSlider to function. * You don't need to change these rules to apply your design. diff --git a/distribute/nouislider.js b/distribute/nouislider.js index 5d304d65..3bf6352d 100644 --- a/distribute/nouislider.js +++ b/distribute/nouislider.js @@ -1,4 +1,4 @@ -/*! nouislider - 12.0.0 - 9/14/2018 */ +/*! nouislider - 12.1.0 - 10/25/2018 */ (function(factory) { if (typeof define === "function" && define.amd) { // AMD. Register as an anonymous module. @@ -13,7 +13,7 @@ })(function() { "use strict"; - var VERSION = "12.0.0"; + var VERSION = "12.1.0"; function isValidFormatter(entry) { return typeof entry === "object" && typeof entry.to === "function" && typeof entry.from === "function"; @@ -700,6 +700,7 @@ var fixed = entry.indexOf("fixed") >= 0; var snap = entry.indexOf("snap") >= 0; var hover = entry.indexOf("hover") >= 0; + var unconstrained = entry.indexOf("unconstrained") >= 0; if (fixed) { if (parsed.handles !== 2) { @@ -710,12 +711,19 @@ testMargin(parsed, parsed.start[1] - parsed.start[0]); } + if (unconstrained && (parsed.margin || parsed.limit)) { + throw new Error( + "noUiSlider (" + VERSION + "): 'unconstrained' behaviour cannot be used with margin or limit" + ); + } + parsed.events = { tap: tap || snap, drag: drag, fixed: fixed, snap: snap, - hover: hover + hover: hover, + unconstrained: unconstrained }; } @@ -1433,8 +1441,7 @@ pointer = true; } - // 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. + // The only thing one handle should be concerned about is the touches that originated on top of it. if (touch) { // Returns true if a touch originated on the target. var isTouchOnTarget = function(checkTouch) { @@ -1827,7 +1834,7 @@ function checkHandlePosition(reference, handleNumber, to, lookBackward, lookForward, getValue) { // For sliders with multiple handles, limit movement to the other handle. // Apply the margin option by adding it to the handle positions. - if (scope_Handles.length > 1) { + if (scope_Handles.length > 1 && !options.events.unconstrained) { if (lookBackward && handleNumber > 0) { to = Math.max(to, reference[handleNumber - 1] + options.margin); } @@ -2082,6 +2089,26 @@ valueSet(options.start, fireSetEvent); } + // Set value for a single handle + function valueSetHandle(handleNumber, value, fireSetEvent) { + var values = []; + + // Ensure numeric input + handleNumber = Number(handleNumber); + + if (!(handleNumber >= 0 && handleNumber < scope_HandleNumbers.length)) { + throw new Error("noUiSlider (" + VERSION + "): invalid handle number, got: " + handleNumber); + } + + for (var i = 0; i < scope_HandleNumbers.length; i++) { + values[i] = null; + } + + values[handleNumber] = value; + + valueSet(values, fireSetEvent); + } + // Get the slider value. function valueGet() { var values = scope_Values.map(options.format.to); @@ -2224,6 +2251,7 @@ off: removeEvent, get: valueGet, set: valueSet, + setHandle: valueSetHandle, reset: valueReset, // Exposed for unit testing, don't use this in your application. __moveHandles: function(a, b, c) { diff --git a/distribute/nouislider.min.css b/distribute/nouislider.min.css index bcc9dbb8..ea1d6dd7 100644 --- a/distribute/nouislider.min.css +++ b/distribute/nouislider.min.css @@ -1,2 +1,2 @@ -/*! nouislider - 12.0.0 - 9/14/2018 */ +/*! nouislider - 12.1.0 - 10/25/2018 */ .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%;-ms-transform-origin:0 0;-webkit-transform-origin:0 0;transform-origin:0 0}html:not([dir=rtl]) .noUi-horizontal .noUi-origin{left:auto;right:0}.noUi-vertical .noUi-origin{width:0}.noUi-horizontal .noUi-origin{height:0}.noUi-handle{position:absolute}.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}html:not([dir=rtl]) .noUi-horizontal .noUi-handle{right:-17px;left:auto}.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-sub{background:#AAA}.noUi-marker-large{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 ac49d4f8..6e3307c2 100644 --- a/distribute/nouislider.min.js +++ b/distribute/nouislider.min.js @@ -1,2 +1,2 @@ -/*! nouislider - 12.0.0 - 9/14/2018 */ -!function(t){"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():window.noUiSlider=t()}(function(){"use strict";var et="12.0.0";function s(t){return null!=t}function rt(t){t.preventDefault()}function i(t){return"number"==typeof t&&!isNaN(t)&&isFinite(t)}function nt(t,e,r){0=e[r];)r+=1;return r}function r(t,e,r){if(r>=t.slice(-1)[0])return 100;var n,i,o=f(r,t),a=t[o-1],s=t[o],l=e[o-1],u=e[o];return l+(i=r,p(n=[a,s],n[0]<0?i+Math.abs(n[0]):i-n[0])/c(l,u))}function n(t,e,r,n){if(100===n)return n;var i,o,a=f(n,t),s=t[a-1],l=t[a];return r?(l-s)/2= 2) required for mode 'count'.");var n=e-1,i=100/n;for(e=[];n--;)e[n]=n*i;e.push(100),t="positions"}return"positions"===t?e.map(function(t){return E.fromStepping(r?E.getStep(t):t)}):"values"===t?r?e.map(function(t){return E.fromStepping(E.getStep(E.toStepping(t)))}):e:void 0}(n,t.values||!1,t.stepped||!1),s=(m=i,g=n,v=a,b={},e=E.xVal[0],r=E.xVal[E.xVal.length-1],w=S=!1,x=0,(v=v.slice().sort(function(t,e){return t-e}).filter(function(t){return!this[t]&&(this[t]=!0)},{}))[0]!==e&&(v.unshift(e),S=!0),v[v.length-1]!==r&&(v.push(r),w=!0),v.forEach(function(t,e){var r,n,i,o,a,s,l,u,c,p,f=t,d=v[e+1],h="steps"===g;if(h&&(r=E.xNumSteps[e]),r||(r=d-f),!1!==f&&void 0!==d)for(r=Math.max(r,1e-7),n=f;n<=d;n=(n+r).toFixed(7)/1){for(u=(a=(o=E.toStepping(n))-x)/m,p=a/(c=Math.round(u)),i=1;i<=c;i+=1)b[(s=x+i*p).toFixed(5)]=[E.fromStepping(s),0];l=-1r.stepAfter.startValue&&(i=r.stepAfter.startValue-n),o=n>r.thisStep.startValue?r.thisStep.step:!1!==r.stepBefore.step&&n-r.stepBefore.highestStep,100===t?i=null:0===t&&(o=null);var a=E.countStepDecimals();return null!==i&&!1!==i&&(i=Number(i.toFixed(a))),null!==o&&!1!==o&&(o=Number(o.toFixed(a))),[o,i]})},on:X,off:function(t){var n=t&&t.split(".")[0],i=n&&t.substring(n.length);Object.keys(S).forEach(function(t){var e=t.split(".")[0],r=t.substring(e.length);n&&n!==e||i&&i!==r||delete S[t]})},get:tt,set:Z,reset:function(t){Z(f.start,t)},__moveHandles:function(t,e,r){$(t,e,m,r)},options:o,updateOptions:function(e,t){var r=tt(),n=["margin","limit","padding","range","animate","snap","step","format"];n.forEach(function(t){void 0!==e[t]&&(o[t]=e[t])});var i=ut(o);n.forEach(function(t){void 0!==e[t]&&(f[t]=i[t])}),E=i.spectrum,f.margin=i.margin,f.limit=i.limit,f.padding=i.padding,f.pips&&z(f.pips),m=[],Z(e.start||r,t)},target:y,removePips:L,pips:z},f.pips&&z(f.pips),f.tooltips&&(i=u.map(r),X("update",function(t,e,r){if(i[e]){var n=t[e];!0!==f.tooltips[e]&&(n=f.tooltips[e].to(r[e])),i[e].innerHTML=n}})),X("update",function(t,e,a,r,s){g.forEach(function(t){var e=u[t],r=I(m,t,0,!0,!0,!0),n=I(m,t,100,!0,!0,!0),i=s[t],o=f.ariaFormat.to(a[t]);r=E.fromStepping(r).toFixed(1),n=E.fromStepping(n).toFixed(1),i=E.fromStepping(i).toFixed(1),e.children[0].setAttribute("aria-valuemin",r),e.children[0].setAttribute("aria-valuemax",n),e.children[0].setAttribute("aria-valuenow",i),e.children[0].setAttribute("aria-valuetext",o)})}),a}return{__spectrum:l,version:et,create:function(t,e){if(!t||!t.nodeName)throw new Error("noUiSlider ("+et+"): create requires a single element, got: "+t);if(t.noUiSlider)throw new Error("noUiSlider ("+et+"): Slider was already initialized.");var r=z(t,ut(e),e);return t.noUiSlider=r}}}); \ No newline at end of file +/*! nouislider - 12.1.0 - 10/25/2018 */ +!function(t){"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():window.noUiSlider=t()}(function(){"use strict";var et="12.1.0";function s(t){return null!=t}function rt(t){t.preventDefault()}function i(t){return"number"==typeof t&&!isNaN(t)&&isFinite(t)}function nt(t,e,r){0=e[r];)r+=1;return r}function r(t,e,r){if(r>=t.slice(-1)[0])return 100;var n,i,o=f(r,t),a=t[o-1],s=t[o],l=e[o-1],u=e[o];return l+(i=r,p(n=[a,s],n[0]<0?i+Math.abs(n[0]):i-n[0])/c(l,u))}function n(t,e,r,n){if(100===n)return n;var i,o,a=f(n,t),s=t[a-1],l=t[a];return r?(l-s)/2= 2) required for mode 'count'.");var n=e-1,i=100/n;for(e=[];n--;)e[n]=n*i;e.push(100),t="positions"}return"positions"===t?e.map(function(t){return E.fromStepping(r?E.getStep(t):t)}):"values"===t?r?e.map(function(t){return E.fromStepping(E.getStep(E.toStepping(t)))}):e:void 0}(n,t.values||!1,t.stepped||!1),s=(m=i,g=n,v=a,b={},e=E.xVal[0],r=E.xVal[E.xVal.length-1],w=S=!1,x=0,(v=v.slice().sort(function(t,e){return t-e}).filter(function(t){return!this[t]&&(this[t]=!0)},{}))[0]!==e&&(v.unshift(e),S=!0),v[v.length-1]!==r&&(v.push(r),w=!0),v.forEach(function(t,e){var r,n,i,o,a,s,l,u,c,p,f=t,d=v[e+1],h="steps"===g;if(h&&(r=E.xNumSteps[e]),r||(r=d-f),!1!==f&&void 0!==d)for(r=Math.max(r,1e-7),n=f;n<=d;n=(n+r).toFixed(7)/1){for(u=(a=(o=E.toStepping(n))-x)/m,p=a/(c=Math.round(u)),i=1;i<=c;i+=1)b[(s=x+i*p).toFixed(5)]=[E.fromStepping(s),0];l=-1r.stepAfter.startValue&&(i=r.stepAfter.startValue-n),o=n>r.thisStep.startValue?r.thisStep.step:!1!==r.stepBefore.step&&n-r.stepBefore.highestStep,100===t?i=null:0===t&&(o=null);var a=E.countStepDecimals();return null!==i&&!1!==i&&(i=Number(i.toFixed(a))),null!==o&&!1!==o&&(o=Number(o.toFixed(a))),[o,i]})},on:X,off:function(t){var n=t&&t.split(".")[0],i=n&&t.substring(n.length);Object.keys(S).forEach(function(t){var e=t.split(".")[0],r=t.substring(e.length);n&&n!==e||i&&i!==r||delete S[t]})},get:tt,set:Z,setHandle:function(t,e,r){var n=[];if(!(0<=(t=Number(t))&&tnoUiSlider offers several ways to handle user interaction. The range can be set to drag, and handles can move to taps. All these effects are optional, and can be enable by adding their keyword to the behaviour option.

-

This option accepts a "-" separated list of "drag", "tap", "fixed", "snap" or "none".

+

This option accepts a "-" separated list of "drag", "tap", "fixed", "snap", "unconstrained" or "none".

@@ -60,6 +60,12 @@

Fire hover events when a user with a mouse or pen hovers over the slider.

+
+ +
behaviour: "unconstrained-tap"
+

Allow handles to move past each other.

+
+
behaviour: "none"
@@ -165,6 +171,27 @@ + +

Unconstrained

+ +
+ +
+

With this option set, handles are allowed to move past each other. The limit and margin options cannot be used with this behaviour.

+

All APIs will return slider values in the original handle order, regardless of whether handles have changed places.

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

Combined options

diff --git a/documentation/behaviour-option/unconstrained.js b/documentation/behaviour-option/unconstrained.js new file mode 100644 index 00000000..1ea50ee6 --- /dev/null +++ b/documentation/behaviour-option/unconstrained.js @@ -0,0 +1,16 @@ +var unconstrainedSlider = document.getElementById('unconstrained'); +var unconstrainedValues = document.getElementById('unconstrained-values'); + +noUiSlider.create(unconstrainedSlider, { + start: [20, 50, 80], + behaviour: 'unconstrained-tap', + connect: true, + range: { + 'min': 0, + 'max': 100 + } +}); + +unconstrainedSlider.noUiSlider.on('update', function (values) { + unconstrainedValues.innerHTML = values.join(' :: '); +}); diff --git a/documentation/examples/keypress-event.js b/documentation/examples/keypress-event.js index 67f8ec0f..5f4f1a2b 100644 --- a/documentation/examples/keypress-event.js +++ b/documentation/examples/keypress-event.js @@ -1,14 +1,8 @@ -function setSliderHandle(i, value) { - var r = [null, null]; - r[i] = value; - keypressSlider.noUiSlider.set(r); -} - // Listen to keydown events on the input field. inputs.forEach(function (input, handle) { input.addEventListener('change', function () { - setSliderHandle(handle, this.value); + keypressSlider.noUiSlider.setHandle(handle, this.value); }); input.addEventListener('keydown', function (e) { @@ -30,7 +24,7 @@ inputs.forEach(function (input, handle) { switch (e.which) { case 13: - setSliderHandle(handle, this.value); + keypressSlider.noUiSlider.setHandle(handle, this.value); break; case 38: @@ -45,7 +39,7 @@ inputs.forEach(function (input, handle) { // null = edge of slider if (position !== null) { - setSliderHandle(handle, value + position); + keypressSlider.noUiSlider.setHandle(handle, value + position); } break; @@ -59,7 +53,7 @@ inputs.forEach(function (input, handle) { } if (position !== null) { - setSliderHandle(handle, value - position); + keypressSlider.noUiSlider.setHandle(handle, value - position); } break; diff --git a/documentation/examples/keypress-slider.js b/documentation/examples/keypress-slider.js index cc9a0601..4d7be9ee 100644 --- a/documentation/examples/keypress-slider.js +++ b/documentation/examples/keypress-slider.js @@ -6,7 +6,6 @@ var inputs = [input0, input1]; noUiSlider.create(keypressSlider, { start: [20, 80], connect: true, - direction: 'rtl', tooltips: [true, wNumb({decimals: 1})], range: { 'min': [0], diff --git a/documentation/reference.php b/documentation/reference.php index 07ddf1c7..e5387831 100644 --- a/documentation/reference.php +++ b/documentation/reference.php @@ -194,7 +194,12 @@ set slider.noUiSlider.set(...) - [...] + [...], boolean + + + setHandle + slider.noUiSlider.setHandle(..., ..., ...) + "number", "string", boolean reset diff --git a/documentation/slider-read-write.php b/documentation/slider-read-write.php index 205f36d9..78cae219 100644 --- a/documentation/slider-read-write.php +++ b/documentation/slider-read-write.php @@ -32,11 +32,17 @@
-

noUiSlider will keep your values within the slider range, which saves you a bunch of validation.

+

If you have configured the slider to use one handle, you can change the current value by passing a number to the .set() method.

-

If you have configured the slider to use one handle, you can change the current value by passing a number to the .set() method. If you have two handles, pass an array. One-handled sliders will also accept arrays.

+

For sliders with multiple handles, pass an array. One-handled sliders will also accept arrays.

-

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

+

Within an array, you can set any position to null to you leave a handle unchanged.

+ +

noUiSlider will always limit values to the slider range.

+ +

To set a single slider handle, the setHandle method can be used. This method accepts a zero-indexed handle number, a value and optionally a 'fire set event' boolean.

+ +

Passing null as the value to setHandle will leave the handle unchanged.

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

diff --git a/documentation/slider-read-write/write.js b/documentation/slider-read-write/write.js index fd0bb6d6..26953186 100644 --- a/documentation/slider-read-write/write.js +++ b/documentation/slider-read-write/write.js @@ -6,11 +6,16 @@ noUiSlider.create(slider, /* { options } */); slider.noUiSlider.set(10); slider.noUiSlider.set([150]); -// Set the upper handle, -// don't change the lower one. +// Set the upper handle on a slider with two handles, +// don't change the lower one slider.noUiSlider.set([null, 14]); -// Set both slider handles +// On a slider with three handles, +// set the third to 12 (the handleNumber is 0-indexed) +// Then: fire the set event +slider.noUiSlider.setHandle(2, 12, true); + +// Set both slider handles on a slider with two handles slider.noUiSlider.set([13.2, 15.7]); // Return to the 'start' values diff --git a/package.json b/package.json index e425c39f..15929940 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nouislider", - "version": "12.0.0", - "main": "distribute/nouislider", + "version": "12.1.0", + "main": "distribute/nouislider.js", "style": "distribute/nouislider.min.css", "license": "MIT", "scripts": { diff --git a/src/nouislider.js b/src/nouislider.js index 9e0a8e34..5c983d2c 100644 --- a/src/nouislider.js +++ b/src/nouislider.js @@ -699,6 +699,7 @@ var fixed = entry.indexOf("fixed") >= 0; var snap = entry.indexOf("snap") >= 0; var hover = entry.indexOf("hover") >= 0; + var unconstrained = entry.indexOf("unconstrained") >= 0; if (fixed) { if (parsed.handles !== 2) { @@ -709,12 +710,19 @@ testMargin(parsed, parsed.start[1] - parsed.start[0]); } + if (unconstrained && (parsed.margin || parsed.limit)) { + throw new Error( + "noUiSlider (" + VERSION + "): 'unconstrained' behaviour cannot be used with margin or limit" + ); + } + parsed.events = { tap: tap || snap, drag: drag, fixed: fixed, snap: snap, - hover: hover + hover: hover, + unconstrained: unconstrained }; } @@ -1432,8 +1440,7 @@ pointer = true; } - // 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. + // The only thing one handle should be concerned about is the touches that originated on top of it. if (touch) { // Returns true if a touch originated on the target. var isTouchOnTarget = function(checkTouch) { @@ -1826,7 +1833,7 @@ function checkHandlePosition(reference, handleNumber, to, lookBackward, lookForward, getValue) { // For sliders with multiple handles, limit movement to the other handle. // Apply the margin option by adding it to the handle positions. - if (scope_Handles.length > 1) { + if (scope_Handles.length > 1 && !options.events.unconstrained) { if (lookBackward && handleNumber > 0) { to = Math.max(to, reference[handleNumber - 1] + options.margin); } @@ -2081,6 +2088,26 @@ valueSet(options.start, fireSetEvent); } + // Set value for a single handle + function valueSetHandle(handleNumber, value, fireSetEvent) { + var values = []; + + // Ensure numeric input + handleNumber = Number(handleNumber); + + if (!(handleNumber >= 0 && handleNumber < scope_HandleNumbers.length)) { + throw new Error("noUiSlider (" + VERSION + "): invalid handle number, got: " + handleNumber); + } + + for (var i = 0; i < scope_HandleNumbers.length; i++) { + values[i] = null; + } + + values[handleNumber] = value; + + valueSet(values, fireSetEvent); + } + // Get the slider value. function valueGet() { var values = scope_Values.map(options.format.to); @@ -2223,6 +2250,7 @@ off: removeEvent, get: valueGet, set: valueSet, + setHandle: valueSetHandle, reset: valueReset, // Exposed for unit testing, don't use this in your application. __moveHandles: function(a, b, c) { diff --git a/tests/slider_errors.js b/tests/slider_errors.js index 4afa0103..08b927b4 100644 --- a/tests/slider_errors.js +++ b/tests/slider_errors.js @@ -105,6 +105,18 @@ QUnit.test("Errors", function (assert) { }); }, "Padding exceeds 100%."); + assert.throws(function () { + noUiSlider.create(slider, { + start: 10, + margin: 5, + behaviour: "unconstrained", + range: { + 'min': 0, + 'max': 10 + } + }); + }, "Unconstrained can't work with margin."); + assert.throws(function () { noUiSlider.create(slider, { start: 0, @@ -118,7 +130,7 @@ QUnit.test("Errors", function (assert) { }, "Limit must be divisible by step."); noUiSlider.create(slider, { - start: 1, + start: [1, 2, 3], margin: 0, // Does not throw, issue #582 cssPrefix: null, // #856 step: null, @@ -128,6 +140,14 @@ QUnit.test("Errors", function (assert) { } }); + assert.throws(function () { + slider.noUiSlider.setHandle(-1, 5); + }, "Out of bounds handle number"); + + assert.throws(function () { + slider.noUiSlider.setHandle(4, 5); + }, "Out of bounds handle number"); + assert.throws(function () { noUiSlider.create(slider, { start: 10, diff --git a/tests/slider_setting-getting.js b/tests/slider_setting-getting.js index 27332efc..6eef4a0c 100644 --- a/tests/slider_setting-getting.js +++ b/tests/slider_setting-getting.js @@ -49,4 +49,16 @@ QUnit.test("Value setting/getting", function (assert) { slider.noUiSlider.set(false); assert.deepEqual(slider.noUiSlider.get(), ["30.0", "30.0"]); + slider.noUiSlider.setHandle(0, 20); + assert.deepEqual(slider.noUiSlider.get(), ["20.0", "30.0"]); + + slider.noUiSlider.setHandle(1, 40); + assert.deepEqual(slider.noUiSlider.get(), ["20.0", "40.0"]); + + slider.noUiSlider.setHandle(1, 15); + assert.deepEqual(slider.noUiSlider.get(), ["20.0", "20.0"]); + + slider.noUiSlider.setHandle(1, null); + assert.deepEqual(slider.noUiSlider.get(), ["20.0", "20.0"]); + });