From c7facf847f78883e069d50016d2c0971cc166696 Mon Sep 17 00:00:00 2001
From: Sylvain Henry <sylvain@haskus.fr>
Date: Tue, 14 Aug 2018 12:54:21 +0200
Subject: [PATCH] Slider: add support for crossing multiple handles

With this patch, a slider which has both "range = true" and
"allowCrossingHandles = true" automatically determines the handlers
starting and stopping the range. Handlers can freely cross each other.
---
 tests/unit/slider/common.js  |  1 +
 tests/unit/slider/options.js | 20 +++++++++++++++
 ui/widgets/slider.js         | 48 +++++++++++++++++++++++++-----------
 3 files changed, 55 insertions(+), 14 deletions(-)

diff --git a/tests/unit/slider/common.js b/tests/unit/slider/common.js
index df53cda4ff3..d9801c8c83b 100644
--- a/tests/unit/slider/common.js
+++ b/tests/unit/slider/common.js
@@ -22,6 +22,7 @@ common.testWidget( "slider", {
 		step: 1,
 		value: 0,
 		values: null,
+		allowCrossingHandles: false,
 
 		// Callbacks
 		create: null,
diff --git a/tests/unit/slider/options.js b/tests/unit/slider/options.js
index d1eb2b374d6..a189067a295 100644
--- a/tests/unit/slider/options.js
+++ b/tests/unit/slider/options.js
@@ -401,4 +401,24 @@ QUnit.test( "range", function( assert ) {
 	element.slider( "destroy" );
 } );
 
+QUnit.test( "allowCrossingHandles", function( assert ) {
+	assert.expect( 1 );
+
+	element = $( "<div></div>" ).slider( {
+		range: true,
+		min: 0,
+		max: 100,
+		values: [ 25, 75 ],
+		allowCrossingHandles: true
+	} );
+
+	assert.deepEqual( element.slider( "values" ), [ 25, 75 ], "values" );
+
+	// var handles = element.find( ".ui-slider-handle" );
+	// handles.eq( 0 ).simulate( "drag", { dx: 1000 } );
+	// assert.deepEqual( element.slider( "values" ), [ 100, 75 ], "values" );
+
+	element.slider( "destroy" );
+} );
+
 } );
diff --git a/ui/widgets/slider.js b/ui/widgets/slider.js
index 8b0f907f577..571e8047163 100644
--- a/ui/widgets/slider.js
+++ b/ui/widgets/slider.js
@@ -53,6 +53,7 @@ return $.widget( "ui.slider", $.ui.mouse, {
 		min: 0,
 		orientation: "horizontal",
 		range: false,
+		allowCrossingHandles: false,
 		step: 1,
 		value: 0,
 		values: null,
@@ -330,11 +331,14 @@ return $.widget( "ui.slider", $.ui.mouse, {
 			newValues = this.values();
 
 		if ( this._hasMultipleValues() ) {
-			otherVal = this.values( index ? 0 : 1 );
-			currentValue = this.values( index );
+			if ( !this.options.allowCrossingHandles ) {
+				otherVal = this.values( index ? 0 : 1 );
+				currentValue = this.values( index );
 
-			if ( this.options.values.length === 2 && this.options.range === true ) {
-				newVal =  index === 0 ? Math.min( otherVal, newVal ) : Math.max( otherVal, newVal );
+				if ( this.options.values.length === 2 && this.options.range === true ) {
+					newVal =  index === 0 ? Math.min( otherVal, newVal )
+										  : Math.max( otherVal, newVal );
+				}
 			}
 
 			newValues[ index ] = newVal;
@@ -473,6 +477,7 @@ return $.widget( "ui.slider", $.ui.mouse, {
 				this._animateOff = false;
 				break;
 			case "range":
+			case "allowCrossingHandles":
 				this._animateOff = true;
 				this._refresh();
 				this._animateOff = false;
@@ -590,7 +595,9 @@ return $.widget( "ui.slider", $.ui.mouse, {
 	},
 
 	_refreshValue: function() {
-		var lastValPercent, valPercent, value, valueMin, valueMax,
+		var valPercent, value, valueMin, valueMax,
+			rangeStartIndex = 0,
+			rangeStopIndex = 1,
 			oRange = this.options.range,
 			o = this.options,
 			that = this,
@@ -599,34 +606,48 @@ return $.widget( "ui.slider", $.ui.mouse, {
 
 		if ( this._hasMultipleValues() ) {
 			this.handles.each( function( i ) {
-				valPercent = ( that.values( i ) - that._valueMin() ) / ( that._valueMax() -
-					that._valueMin() ) * 100;
+				if ( that.values( i ) > that.values( rangeStopIndex ) ) {
+					rangeStopIndex = i;
+				}
+				if ( that.values( i ) < that.values( rangeStartIndex ) ) {
+					rangeStartIndex = i;
+				}
+			} );
+			this.handles.each( function( i ) {
+				var valPercentStart;
+				var computeValPercent = function( idx ) {
+					return ( that.values( idx ) - that._valueMin() ) / ( that._valueMax() -
+						that._valueMin() ) * 100;
+				};
+				valPercent = computeValPercent( i );
 				_set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
 				$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
 				if ( that.options.range === true ) {
 					if ( that.orientation === "horizontal" ) {
-						if ( i === 0 ) {
+						if ( i === rangeStartIndex ) {
 							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
 								left: valPercent + "%"
 							}, o.animate );
 						}
-						if ( i === 1 ) {
+						if ( i === rangeStopIndex ) {
+							valPercentStart = computeValPercent( rangeStartIndex );
 							that.range[ animate ? "animate" : "css" ]( {
-								width: ( valPercent - lastValPercent ) + "%"
+								width: Math.abs ( valPercent - valPercentStart ) + "%"
 							}, {
 								queue: false,
 								duration: o.animate
 							} );
 						}
 					} else {
-						if ( i === 0 ) {
+						if ( i === rangeStartIndex ) {
 							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
 								bottom: ( valPercent ) + "%"
 							}, o.animate );
 						}
-						if ( i === 1 ) {
+						if ( i === rangeStopIndex ) {
+							valPercentStart = computeValPercent( rangeStartIndex );
 							that.range[ animate ? "animate" : "css" ]( {
-								height: ( valPercent - lastValPercent ) + "%"
+								height: Math.abs ( valPercent - valPercentStart ) + "%"
 							}, {
 								queue: false,
 								duration: o.animate
@@ -634,7 +655,6 @@ return $.widget( "ui.slider", $.ui.mouse, {
 						}
 					}
 				}
-				lastValPercent = valPercent;
 			} );
 		} else {
 			value = this.value();