diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 2c862e8..de62755 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,5 +1,9 @@ # Changelog +### 15.8.0 (*2024-06-10*) +- Added: Allow `connect` option to get updated (#1272); +- Added: `invert-connects` behaviour (#1262, #1272); + ### 15.7.2 (*2024-05-14*) - Added: `getPositions` to Typescript definitions (#1270); - Added: Allow `null` in `set` Typescript definitions (#1271); diff --git a/RELEASE.md b/RELEASE.md index a0bcd9f..a44973e 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -3,4 +3,5 @@ - Run `npm audit fix` - Update `changelog.md` - Run `npm publish -otp=<2FA VALUE FROM AUTHENTICATOR>` +- Commit changes to changelog and build files - Create a new release on https://github.com/leongersen/noUiSlider/releases diff --git a/dist/nouislider.d.ts b/dist/nouislider.d.ts index c2e6eff..9cb9e12 100644 --- a/dist/nouislider.d.ts +++ b/dist/nouislider.d.ts @@ -108,10 +108,10 @@ interface UpdatableOptions { format?: Formatter; tooltips?: boolean | PartialFormatter | (boolean | PartialFormatter)[]; animate?: boolean; + connect?: "lower" | "upper" | boolean | boolean[]; } export interface Options extends UpdatableOptions { range: Range; - connect?: "lower" | "upper" | boolean | boolean[]; orientation?: "vertical" | "horizontal"; direction?: "ltr" | "rtl"; behaviour?: string; diff --git a/dist/nouislider.js b/dist/nouislider.js index 1dff19b..2d3e054 100644 --- a/dist/nouislider.js +++ b/dist/nouislider.js @@ -713,6 +713,7 @@ var snap = entry.indexOf("snap") >= 0; var hover = entry.indexOf("hover") >= 0; var unconstrained = entry.indexOf("unconstrained") >= 0; + var invertConnects = entry.indexOf("invert-connects") >= 0; var dragAll = entry.indexOf("drag-all") >= 0; var smoothSteps = entry.indexOf("smooth-steps") >= 0; if (fixed) { @@ -722,6 +723,9 @@ // Use margin to enforce fixed state testMargin(parsed, parsed.start[1] - parsed.start[0]); } + if (invertConnects && parsed.handles !== 2) { + throw new Error("noUiSlider: 'invert-connects' behaviour must be used with 2 handles"); + } if (unconstrained && (parsed.margin || parsed.limit)) { throw new Error("noUiSlider: 'unconstrained' behaviour cannot be used with margin or limit"); } @@ -734,6 +738,7 @@ snap: snap, hover: hover, unconstrained: unconstrained, + invertConnects: invertConnects, }; } function testTooltips(parsed, entry) { @@ -904,6 +909,7 @@ // Slider DOM Nodes var scope_Target = target; var scope_Base; + var scope_ConnectBase; var scope_Handles; var scope_Connects; var scope_Pips; @@ -915,6 +921,7 @@ var scope_HandleNumbers = []; var scope_ActiveHandlesCount = 0; var scope_Events = {}; + var scope_ConnectsInverted = false; // Document Nodes var scope_Document = target.ownerDocument; var scope_DocumentElement = options.documentElement || scope_Document.documentElement; @@ -971,17 +978,17 @@ } // Add handles to the slider base. function addElements(connectOptions, base) { - var connectBase = addNodeTo(base, options.cssClasses.connects); + scope_ConnectBase = addNodeTo(base, options.cssClasses.connects); scope_Handles = []; scope_Connects = []; - scope_Connects.push(addConnect(connectBase, connectOptions[0])); + scope_Connects.push(addConnect(scope_ConnectBase, connectOptions[0])); // [::::O====O====O====] // connectOptions = [0, 1, 1, 1] for (var i = 0; i < options.handles; i++) { // Keep a list of all added handles. scope_Handles.push(addOrigin(base, i)); scope_HandleNumbers[i] = i; - scope_Connects.push(addConnect(connectBase, connectOptions[i + 1])); + scope_Connects.push(addConnect(scope_ConnectBase, connectOptions[i + 1])); } } // Initialize a single slider. @@ -1930,8 +1937,26 @@ var translation = transformDirection(to, 0) - scope_DirOffset; var translateRule = "translate(" + inRuleOrder(translation + "%", "0") + ")"; scope_Handles[handleNumber].style[options.transformRule] = translateRule; + // sanity check for at least 2 handles (e.g. during setup) + if (options.events.invertConnects && scope_Locations.length > 1) { + // check if handles passed each other, but don't match the ConnectsInverted state + var handlesAreInOrder = scope_Locations.every(function (position, index, locations) { + return index === 0 || position >= locations[index - 1]; + }); + if (scope_ConnectsInverted !== !handlesAreInOrder) { + // invert connects when handles pass each other + invertConnects(); + // invertConnects already updates all connect elements + return; + } + } updateConnect(handleNumber); updateConnect(handleNumber + 1); + if (scope_ConnectsInverted) { + // When connects are inverted, we also have to update adjacent connects + updateConnect(handleNumber - 1); + updateConnect(handleNumber + 2); + } } // Handles before the slider middle are stacked later = higher, // Handles after the middle later is lower @@ -1961,13 +1986,20 @@ if (!scope_Connects[index]) { return; } + // Create a copy of locations, so we can sort them for the local scope logic + var locations = scope_Locations.slice(); + if (scope_ConnectsInverted) { + locations.sort(function (a, b) { + return a - b; + }); + } var l = 0; var h = 100; if (index !== 0) { - l = scope_Locations[index - 1]; + l = locations[index - 1]; } if (index !== scope_Connects.length - 1) { - h = scope_Locations[index]; + h = locations[index]; } // We use two rules: // 'translate' to change the left/top offset; @@ -2159,6 +2191,7 @@ "format", "pips", "tooltips", + "connect", ]; // Only change options that we're actually passed to update. updateAble.forEach(function (name) { @@ -2196,6 +2229,32 @@ // Invalidate the current positioning so valueSet forces an update. scope_Locations = []; valueSet(isSet(optionsToUpdate.start) ? optionsToUpdate.start : v, fireSetEvent); + // Update connects only if it was set + if (optionsToUpdate.connect) { + updateConnectOption(); + } + } + function updateConnectOption() { + // IE supported way of removing children including event handlers + while (scope_ConnectBase.firstChild) { + scope_ConnectBase.removeChild(scope_ConnectBase.firstChild); + } + // Adding new connects according to the new connect options + for (var i = 0; i <= options.handles; i++) { + scope_Connects[i] = addConnect(scope_ConnectBase, options.connect[i]); + updateConnect(i); + } + // re-adding drag events for the new connect elements + // to ignore the other events we have to negate the 'if (!behaviour.fixed)' check + bindSliderEvents({ drag: options.events.drag, fixed: true }); + } + // Invert options for connect handles + function invertConnects() { + scope_ConnectsInverted = !scope_ConnectsInverted; + testConnect(options, + // inverse the connect boolean array + options.connect.map(function (b) { return !b; })); + updateConnectOption(); } // Initialization steps function setupSlider() { diff --git a/dist/nouislider.min.js b/dist/nouislider.min.js index d52d86a..978a4a3 100644 --- a/dist/nouislider.min.js +++ b/dist/nouislider.min.js @@ -1 +1 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).noUiSlider={})}(this,function(ot){"use strict";function n(t){return"object"==typeof t&&"function"==typeof t.to}function st(t){t.parentElement.removeChild(t)}function at(t){return null!=t}function lt(t){t.preventDefault()}function i(t){return"number"==typeof t&&!isNaN(t)&&isFinite(t)}function ut(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=l(r,t),i=t[n-1],o=t[n],t=e[n-1],n=e[n];return t+(r=r,a(o=[i,o],o[0]<0?r+Math.abs(o[0]):r-o[0],0)/s(t,n))}function o(t,e,r,n){if(100===n)return n;var i=l(n,t),o=t[i-1],s=t[i];return r?(s-o)/2this.xPct[n+1];)n++;else t===this.xPct[this.xPct.length-1]&&(n=this.xPct.length-2);r||t!==this.xPct[n+1]||n++;for(var i,o=1,s=(e=null===e?[]:e)[n],a=0,l=0,u=0,c=r?(t-this.xPct[n])/(this.xPct[n+1]-this.xPct[n]):(this.xPct[n+1]-t)/(this.xPct[n+1]-this.xPct[n]);0= 2) required for mode 'count'.");for(var e=t.values-1,r=100/e,n=[];e--;)n[e]=e*r;return n.push(100),U(n,t.stepped)}(d),m={},t=S.xVal[0],e=S.xVal[S.xVal.length-1],g=!1,v=!1,b=0;return(h=h.slice().sort(function(t,e){return t-e}).filter(function(t){return!this[t]&&(this[t]=!0)},{}))[0]!==t&&(h.unshift(t),g=!0),h[h.length-1]!==e&&(h.push(e),v=!0),h.forEach(function(t,e){var r,n,i,o,s,a,l,u,t=t,c=h[e+1],p=d.mode===ot.PipsMode.Steps,f=(f=p?S.xNumSteps[e]:f)||c-t;for(void 0===c&&(c=t),f=Math.max(f,1e-7),r=t;r<=c;r=Number((r+f).toFixed(7))){for(a=(o=(i=S.toStepping(r))-b)/(d.density||1),u=o/(l=Math.round(a)),n=1;n<=l;n+=1)m[(s=b+n*u).toFixed(5)]=[S.fromStepping(s),0];a=-1ot.PipsType.NoValue&&((t=P(a,!1)).className=p(n,f.cssClasses.value),t.setAttribute("data-value",String(r)),t.style[f.style]=e+"%",t.innerHTML=String(s.to(r))))}),a}function L(){n&&(st(n),n=null)}function T(t){L();var e=D(t),r=t.filter,t=t.format||{to:function(t){return String(Math.round(t))}};return n=d.appendChild(O(e,r,t))}function j(){var t=i.getBoundingClientRect(),e="offset"+["Width","Height"][f.ort];return 0===f.ort?t.width||i[e]:t.height||i[e]}function z(n,i,o,s){function e(t){var e,r=function(e,t,r){var n=0===e.type.indexOf("touch"),i=0===e.type.indexOf("mouse"),o=0===e.type.indexOf("pointer"),s=0,a=0;0===e.type.indexOf("MSPointer")&&(o=!0);if("mousedown"===e.type&&!e.buttons&&!e.touches)return!1;if(n){var l=function(t){t=t.target;return t===r||r.contains(t)||e.composed&&e.composedPath().shift()===r};if("touchstart"===e.type){n=Array.prototype.filter.call(e.touches,l);if(1r.stepAfter.startValue&&(i=r.stepAfter.startValue-n),t=n>r.thisStep.startValue?r.thisStep.step:!1!==r.stepBefore.step&&n-r.stepBefore.highestStep,100===e?i=null:0===e&&(t=null);e=S.countStepDecimals();return null!==i&&!1!==i&&(i=Number(i.toFixed(e))),[t=null!==t&&!1!==t?Number(t.toFixed(e)):t,i]}ft(t=d,f.cssClasses.target),0===f.dir?ft(t,f.cssClasses.ltr):ft(t,f.cssClasses.rtl),0===f.ort?ft(t,f.cssClasses.horizontal):ft(t,f.cssClasses.vertical),ft(t,"rtl"===getComputedStyle(t).direction?f.cssClasses.textDirectionRtl:f.cssClasses.textDirectionLtr),i=P(t,f.cssClasses.base),function(t,e){var r=P(e,f.cssClasses.connects);l=[],(a=[]).push(N(r,t[0]));for(var n=0;n=e[r];)r+=1;return r}function r(t,e,r){if(r>=t.slice(-1)[0])return 100;var n=l(r,t),i=t[n-1],o=t[n],t=e[n-1],n=e[n];return t+(r=r,a(o=[i,o],o[0]<0?r+Math.abs(o[0]):r-o[0],0)/s(t,n))}function o(t,e,r,n){if(100===n)return n;var i=l(n,t),o=t[i-1],s=t[i];return r?(s-o)/2this.xPct[n+1];)n++;else t===this.xPct[this.xPct.length-1]&&(n=this.xPct.length-2);r||t!==this.xPct[n+1]||n++;for(var i,o=1,s=(e=null===e?[]:e)[n],a=0,l=0,u=0,c=r?(t-this.xPct[n])/(this.xPct[n+1]-this.xPct[n]):(this.xPct[n+1]-t)/(this.xPct[n+1]-this.xPct[n]);0= 2) required for mode 'count'.");for(var e=t.values-1,r=100/e,n=[];e--;)n[e]=e*r;return n.push(100),U(n,t.stepped)}(d),m={},t=S.xVal[0],e=S.xVal[S.xVal.length-1],g=!1,v=!1,b=0;return(h=h.slice().sort(function(t,e){return t-e}).filter(function(t){return!this[t]&&(this[t]=!0)},{}))[0]!==t&&(h.unshift(t),g=!0),h[h.length-1]!==e&&(h.push(e),v=!0),h.forEach(function(t,e){var r,n,i,o,s,a,l,u,t=t,c=h[e+1],p=d.mode===ut.PipsMode.Steps,f=(f=p?S.xNumSteps[e]:f)||c-t;for(void 0===c&&(c=t),f=Math.max(f,1e-7),r=t;r<=c;r=Number((r+f).toFixed(7))){for(a=(o=(i=S.toStepping(r))-b)/(d.density||1),u=o/(l=Math.round(a)),n=1;n<=l;n+=1)m[(s=b+n*u).toFixed(5)]=[S.fromStepping(s),0];a=-1ut.PipsType.NoValue&&((t=P(a,!1)).className=p(n,f.cssClasses.value),t.setAttribute("data-value",String(r)),t.style[f.style]=e+"%",t.innerHTML=String(s.to(r))))}),a}function L(){s&&(ct(s),s=null)}function T(t){L();var e=D(t),r=t.filter,t=t.format||{to:function(t){return String(Math.round(t))}};return s=d.appendChild(O(e,r,t))}function j(){var t=i.getBoundingClientRect(),e="offset"+["Width","Height"][f.ort];return 0===f.ort?t.width||i[e]:t.height||i[e]}function z(n,i,o,s){function e(t){var e,r=function(e,t,r){var n=0===e.type.indexOf("touch"),i=0===e.type.indexOf("mouse"),o=0===e.type.indexOf("pointer"),s=0,a=0;0===e.type.indexOf("MSPointer")&&(o=!0);if("mousedown"===e.type&&!e.buttons&&!e.touches)return!1;if(n){var l=function(t){t=t.target;return t===r||r.contains(t)||e.composed&&e.composedPath().shift()===r};if("touchstart"===e.type){n=Array.prototype.filter.call(e.touches,l);if(1=r[e-1]});if(x!==!e)return x=!x,xt(f,f.connect.map(function(t){return!t})),void at()}rt(t),rt(t+1),x&&(rt(t-1),rt(t+2))}function tt(){g.forEach(function(t){var e=50r.stepAfter.startValue&&(i=r.stepAfter.startValue-n),t=n>r.thisStep.startValue?r.thisStep.step:!1!==r.stepBefore.step&&n-r.stepBefore.highestStep,100===e?i=null:0===e&&(t=null);e=S.countStepDecimals();return null!==i&&!1!==i&&(i=Number(i.toFixed(e))),[t=null!==t&&!1!==t?Number(t.toFixed(e)):t,i]}function at(){for(;n.firstChild;)n.removeChild(n.firstChild);for(var t=0;t<=f.handles;t++)u[t]=N(n,f.connect[t]),rt(t);Y({drag:f.events.drag,fixed:!0})}gt(t=d,f.cssClasses.target),0===f.dir?gt(t,f.cssClasses.ltr):gt(t,f.cssClasses.rtl),0===f.ort?gt(t,f.cssClasses.horizontal):gt(t,f.cssClasses.vertical),gt(t,"rtl"===getComputedStyle(t).direction?f.cssClasses.textDirectionRtl:f.cssClasses.textDirectionLtr),i=P(t,f.cssClasses.base),function(t,e){n=P(e,f.cssClasses.connects),l=[],(u=[]).push(N(n,t[0]));for(var r=0;r=e[r];)r+=1;return r}function toStepping(t,e,r){if(r>=t.slice(-1)[0])return 100;var n=getJ(r,t),i=t[n-1],s=t[n],t=e[n-1],n=e[n];return t+toPercentage([i,s],r)/subRangeRatio(t,n)}function fromStepping(t,e,r){if(100<=r)return t.slice(-1)[0];var n=getJ(r,e),i=t[n-1],s=t[n],t=e[n-1];return isPercentage([i,s],(r-t)*subRangeRatio(t,e[n]))}function getStep(t,e,r,n){if(100===n)return n;var i=getJ(n,t),s=t[i-1],o=t[i];return r?(o-s)/2this.xPct[n+1];)n++;else t===this.xPct[this.xPct.length-1]&&(n=this.xPct.length-2);r||t!==this.xPct[n+1]||n++;for(var i,s=1,o=(e=null===e?[]:e)[n],a=0,l=0,u=0,c=r?(t-this.xPct[n])/(this.xPct[n+1]-this.xPct[n]):(this.xPct[n+1]-t)/(this.xPct[n+1]-this.xPct[n]);0= 2) required for mode 'count'.");for(var e=t.values-1,r=100/e,n=[];e--;)n[e]=e*r;return n.push(100),M(n,t.stepped)}(f),m={},t=S.xVal[0],e=S.xVal[S.xVal.length-1],g=!1,v=!1,b=0;return(h=unique(h.slice().sort(function(t,e){return t-e})))[0]!==t&&(h.unshift(t),g=!0),h[h.length-1]!==e&&(h.push(e),v=!0),h.forEach(function(t,e){var r,n,i,s,o,a,l,u,t=t,c=h[e+1],p=f.mode===PipsMode.Steps,d=(d=p?S.xNumSteps[e]:d)||c-t;for(void 0===c&&(c=t),d=Math.max(d,1e-7),r=t;r<=c;r=Number((r+d).toFixed(7))){for(a=(s=(i=S.toStepping(r))-b)/(f.density||1),u=s/(l=Math.round(a)),n=1;n<=l;n+=1)m[(o=b+n*u).toFixed(5)]=[S.fromStepping(o),0];a=-1PipsType.NoValue&&((t=N(a,!1)).className=p(n,d.cssClasses.value),t.setAttribute("data-value",String(r)),t.style[d.style]=e+"%",t.innerHTML=String(o.to(r))))}),a}function U(){n&&(removeElement(n),n=null)}function L(t){U();var e=D(t),r=t.filter,t=t.format||{to:function(t){return String(Math.round(t))}};return n=f.appendChild(T(e,r,t))}function O(){var t=r.getBoundingClientRect(),e="offset"+["Width","Height"][d.ort];return 0===d.ort?t.width||r[e]:t.height||r[e]}function F(e,r,n,i){function s(t){return!!(t=function(e,t,r){var n=0===e.type.indexOf("touch"),i=0===e.type.indexOf("mouse"),s=0===e.type.indexOf("pointer"),o=0,a=0;0===e.type.indexOf("MSPointer")&&(s=!0);if("mousedown"===e.type&&!e.buttons&&!e.touches)return!1;if(n){var l=function(t){t=t.target;return t===r||r.contains(t)||e.composed&&e.composedPath().shift()===r};if("touchstart"===e.type){n=Array.prototype.filter.call(e.touches,l);if(1r.stepAfter.startValue&&(i=r.stepAfter.startValue-n),t=n>r.thisStep.startValue?r.thisStep.step:!1!==r.stepBefore.step&&n-r.stepBefore.highestStep,100===e?i=null:0===e&&(t=null);e=S.countStepDecimals();return null!==i&&!1!==i&&(i=Number(i.toFixed(e))),[t=null!==t&&!1!==t?Number(t.toFixed(e)):t,i]}addClass(t=f,d.cssClasses.target),0===d.dir?addClass(t,d.cssClasses.ltr):addClass(t,d.cssClasses.rtl),0===d.ort?addClass(t,d.cssClasses.horizontal):addClass(t,d.cssClasses.vertical),addClass(t,"rtl"===getComputedStyle(t).direction?d.cssClasses.textDirectionRtl:d.cssClasses.textDirectionLtr),r=N(t,d.cssClasses.base),function(t,e){var r=N(e,d.cssClasses.connects);l=[],(a=[]).push(w(r,t[0]));for(var n=0;n=e[r];)r+=1;return r}function toStepping(t,e,r){if(r>=t.slice(-1)[0])return 100;var n=getJ(r,t),i=t[n-1],s=t[n],t=e[n-1],n=e[n];return t+toPercentage([i,s],r)/subRangeRatio(t,n)}function fromStepping(t,e,r){if(100<=r)return t.slice(-1)[0];var n=getJ(r,e),i=t[n-1],s=t[n],t=e[n-1];return isPercentage([i,s],(r-t)*subRangeRatio(t,e[n]))}function getStep(t,e,r,n){if(100===n)return n;var i=getJ(n,t),s=t[i-1],o=t[i];return r?(o-s)/2this.xPct[n+1];)n++;else t===this.xPct[this.xPct.length-1]&&(n=this.xPct.length-2);r||t!==this.xPct[n+1]||n++;for(var i,s=1,o=(e=null===e?[]:e)[n],a=0,l=0,u=0,c=r?(t-this.xPct[n])/(this.xPct[n+1]-this.xPct[n]):(this.xPct[n+1]-t)/(this.xPct[n+1]-this.xPct[n]);0= 2) required for mode 'count'.");for(var e=t.values-1,r=100/e,n=[];e--;)n[e]=e*r;return n.push(100),D(n,t.stepped)}(f),m={},t=S.xVal[0],e=S.xVal[S.xVal.length-1],g=!1,v=!1,b=0;return(h=unique(h.slice().sort(function(t,e){return t-e})))[0]!==t&&(h.unshift(t),g=!0),h[h.length-1]!==e&&(h.push(e),v=!0),h.forEach(function(t,e){var r,n,i,s,o,a,l,u,t=t,c=h[e+1],p=f.mode===PipsMode.Steps,d=(d=p?S.xNumSteps[e]:d)||c-t;for(void 0===c&&(c=t),d=Math.max(d,1e-7),r=t;r<=c;r=Number((r+d).toFixed(7))){for(a=(s=(i=S.toStepping(r))-b)/(f.density||1),u=s/(l=Math.round(a)),n=1;n<=l;n+=1)m[(o=b+n*u).toFixed(5)]=[S.fromStepping(o),0];a=-1PipsType.NoValue&&((t=C(a,!1)).className=p(n,d.cssClasses.value),t.setAttribute("data-value",String(r)),t.style[d.style]=e+"%",t.innerHTML=String(o.to(r))))}),a}function L(){i&&(removeElement(i),i=null)}function O(t){L();var e=T(t),r=t.filter,t=t.format||{to:function(t){return String(Math.round(t))}};return i=p.appendChild(U(e,r,t))}function F(){var t=r.getBoundingClientRect(),e="offset"+["Width","Height"][d.ort];return 0===d.ort?t.width||r[e]:t.height||r[e]}function R(e,r,n,i){function s(t){return!!(t=function(e,t,r){var n=0===e.type.indexOf("touch"),i=0===e.type.indexOf("mouse"),s=0===e.type.indexOf("pointer"),o=0,a=0;0===e.type.indexOf("MSPointer")&&(s=!0);if("mousedown"===e.type&&!e.buttons&&!e.touches)return!1;if(n){var l=function(t){t=t.target;return t===r||r.contains(t)||e.composed&&e.composedPath().shift()===r};if("touchstart"===e.type){n=Array.prototype.filter.call(e.touches,l);if(1=r[e-1]});if(b!==!e)return b=!b,testConnect(d,d.connect.map(function(t){return!t})),void at()}rt(t),rt(t+1),b&&(rt(t-1),rt(t+2))}function tt(){m.forEach(function(t){var e=50r.stepAfter.startValue&&(i=r.stepAfter.startValue-n),t=n>r.thisStep.startValue?r.thisStep.step:!1!==r.stepBefore.step&&n-r.stepBefore.highestStep,100===e?i=null:0===e&&(t=null);e=S.countStepDecimals();return null!==i&&!1!==i&&(i=Number(i.toFixed(e))),[t=null!==t&&!1!==t?Number(t.toFixed(e)):t,i]}function at(){for(;n.firstChild;)n.removeChild(n.firstChild);for(var t=0;t<=d.handles;t++)u[t]=P(n,d.connect[t]),rt(t);q({drag:d.events.drag,fixed:!0})}addClass(t=p,d.cssClasses.target),0===d.dir?addClass(t,d.cssClasses.ltr):addClass(t,d.cssClasses.rtl),0===d.ort?addClass(t,d.cssClasses.horizontal):addClass(t,d.cssClasses.vertical),addClass(t,"rtl"===getComputedStyle(t).direction?d.cssClasses.textDirectionRtl:d.cssClasses.textDirectionLtr),r=C(t,d.cssClasses.base),function(t,e){n=C(e,d.cssClasses.connects),l=[],(u=[]).push(P(n,t[0]));for(var r=0;r= 0; var hover = entry.indexOf("hover") >= 0; var unconstrained = entry.indexOf("unconstrained") >= 0; + var invertConnects = entry.indexOf("invert-connects") >= 0; var dragAll = entry.indexOf("drag-all") >= 0; var smoothSteps = entry.indexOf("smooth-steps") >= 0; if (fixed) { @@ -717,6 +718,9 @@ function testBehaviour(parsed, entry) { // Use margin to enforce fixed state testMargin(parsed, parsed.start[1] - parsed.start[0]); } + if (invertConnects && parsed.handles !== 2) { + throw new Error("noUiSlider: 'invert-connects' behaviour must be used with 2 handles"); + } if (unconstrained && (parsed.margin || parsed.limit)) { throw new Error("noUiSlider: 'unconstrained' behaviour cannot be used with margin or limit"); } @@ -729,6 +733,7 @@ function testBehaviour(parsed, entry) { snap: snap, hover: hover, unconstrained: unconstrained, + invertConnects: invertConnects, }; } function testTooltips(parsed, entry) { @@ -899,6 +904,7 @@ function scope(target, options, originalOptions) { // Slider DOM Nodes var scope_Target = target; var scope_Base; + var scope_ConnectBase; var scope_Handles; var scope_Connects; var scope_Pips; @@ -910,6 +916,7 @@ function scope(target, options, originalOptions) { var scope_HandleNumbers = []; var scope_ActiveHandlesCount = 0; var scope_Events = {}; + var scope_ConnectsInverted = false; // Document Nodes var scope_Document = target.ownerDocument; var scope_DocumentElement = options.documentElement || scope_Document.documentElement; @@ -966,17 +973,17 @@ function scope(target, options, originalOptions) { } // Add handles to the slider base. function addElements(connectOptions, base) { - var connectBase = addNodeTo(base, options.cssClasses.connects); + scope_ConnectBase = addNodeTo(base, options.cssClasses.connects); scope_Handles = []; scope_Connects = []; - scope_Connects.push(addConnect(connectBase, connectOptions[0])); + scope_Connects.push(addConnect(scope_ConnectBase, connectOptions[0])); // [::::O====O====O====] // connectOptions = [0, 1, 1, 1] for (var i = 0; i < options.handles; i++) { // Keep a list of all added handles. scope_Handles.push(addOrigin(base, i)); scope_HandleNumbers[i] = i; - scope_Connects.push(addConnect(connectBase, connectOptions[i + 1])); + scope_Connects.push(addConnect(scope_ConnectBase, connectOptions[i + 1])); } } // Initialize a single slider. @@ -1925,8 +1932,26 @@ function scope(target, options, originalOptions) { var translation = transformDirection(to, 0) - scope_DirOffset; var translateRule = "translate(" + inRuleOrder(translation + "%", "0") + ")"; scope_Handles[handleNumber].style[options.transformRule] = translateRule; + // sanity check for at least 2 handles (e.g. during setup) + if (options.events.invertConnects && scope_Locations.length > 1) { + // check if handles passed each other, but don't match the ConnectsInverted state + var handlesAreInOrder = scope_Locations.every(function (position, index, locations) { + return index === 0 || position >= locations[index - 1]; + }); + if (scope_ConnectsInverted !== !handlesAreInOrder) { + // invert connects when handles pass each other + invertConnects(); + // invertConnects already updates all connect elements + return; + } + } updateConnect(handleNumber); updateConnect(handleNumber + 1); + if (scope_ConnectsInverted) { + // When connects are inverted, we also have to update adjacent connects + updateConnect(handleNumber - 1); + updateConnect(handleNumber + 2); + } } // Handles before the slider middle are stacked later = higher, // Handles after the middle later is lower @@ -1956,13 +1981,20 @@ function scope(target, options, originalOptions) { if (!scope_Connects[index]) { return; } + // Create a copy of locations, so we can sort them for the local scope logic + var locations = scope_Locations.slice(); + if (scope_ConnectsInverted) { + locations.sort(function (a, b) { + return a - b; + }); + } var l = 0; var h = 100; if (index !== 0) { - l = scope_Locations[index - 1]; + l = locations[index - 1]; } if (index !== scope_Connects.length - 1) { - h = scope_Locations[index]; + h = locations[index]; } // We use two rules: // 'translate' to change the left/top offset; @@ -2154,6 +2186,7 @@ function scope(target, options, originalOptions) { "format", "pips", "tooltips", + "connect", ]; // Only change options that we're actually passed to update. updateAble.forEach(function (name) { @@ -2191,6 +2224,32 @@ function scope(target, options, originalOptions) { // Invalidate the current positioning so valueSet forces an update. scope_Locations = []; valueSet(isSet(optionsToUpdate.start) ? optionsToUpdate.start : v, fireSetEvent); + // Update connects only if it was set + if (optionsToUpdate.connect) { + updateConnectOption(); + } + } + function updateConnectOption() { + // IE supported way of removing children including event handlers + while (scope_ConnectBase.firstChild) { + scope_ConnectBase.removeChild(scope_ConnectBase.firstChild); + } + // Adding new connects according to the new connect options + for (var i = 0; i <= options.handles; i++) { + scope_Connects[i] = addConnect(scope_ConnectBase, options.connect[i]); + updateConnect(i); + } + // re-adding drag events for the new connect elements + // to ignore the other events we have to negate the 'if (!behaviour.fixed)' check + bindSliderEvents({ drag: options.events.drag, fixed: true }); + } + // Invert options for connect handles + function invertConnects() { + scope_ConnectsInverted = !scope_ConnectsInverted; + testConnect(options, + // inverse the connect boolean array + options.connect.map(function (b) { return !b; })); + updateConnectOption(); } // Initialization steps function setupSlider() { diff --git a/package-lock.json b/package-lock.json index 7712f42..48bee54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "nouislider", - "version": "15.7.2", + "version": "15.8.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "nouislider", - "version": "15.7.2", + "version": "15.8.0", "license": "MIT", "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.10", diff --git a/package.json b/package.json index d864294..ae9d817 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nouislider", - "version": "15.7.2", + "version": "15.8.0", "main": "dist/nouislider.js", "module": "dist/nouislider.mjs", "style": "dist/nouislider.min.css",