/*!
 * OpenUI5
 * (c) Copyright 2009-2026 SAP SE or an SAP affiliate company.
 * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
 */

sap.ui.define([
	'sap/ui/core/Control',
	'sap/ui/core/EnabledPropagator',
	'sap/ui/core/InvisibleText',
	'sap/ui/core/library',
	'sap/ui/core/ResizeHandler',
	'sap/base/Log',
	'./library',
	'./SliderTooltipContainer',
	'./SliderTooltip',
	'./SliderUtilities',
	'./SliderRenderer',
	'./ResponsiveScale',
	"sap/ui/thirdparty/jquery",
	"sap/ui/events/KeyCodes",
	"sap/ui/core/Configuration"
],
function(
	Control,
	EnabledPropagator,
	InvisibleText,
	coreLibrary,
	ResizeHandler,
	log,
	library,
	SliderTooltipContainer,
	SliderTooltip,
	SliderUtilities,
	SliderRenderer,
	ResponsiveScale,
	jQuery,
	KeyCodes,
	Configuration
) {
		"use strict";

		// shortcut for sap.m.touch
		var touch = library.touch;

		/**
		 * Constructor for a new <code>Slider</code>.
		 *
		 * @param {string} [sId] ID for the new control, generated automatically if no ID is given
		 * @param {object} [mSettings] Initial settings for the new control
		 *
		 * @class
		 * <h3>Overview</h3>
		 *
		 * A {@link sap.m.Slider} control represents a numerical range and a handle.
		 * The purpose of the control is to enable visual selection of a value in a continuous numerical range by moving an adjustable handle.
		 *
		 * <b>Notes:</b>
		 * <ul><li>Only horizontal sliders are possible. </li>
		 * <li>The handle can be moved in steps of predefined size. This is done with the <code>step</code> property. </li>
		 * <li>Setting the property <code>showAdvancedTooltip</code> shows an input field above the handle</li>
		 * <li>Setting the property <code>inputsAsTooltips</code> enables the user to enter a specific value in the handle's tooltip. </li>
		 * </ul>
		 *
		 * <h3>Structure</h3>
		 *
		 * The most important properties of the Slider are:
		 * <ul>
		 * <li> min - The minimum value of the slider range </li>
		 * <li> max - The maximum value of the slider range </li>
		 * <li> value - The current value of the slider</li>
		 * <li> progress - Determines if a progress bar will be shown on the slider range</li>
		 * <li> step - Determines the increments in which the slider will move</li>
		 * </ul>
		 * These properties determine the visualization of the tooltips:
		 * <ul>
		 * <li> <code>showAdvancedTooltip</code> - Determines if a tooltip should be displayed above the handle</li>
		 * <li> <code>inputsAsTooltips</code> - Determines if the tooltip displayed above the slider's handle should include an input field</li>
		 * </ul>
		 *
		 * <h3>Usage</h3>
		 *
		 * The most common usecase is to select values on a continuous numerical scale (e.g. temperature, volume, etc. ).
		 *
		 * <h3>Responsive Behavior</h3>
		 *
		 * The <code>sap.m.Slider</code> control adjusts to the size of its parent container by recalculating and resizing the width of the control.
		 * You can move the slider handle in several different ways:
		 * <ul>
		 * <li> Drag and drop to the desired value</li>
		 * <li> Click/tap on the range bar to move the handle to that location </li>
		 * <li> Enter the desired value in the input field (if available) </li>
		 * <li> Keyboard (Arrow keys, "+" and "-") </li>
		 * </ul>
		 *
		 * @extends sap.ui.core.Control
		 * @implements sap.ui.core.IFormContent
		 *
		 * @author SAP SE
		 * @version 1.108.49
		 *
		 * @constructor
		 * @public
		 * @alias sap.m.Slider
		 * @see {@link fiori:https://experience.sap.com/fiori-design-web/slider/ Slider}
		 */
		var Slider = Control.extend("sap.m.Slider", /** @lends sap.m.Slider.prototype */ {
			metadata: {

				interfaces: ["sap.ui.core.IFormContent"],
				library: "sap.m",
				properties: {

					/**
					 * Defines the width of the control.
					 */
					width: { type: "sap.ui.core.CSSSize", group: "Appearance", defaultValue: "100%" },

					/**
					 * Indicates whether the user can change the value.
					 */
					enabled: { type: "boolean", group: "Behavior", defaultValue: true },

					/**
					 * The name property to be used in the HTML code for the slider (e.g. for HTML forms that send data to the server via submit).
					 */
					name: { type: "string", group: "Misc", defaultValue: "" },

					/**
					 * The minimum value.
					 */
					min: { type: "float", group: "Data", defaultValue: 0 },

					/**
					 * The maximum value.
					 */
					max: { type: "float", group: "Data", defaultValue: 100 },

					/**
					 * Define the amount of units to change the slider when adjusting by drag and drop.
					 *
					 * Defines the size of the slider's selection intervals. (e.g. min = 0, max = 10, step = 5 would result in possible selection of the values 0, 5, 10).
					 *
					 * The step must be positive, if a negative number is provided, the default value will be used instead.
					 * If the width of the slider converted to pixels is less than the range (max - min), the value will be rounded to multiples of the step size.
					 */
					step: { type: "float", group: "Data", defaultValue: 1 },

					/**
					 * Indicate whether a progress bar indicator is shown.
					 */
					progress: { type: "boolean", group: "Misc", defaultValue: true },

					/**
					 * Define the value.
					 *
					 * If the value is lower/higher than the allowed minimum/maximum, the value of the properties <code>min</code>/<code>max</code> are used instead.
					 */
					value: { type: "float", group: "Data", defaultValue: 0 },

					/**
					 * Indicate whether the handle tooltip is shown.
					 * @since 1.31
					 *
					 */
					showHandleTooltip: { type: "boolean", group: "Appearance", defaultValue: true},

					/**
					 * Indicate whether the handle's advanced tooltip is shown. <b>Note:</b> Setting this option to <code>true</code>
					 * will ignore the value set in <code>showHandleTooltip</code>. This will cause only the advanced tooltip to be shown.
					 * @since 1.42
					 *
					 */
					showAdvancedTooltip: { type: "boolean", group: "Appearance", defaultValue: false},

					/**
					 * Indicates whether input fields should be used as tooltips for the handles. <b>Note:</b> Setting this option to <code>true</code>
					 * will only work if <code>showAdvancedTooltip</code> is set to <code>true</code>.
					 * @since 1.42
					 */
					inputsAsTooltips: {type: "boolean", group: "Appearance", defaultValue: false},

					/**
					 * Enables tickmarks visualisation
					 *
					 * @since 1.44
					 */
					enableTickmarks: {type: "boolean", group: "Appearance", defaultValue: false},

					/**
					 * Indicates whether a handle is pressed
					 *
					 * @private
					 */
					handlePressed: {type: "boolean", defaultValue: false, group: "Appearance", visibility: "hidden" }
				},
				defaultAggregation: "scale",
				aggregations: {
					/**
					 * A Container popup that renders SliderTooltips
					 * The actual type should be sap.m.SliderTooltipContainer
					 *
					 * @since 1.54
					 */
					_tooltipContainer: { type: "sap.ui.core.Control", multiple: false, visibility: "hidden" },
					/**
					 * Scale for visualisation of tickmarks and labels
					 *
					 * @since 1.46
					 */
					scale: { type: "sap.m.IScale", multiple: false, singularName: "scale" },
					/**
					 * Default scale for visualisation of tickmarks, if scale aggregation is not provided
					 *
					 * @since 1.56
					 */
					_defaultScale: { type: "sap.m.ResponsiveScale", multiple: false, visibility: "hidden" },

					/**
					 * Multiple Aggregation for Tooltips
					 *
					 * @since 1.56
					 */
					_defaultTooltips: { type: "sap.m.SliderTooltipBase", multiple: true, visibility: "hidden" },

					/**
					 * Aggregation for user-defined tooltips.
					 * <b>Note:</b> In case of Slider, only the first tooltip of the aggregation is used. In the RangeSlider case - the first two.
					 * If no custom tooltips are provided, the default are used
					 *
					 * @since 1.56
					 */
					customTooltips: { type: "sap.m.SliderTooltipBase", multiple: true },

					/**
					 * Invisible text for handles and progress announcement
					 *
					 * @since 1.54
					 */
					_handlesLabels: { type: "sap.ui.core.InvisibleText", multiple: true, visibility: "hidden" }
				},
				associations: {

					/**
					 * Association to controls / IDs which label this control (see WAI-ARIA attribute <code>aria-labelledby</code>).
					 * @since 1.27.0
					 */
					ariaLabelledBy: { type: "sap.ui.core.Control", multiple: true, singularName: "ariaLabelledBy" }
				},
				events: {

					/**
					 * This event is triggered after the end user finishes interacting, if there is any change.
					 */
					change: {
						parameters: {

							/**
							 * The current value of the slider after a change.
							 */
							value: { type: "float" }
						}
					},

					/**
					 * This event is triggered during the dragging period, each time the slider value changes.
					 */
					liveChange: {
						parameters: {

							/**
							 * The current value of the slider after a live change.
							 */
							value: { type: "float" }
						}
					}
				},
				designtime: "sap/m/designtime/Slider.designtime"
			},

			renderer: SliderRenderer
		});

		EnabledPropagator.apply(Slider.prototype, [true]);

		/* =========================================================== */
		/* Private methods and properties                              */
		/* =========================================================== */

		/* ----------------------------------------------------------- */
		/* Private methods                                             */
		/* ----------------------------------------------------------- */

		/**
		 * Returns the scale control that is used when tickmarks are enabled
		 *
		 * @private
		 * @returns {sap.m.IScale|undefined} The scale that is used or undefined,
		 * if tickmarks are not enabled
		 */
		Slider.prototype._getUsedScale = function () {
			if (!this.getEnableTickmarks()) {
				return;
			}

			return this.getAggregation('scale') || this.getAggregation('_defaultScale');
		};

		Slider.prototype._syncScaleUsage = function () {
			var bEnabledTickmarks = this.getEnableTickmarks(),
				oUserDefinedScale = this.getAggregation('scale'),
				oDefaultScale = this.getAggregation("_defaultScale");


			// if the default scale was set, but later on the user adds a scale
			// or set the enableTickmarks property to false, we should destroy the default one
			if ((oDefaultScale && !bEnabledTickmarks) || (oUserDefinedScale && oDefaultScale)) {
				this.destroyAggregation("_defaultScale", true);
			}

			// if there is no scale set, fall back to the default scale
			if (bEnabledTickmarks && !oUserDefinedScale && !oDefaultScale) {
				this.setAggregation("_defaultScale", new ResponsiveScale(), true);
			}
		};

		Slider.prototype._showTooltipsIfNeeded = function () {
			if (this.getShowAdvancedTooltip()) {
				this.getAggregation("_tooltipContainer").show(this);
				this.updateAdvancedTooltipDom(this.getValue());
			}
		};

		/**
		 * Convert <code>fValue</code> for RTL-Mode.
		 *
		 * @param {float} fValue input value
		 * @private
		 * @returns {float} output value
		 */
		Slider.prototype._convertValueToRtlMode = function(fValue) {
			return this.getMax() - fValue + this.getMin();
		};

		/**
		 * Recalculate some styles.
		 *
		 * @private
		 */
		Slider.prototype._recalculateStyles = function() {
			var $Slider = this.$();

			this._fSliderWidth = $Slider.width();
			this._fSliderPaddingLeft = parseFloat($Slider.css("padding-left"));
			this._fSliderOffsetLeft = $Slider.offset().left;
			this._fHandleWidth = this.$("handle").width();
		};

		/**
		 * Checks whether the minimum is lower than or equal to the maximum and
		 * whether the step is bigger than slider range.
		 *
		 * @private
		 * @returns {boolean} Whether the properties are correctly set
		 */
		Slider.prototype._validateProperties = function() {
			var fMin = this.getMin(),
				fMax = this.getMax(),
				fStep = this.getStep(),
				bMinbiggerThanMax = false,
				bError = false;

			// if the minimum is lower than or equal to the maximum, log a warning
			if (fMin >= fMax) {
				bMinbiggerThanMax = true;
				bError = true;
				log.warning("Warning: " + "Property wrong min: " + fMin + " >= max: " + fMax + " on ", this);
			}

			// if the step is negative or 0, set to 1 and log a warning
			if (fStep <= 0) {
				log.warning("Warning: " + "The step could not be negative on ", this);
			}

			// the step can't be bigger than slider range, log a warning
			if (fStep > (fMax - fMin) && !bMinbiggerThanMax) {
				bError = true;
				log.warning("Warning: " + "Property wrong step: " + fStep + " > max: " + fMax + " - " + "min: " + fMin + " on ", this);
			}

			return bError;
		};

		/**
		 * Calculate percentage.
		 *
		 * @param {float} fValue The value
		 * @private
		 * @returns {float} percent The corresponding percentage
		 */
		Slider.prototype._getPercentOfValue = function(fValue) {
			return SliderUtilities.getPercentOfValue(fValue, this.getMin(), this.getMax());
		};

		/**
		 * Get the value on certain position
		 *
		 * @param {float} fPercent The percent value
		 * @returns {number} The position value
		 * @private
		 */
		Slider.prototype._getValueOfPercent = function(fPercent) {
			var fMin = this.getMin(),
				fValue = (fPercent * (this.getMax() - fMin) / 100) + fMin,
				sNewValueFixedPoint = this.toFixed(fValue, this.getDecimalPrecisionOfNumber(this.getStep()));

			return Number(sNewValueFixedPoint);
		};

		/**
		 * Checks whether the given step is of the proper type.
		 *
		 * @param {int} iStep The step size
		 * @private
		 * @returns {int} The validated step size
		 */
		Slider.prototype._validateStep = function(iStep) {
			if (typeof iStep === "undefined") {
				return 1;	// default n = 1
			}

			if (typeof iStep !== "number") {
				log.warning('Warning: "iStep" needs to be a number', this);
				return 0;
			}

			if ((Math.floor(iStep) === iStep) && isFinite(iStep)) {
				return iStep;
			}

			log.warning('Warning: "iStep" needs to be a finite interger', this);

			return 0;
		};

		/**
		 * Handles resize of Slider.
		 *
		 * @private
		 */
		Slider.prototype._handleSliderResize = function (oEvent) {
			var oScale = this._getUsedScale();

			if (this.getEnableTickmarks() && oScale && oScale.handleResize) {
				oScale.handleResize(oEvent);
			}

			if (this.getShowAdvancedTooltip()) {
				this._handleTooltipContainerResponsiveness();
			}
		};

		Slider.prototype._handleTooltipContainerResponsiveness = function () {
			this.getAggregation("_tooltipContainer").setWidth(this.$().width() + "px");
		};

		Slider.prototype.getDecimalPrecisionOfNumber = function(fValue) {

			// the value is an integer
			if (Math.floor(fValue) === fValue) {
				return 0;
			}

			var sValue = fValue.toString(),
				iIndexOfDot = sValue.indexOf("."),
				iIndexOfENotation = sValue.indexOf("e-"),
				bIndexOfENotationFound = iIndexOfENotation !== -1, // the "e-" is found in the value
				bIndexOfDotFound = iIndexOfDot !== -1; // the "." is found in the value

			// note: numbers such as 0.0000005 are represented using the e-notation
			// (for example, 0.0000005 becomes 5e-7)
			if (bIndexOfENotationFound) {

				// get the e-notation exponent e.g.: in the number 5e-7, the exponent is 7
				var iENotationExponent = +sValue.slice(iIndexOfENotation + 2);

				// If both, the e-notation and the dot character are found in the string representation of the number,
				// it means that the number has a format similar to e.g.: 1.15e-7.
				// In this case, the precision is calculated by adding the number of digits between the dot character
				// and the e character, e.g.: the number 1.15e-7 has a precision of 9
				if (bIndexOfDotFound) {
					return iENotationExponent + sValue.slice(iIndexOfDot + 1, iIndexOfENotation).length;
				}

				return iENotationExponent;
			}

			if (bIndexOfDotFound) {
				return sValue.length - iIndexOfDot - 1;
			}

			return 0;
		};

		/**
		 * Formats the <code>fNumber</code> using the fixed-point notation.
		 *
		 * <b>Note:</b> The number of digits to appear after the decimal point of the value
		 * should be between 0 and 20 to avoid a RangeError when calling the <code>Number.toFixed()</code> method.
		 *
		 * @param {float} fNumber The number to format.
		 * @param {int} [iDigits] The number of digits to appear after the decimal point.
		 * @returns {string} A string representation of <code>fNumber</code> that does not use exponential notation.
		 * @private
		 */
		Slider.prototype.toFixed = function(fNumber, iDigits) {

			if (iDigits === undefined) {
				iDigits = this.getDecimalPrecisionOfNumber(fNumber);
			}

			if (iDigits > 20) {
				iDigits = 20;
			} else if (iDigits < 0) {
				iDigits = 0;
			}

			// note: .toFixed() does not return a string when the number is negative
			return fNumber.toFixed(iDigits) + "";
		};

		Slider.prototype.setDomValue = function(sNewValue) {
			var oDomRef = this.getDomRef(),
				sScaleLabel = this._formatValueByCustomElement(sNewValue),
				oTooltipContainer = this.getAggregation("_tooltipContainer");

			if (!oDomRef) {
				return;
			}

			// note: round negative percentages to 0
			var sPerValue = Math.max(this._getPercentOfValue(+sNewValue), 0) + "%",
				oHandleDomRef = this.getDomRef("handle");

			if (this.getName()) {
				this.getDomRef("input").setAttribute("value", sScaleLabel);
			}

			if (this.getProgress()) {

				// update the progress indicator
				this.getDomRef("progress").style.width = sPerValue;
			}

			// update the position of the handle
			oHandleDomRef.style[Configuration.getRTL() ? "right" : "left"] = sPerValue;

			// update the position of the advanced tooltip
			if (this.getShowAdvancedTooltip() && oTooltipContainer.getDomRef()) {
				this.updateAdvancedTooltipDom(sNewValue);
			}

			if (this.getShowHandleTooltip() && !this.getShowAdvancedTooltip()) {

				// update the tooltip
				oHandleDomRef.title = sScaleLabel;
			}

			this._updateHandleAriaAttributeValues(oHandleDomRef, sNewValue, sScaleLabel);
		};

		/**
		 * Updates the aria-valuenow and aria-valuetext.
		 *
		 * @param {object} oHandleDomRef The DOM reference of the slider handle
		 * @param {string} sValue The current value
		 * @param {string} sScaleLabel The label of the tickmark label
		 * @private
		 */
		Slider.prototype._updateHandleAriaAttributeValues = function (oHandleDomRef, sValue, sScaleLabel) {
			// update the ARIA attribute value
			if (this._isElementsFormatterNotNumerical(sValue)) {
				oHandleDomRef.setAttribute("aria-valuenow", sValue);
				oHandleDomRef.setAttribute("aria-valuetext", sScaleLabel);
			} else {
				oHandleDomRef.setAttribute("aria-valuenow", sScaleLabel);
				oHandleDomRef.removeAttribute("aria-valuetext");
			}
		};

		/**
		 * Format the value from the Scale or Tooltip callback.
		 *
		 * As the scale might want to display something else, but not numbers, we need to ensure that the format
		 * would be populated to the relevant parts of the Slider:
		 * - Handle tooltips
		 * - Accessibility values
		 * - Advanced tooltips are not taken into consideration as they need to implement their own formatting function.
		 * That way we'd keep components as loose as possible.
		 *
		 * @param {float} fValue The value to be formatted
		 * @param {string} sPriority Default priority is:
		 *    1) Float value from the Slider,
		 *    2) Scale formatter,
		 *    3) Tooltips formatter so #3 always overwrites the rest.
		 *    Priorities could be changed, so some formatter to prevail the others. Possible values are:
		 *    - 'slider' (uses default value from the Slider),
		 *    - 'scale' (use the Scale formatter),
		 *    - null/undefined (use the Tooltips' formatter)
		 *
		 * @returns {string} The formatted value
		 * @private
		 */
		Slider.prototype._formatValueByCustomElement = function (fValue, sPriority) {
			var oScale = this._getUsedScale(),
				oTooltip = this.getUsedTooltips()[0],
				sFormattedValue = "" + fValue;

			if (sPriority === 'slider') {
				return sFormattedValue;
			}

			// If there's a labelling for the scale, use it
			if (this.getEnableTickmarks() && oScale && oScale.getLabel) {
				sFormattedValue = "" + oScale.getLabel(fValue, this);
			}

			if (sPriority === 'scale') {
				return sFormattedValue;
			}

			// If there's a labelling for the tooltips, use it and overwrite previous
			if (this.getShowAdvancedTooltip() && oTooltip && oTooltip.getLabel) {
				sFormattedValue = "" + oTooltip.getLabel(fValue, this);
			}

			return sFormattedValue;
		};

		/**
		 * Checks whether the scale has a numerical label defined for a certain value
		 * of the slider.
		 *
		 * @param {float} fValue
		 * @returns {boolean} Returns true, when the scale has a not numerical label defined.
		 * @private
		 */
		Slider.prototype._isElementsFormatterNotNumerical = function (fValue) {
			var vValue = this._formatValueByCustomElement(fValue);
			return isNaN(vValue);
		};

		/**
		 * Updates value of the advanced tooltip.
		 *
		 * @param {string} sNewValue The new value
		 * @protected
		 */
		Slider.prototype.updateAdvancedTooltipDom = function (sNewValue) {
			var aTooltips = this.getUsedTooltips();

			this.updateTooltipsPositionAndState(aTooltips[0], parseFloat(sNewValue));
		};

		/**
		 * Gets the tooltips that should be shown.
		 * Returns custom tooltips if provided. Otherwise - default tooltips
		 *
		 * @protected
		 * @returns {sap.m.SliderTooltipBase[]} SliderTooltipBase instances.
		 */
		Slider.prototype.getUsedTooltips = function () {
			var aCustomTooltips = this.getCustomTooltips(),
				aDefaultTooltips = this.getAggregation("_defaultTooltips") || [];

			return aCustomTooltips.length ? aCustomTooltips : aDefaultTooltips;
		};

		/**
		 * Updates values of Slider and repositions tooltips.
		 *
		 * @param {string} oTooltip Tooltip to be changed
		 * @param {float} fValue New value of the Slider
		 * @private
		 * @ui5-restricted sap.m.SliderTooltipBase
		 */
		Slider.prototype.updateTooltipsPositionAndState = function (oTooltip, fValue) {
			var oTooltipsContainer = this.getAggregation("_tooltipContainer");

			oTooltip.setValue(fValue);
			oTooltipsContainer.repositionTooltips(this.getMin(), this.getMax());
		};

		/**
		 * Gets the closest handle to a <code>touchstart</code> event.
		 *
		 * @returns {object} The nearest handle DOM reference.
		 */
		Slider.prototype.getClosestHandleDomRef = function() {

			// there is only one handle, it is always the nearest
			return this.getDomRef("handle");
		};

		/**
		 * Increase the value of the slider by the given <code>fIncrement</code>.
		 *
		 * @param {int} fIncrement The increment size
		 * @private
		 */
		Slider.prototype._increaseValueBy = function(fIncrement) {
			var fValue,
				fNewValue;

			if (this.getEnabled()) {
				fValue = this.getValue();
				this.setValue(fValue + (fIncrement || 1));
				fNewValue = this.getValue();

				if (fValue < fNewValue) {
					this._fireChangeAndLiveChange({ value: fNewValue });
				}
			}
		};

		/**
		 * Decrease the value of the slider by the given <code>fDecrement</code>.
		 *
		 * @param {int} fDecrement The decrement size
		 * @private
		 */
		Slider.prototype._decreaseValueBy = function(fDecrement) {
			var fValue,
				fNewValue;

			if (this.getEnabled()) {
				fValue = this.getValue();
				this.setValue(fValue - (fDecrement || 1));
				fNewValue = this.getValue();

				if (fValue > fNewValue) {
					this._fireChangeAndLiveChange({ value: fNewValue });
				}
			}
		};

		Slider.prototype._getLongStep = function() {
			var fMin = this.getMin(),
				fMax = this.getMax(),
				fStep = this.getStep(),
				fLongStep = (fMax - fMin) / 10,
				iStepsFromMinToMax = (fMax - fMin) / fStep;

			return iStepsFromMinToMax > 10 ? fLongStep : fStep;
		};

		Slider.prototype._fireChangeAndLiveChange = function(oParam) {
			this.fireChange(oParam);
			this.fireLiveChange(oParam);
		};

		/**
		 * Handles change of Tooltip's inputs.
		 *
		 * @param {jQuery.Event} oEvent
		 * @protected
		 */
		Slider.prototype.handleTooltipChange = function (oEvent) {
			var fNewValue = parseFloat(oEvent.getParameter("value"));

			this.setValue(fNewValue);
			this._fireChangeAndLiveChange({ value: fNewValue });
		};

		/**
		 * Register the ResizeHandler
		 *
		 * @private
		 */
		Slider.prototype._registerResizeHandler = function () {
			if (!this._parentResizeHandler) {
				this._parentResizeHandler = ResizeHandler.register(this, this._handleSliderResize.bind(this));
			}
		};

		/**
		 * Deregister the ResizeHandler
		 *
		 * @private
		 */
		Slider.prototype._deregisterResizeHandler = function () {
			if (this._parentResizeHandler) {
				ResizeHandler.deregister(this._parentResizeHandler);
				this._parentResizeHandler = null;
			}
		};

		/* =========================================================== */
		/* Lifecycle methods                                           */
		/* =========================================================== */

		Slider.prototype.init = function () {
			var oSliderLabel;

			// used to track the id of touch points
			this._iActiveTouchId = -1;

			this._bSetValueFirstCall = true;

			this._fValueBeforeFocus = 0;

			// resize handler of the slider
			this._parentResizeHandler = null;

			this._oResourceBundle = sap.ui.getCore().getLibraryResourceBundle("sap.m");

			// a reference to the SliderTooltipContainer
			this._oTooltipContainer = null;

			oSliderLabel = new InvisibleText({
				text: this._oResourceBundle.getText("SLIDER_HANDLE")
			});

			this.addAggregation("_handlesLabels", oSliderLabel);
		};

		Slider.prototype.exit = function () {
			if (this._oResourceBundle) {
				this._oResourceBundle = null;
			}

			if (this.getAggregation("_defaultTooltips")) {
				this.destroyAggregation("_defaultTooltips");
			}

			this._deregisterResizeHandler();
		};

		Slider.prototype.onBeforeRendering = function () {
			var bError = this._validateProperties();

			// update the value only if there aren't errors
			if (!bError) {
				this.setValue(this.getValue());

				// this is the current % value of the progress bar
				// note: round negative percentages to 0
				this._sProgressValue = Math.max(this._getPercentOfValue(this.getValue()), 0) + "%";
			}

			if (this.getShowAdvancedTooltip()) {
				this.initAndSyncTooltips(["leftTooltip"]);
			}

			this._deregisterResizeHandler();

			// set the correct scale aggregation, if needed
			this._syncScaleUsage();
		};

		/**
		 * Forwards properties to a given control
		 * @param {Array} [aProperties] Array of properties to forward
		 * @param {sap.ui.core.Element} [oControl] Control to which should be forward
		 * @protected
		 */
		Slider.prototype.forwardProperties = function (aProperties, oControl) {
			aProperties.forEach(function (sProperty) {
				oControl.setProperty(sProperty, this.getProperty(sProperty), true);
			}, this);
		};

		/**
		 * Forwards properties to default tooltips
		 *
		 * @param {number} [iTooltipCount] Count of the tooltips
		 * @protected
		 */
		Slider.prototype.forwardPropertiesToDefaultTooltips = function (iTooltipCount) {
			var aDefaultTooltips = this.getAggregation("_defaultTooltips") || [];

			for (var index = 0; index < iTooltipCount; index++) {
				this.forwardProperties(["min", "max", "step"], aDefaultTooltips[index]);

				aDefaultTooltips[index].setProperty("width", this._getMaxTooltipWidth() + "px", true);
				aDefaultTooltips[index].setProperty("editable", this.getInputsAsTooltips(), true);
			}
		};

		/**
		 * Creates custom tooltips, if needed, and forwards properties to them
		 *
		 * @param {number} [iTooltipCount] Count of the tooltips
		 * @protected
		 */
		Slider.prototype.associateCustomTooltips = function (iTooltipCount) {
			// destroy all default tooltips
			this.destroyAggregation("_defaultTooltips", true);

			// prevent invalidation of the children before rendering
			this._oTooltipContainer.removeAllAssociation("associatedTooltips", true);

			for (var index = 0; index < iTooltipCount; index++) {
				this._oTooltipContainer.addAssociation("associatedTooltips", this.getCustomTooltips()[index], true);
			}
		};

		/**
		 * Creates default tooltips, if needed, and forwards properties to them
		 *
		 * @param {Array} [aTooltipIds] Array of strings for ID generation
		 * @protected
		 */
		Slider.prototype.assignDefaultTooltips = function (aTooltipIds) {
			var aDefaultTooltips = this.getAggregation("_defaultTooltips") || [];

			// skip init tooltips if they are already there
			if (aDefaultTooltips.length === 0) {
				// clear the assoctiated tooltips from the container
				this._oTooltipContainer.removeAllAssociation("associatedTooltips", true);

				aTooltipIds.forEach(function (sId) {
					this.initDefaultTooltip(sId);
				}, this);
			}

			// forward properties to the default tooltips
			this.forwardProperties(["enabled"], this._oTooltipContainer);
			this.forwardPropertiesToDefaultTooltips(aTooltipIds.length);
		};

		/**
		 * Assigns tooltips and forwards properties to them
		 *
		 * @param {Array}[aTooltipIds] Array of strings for ID generation
		 * @protected
		 */
		Slider.prototype.initAndSyncTooltips = function (aTooltipIds) {
			var aCustomTooltips = this.getCustomTooltips(),
				iCustomTooltipsCount = aCustomTooltips.length,
				iMaxCustomTooltipCount = aTooltipIds.length;

			this.initTooltipContainer();

			// validates the count of passed tooltips and takes the needed count or fallbacks to default tooltips
			if (iCustomTooltipsCount < iMaxCustomTooltipCount) {
				this.assignDefaultTooltips(aTooltipIds);
			} else {

				// log a warning if tooltips are more than one for Slider or more than two for RangeSlider
				if (iCustomTooltipsCount > iMaxCustomTooltipCount) {
					log.warning("Warning: More than " + iMaxCustomTooltipCount + " Custom Tooltips are provided. Only the first will be used.");
				}

				// we use the first 2 tooltips of the aggregation
				this.associateCustomTooltips(iMaxCustomTooltipCount);
			}
		};

		/**
		 * Creates a default SliderTooltip instance and adds it as an aggregation
		 *
		 * @param {string}[sId] The tooltip ID
		 * @protected
		 */
		Slider.prototype.initDefaultTooltip = function (sId) {
			var oTooltip = new SliderTooltip(this.getId() + "-" + sId, {
				change: this.handleTooltipChange.bind(this)
			});

			this.getAggregation("_tooltipContainer").addAssociation("associatedTooltips", oTooltip, true);
			this.addAggregation("_defaultTooltips", oTooltip, true);
		};

		/**
		 * Creates a SliderTooltipContainer
		 *
		 * @protected
		 */
		Slider.prototype.initTooltipContainer = function () {
			if (!this._oTooltipContainer) {
				this._oTooltipContainer = new SliderTooltipContainer();
				this.setAggregation("_tooltipContainer", this._oTooltipContainer, true);
			}
		};

		Slider.prototype._getMaxTooltipWidth = function () {
			var aAbsRange = [Math.abs(this.getMin()), Math.abs(this.getMax())],
				iRangeIndex = aAbsRange[0] > aAbsRange[1] ? 0 : 1;

			return ((aAbsRange[iRangeIndex].toString()).length + this.getDecimalPrecisionOfNumber(this.getStep()) + 1) * SliderUtilities.CONSTANTS.CHARACTER_WIDTH_PX;
		};

		Slider.prototype.onAfterRendering = function () {
			if (this.getShowAdvancedTooltip()) {
				this._recalculateStyles();
				this._handleTooltipContainerResponsiveness();
			}
			this._handleSliderResize({control: this});
			this._registerResizeHandler();
		};

		/* =========================================================== */
		/* Event handlers                                              */
		/* =========================================================== */

		/**
		 * Handles the <code>touchstart</code> event.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.ontouchstart = function(oEvent) {
			var fMin = this.getMin(),
				oTouch = oEvent.targetTouches[0],
				fNewValue,
				CSS_CLASS = this.getRenderer().CSS_CLASS,
				sEventNamespace = "." + CSS_CLASS;

			// mark the event for components that needs to know if the event was handled
			oEvent.setMarked();
			// Should be prevent as in Safari while dragging the handle everything else gets selection.
			// As part of the Slider, Inputs in the tooltips should be excluded
			if (oEvent.target.className.indexOf("sapMInput") === -1) {
				oEvent.preventDefault();
			}

			this.focus();

			// only process single touches
			if (touch.countContained(oEvent.touches, this.getId()) > 1 ||
				!this.getEnabled() ||

				// detect which mouse button caused the event and only process the standard click
				// (this is usually the left button, oEvent.button === 0 for standard click)
				// note: if the current event is a touch event oEvent.button property will be not defined
				oEvent.button ||

				// process the event if the target is not a composite control e.g.: a tooltip
				(oEvent.srcControl !== this)) {

				return;
			}

			// track the id of the first active touch point
			this._iActiveTouchId = oTouch.identifier;

			// registers event listeners
			jQuery(document).on("touchend" + sEventNamespace + " touchcancel" + sEventNamespace + " mouseup" + sEventNamespace, this._ontouchend.bind(this))
							.on(oEvent.originalEvent.type === "touchstart" ? "touchmove" + sEventNamespace : "touchmove" + sEventNamespace + " mousemove" + sEventNamespace, this._ontouchmove.bind(this));

			var oNearestHandleDomRef = this.getClosestHandleDomRef();

			if (oTouch.target !== oNearestHandleDomRef) {

				// set the focus to the nearest slider handle
				setTimeout(oNearestHandleDomRef["focus"].bind(oNearestHandleDomRef), 0);
			}

			// recalculate some styles,
			// those values may change when the device orientation changes
			this._recalculateStyles();
			this._fDiffX = this._fSliderPaddingLeft;
			this._fInitialValue = this.getValue();

			// add active state
			this.$("inner").addClass(CSS_CLASS + "Pressed");

			this.setProperty("handlePressed", true);

			if (oTouch.target === this.getDomRef("handle")) {

				this._fDiffX = (oTouch.pageX - jQuery(oNearestHandleDomRef).offset().left) + this._fSliderPaddingLeft - (this._fHandleWidth / 2);
			} else {

				fNewValue = (((oTouch.pageX - this._fSliderPaddingLeft - this._fSliderOffsetLeft) / this._fSliderWidth) * (this.getMax() - fMin)) +  fMin;

				if (Configuration.getRTL()) {
					fNewValue = this._convertValueToRtlMode(fNewValue);
				}

				// update the value
				this.setValue(fNewValue);

				// new validated value
				fNewValue = this.getValue();

				if (this._fInitialValue !== fNewValue) {
					this.fireLiveChange({ value: fNewValue });
				}
			}
		};

		/**
		 * Handles the <code>touchmove</code> event.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 * @private
		 */
		Slider.prototype._ontouchmove = function(oEvent) {

			// mark the event for components that needs to know if the event was handled
			oEvent.setMarked();

			// note: prevent native document scrolling
			oEvent.preventDefault();

			// suppress the emulated mouse event from touch interfaces
			if (oEvent.isMarked("delayedMouseEvent") ||
				!this.getEnabled() ||

				// detect which mouse button caused the event and only process the standard click
				// (this is usually the left button, oEvent.button === 0 for standard click)
				// note: if the current event is a touch event oEvent.button property will be not defined
				oEvent.button) {

				return;
			}

			var fMin = this.getMin(),
				fValue = this.getValue(),
				oTouch = touch.find(oEvent.changedTouches, this._iActiveTouchId),	// find the active touch point
				iPageX = oTouch ? oTouch.pageX : oEvent.pageX,
				fNewValue = (((iPageX - this._fDiffX - this._fSliderOffsetLeft) / this._fSliderWidth) * (this.getMax() - fMin)) +  fMin;

			// RTL mirror
			if (Configuration.getRTL()) {
				fNewValue = this._convertValueToRtlMode(fNewValue);
			}

			this.setValue(fNewValue);

			// validated value
			fNewValue = this.getValue();

			if (fValue !== fNewValue) {
				this.fireLiveChange({ value: fNewValue });
			}
		};

		/**
		 * Handles the <code>touchend</code> event.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 * @private
		 */
		Slider.prototype._ontouchend = function(oEvent) {
			var CSS_CLASS = this.getRenderer().CSS_CLASS,
				sEventNamespace = "." + CSS_CLASS;

			// mark the event for components that needs to know if the event was handled
			oEvent.setMarked();

			// suppress the emulated mouse event from touch interfaces
			if (oEvent.isMarked("delayedMouseEvent") ||
				!this.getEnabled() ||

				// detect which mouse button caused the event and only process the standard click
				// (this is usually the left button, oEvent.button === 0 for standard click)
				// note: if the current event is a touch event oEvent.button property will be not defined
				oEvent.button) {

				return;
			}

			// removes the registered event listeners
			jQuery(document).off(sEventNamespace);

			var fValue = this.getValue();

			// remove the active state
			this.setProperty("handlePressed", false);

			if (this._fInitialValue !== fValue) {
				this.fireChange({ value: fValue });
			}
		};

		/**
		 * Handles the <code>focusin</code> event.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onfocusin = function(oEvent) {
			this._fValueBeforeFocus = this.getValue();

			if (this.getShowAdvancedTooltip()) {
				this.getAggregation("_tooltipContainer").show(this);
				this._setAriaControls();
				this.updateAdvancedTooltipDom(this.getValue());
			}
		};

		/**
		 * Adds aria-controls attribute, when the tooltips are rendered.
		 *
		 * @private
		 */
		Slider.prototype._setAriaControls = function () {
			var oTooltip = this.getUsedTooltips()[0],
				oHandle = this.getFocusDomRef();

			if (this.getInputsAsTooltips() && oTooltip && oTooltip.getDomRef()) {
				oHandle.setAttribute("aria-controls", oTooltip.getId());
			}
		};

		/**
		 * Handles the <code>focusout</code> event.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onfocusout = function(oEvent) {

			if (!this.getShowAdvancedTooltip()) {
				return;
			}

			var bSliderFocused = jQuery.contains(this.getDomRef(), oEvent.relatedTarget),
				bTooltipFocused = jQuery.contains(this.getAggregation("_tooltipContainer").getDomRef(), oEvent.relatedTarget);

			if (bSliderFocused || bTooltipFocused) {
				return;
			}

			this.getAggregation("_tooltipContainer").hide();
		};

		Slider.prototype.onmouseover = function(oEvent) {
			var bTooltipFocused, oTooltipContainer;

			if (this.getShowAdvancedTooltip()) {
				this.getAggregation("_tooltipContainer").show(this);

				oTooltipContainer = this.getAggregation("_tooltipContainer");
				bTooltipFocused = jQuery.contains(oTooltipContainer.getDomRef(), document.activeElement);
				this._setAriaControls();

				// do not update Tooltip's value if it is already focused
				if (bTooltipFocused) {
					return;
				}

				this.updateAdvancedTooltipDom(this.getValue());
			}
		};

		Slider.prototype.onmouseout = function (oEvent) {

			if (!this.getShowAdvancedTooltip()) {
				return;
			}

			var oTooltipContianerRef = this.getAggregation("_tooltipContainer").getDomRef(),
				oSliderRef = this.getDomRef(),
				bHandleFocused = jQuery.contains(oSliderRef, document.activeElement),
				bTooltipFocused = jQuery.contains(oTooltipContianerRef, document.activeElement);

			if (!oTooltipContianerRef || bHandleFocused || bTooltipFocused) {
				return;
			}

			if (jQuery.contains(this.getDomRef(), oEvent.toElement) || (oSliderRef === oEvent.toElement)) {
				return;
			}

			if (jQuery.contains(this.getAggregation("_tooltipContainer").getDomRef(), oEvent.toElement)) {
				return;
			}

			this.getAggregation("_tooltipContainer").hide();
		};

		/* ----------------------------------------------------------- */
		/* Keyboard handling                                           */
		/* ----------------------------------------------------------- */

		/**
		 * Slider should focus its inputs of they are advanced and editable on F2.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onkeydown = function (oEvent) {
			var aTooltips = this.getUsedTooltips();

			if (oEvent.keyCode === SliderUtilities.CONSTANTS.F2_KEYCODE && aTooltips[0] && this.getInputsAsTooltips()) {
				aTooltips[0].focus();
			}

			if (oEvent.keyCode === KeyCodes.SPACE) {
				oEvent.preventDefault();
			}
		};

		/**
		 * Handles the <code>sapincrease</code> event when right arrow or up arrow is pressed.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onsapincrease = function(oEvent) {
			var fValue,
				fNewValue;

			// note: prevent document scrolling when arrow keys are pressed
			oEvent.preventDefault();

			// mark the event for components that needs to know if the event was handled
			oEvent.setMarked();

			if (this.getEnabled()) {
				fValue = this.getValue();
				this.stepUp(1);
				fNewValue = this.getValue();

				if (fValue < fNewValue) {
					this._fireChangeAndLiveChange({ value: fNewValue });
				}
			}

			this._showTooltipsIfNeeded();
		};

		/**
		 * Handles the <code>sapincreasemodifiers</code> event when Ctrl + right arrow or up arrow are pressed.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onsapincreasemodifiers = function(oEvent) {

			if (oEvent.altKey) {
				return;
			}

			// note: prevent document scrolling when arrow keys are pressed
			oEvent.preventDefault();
			oEvent.stopPropagation();

			// mark the event for components that needs to know if the event was handled
			oEvent.setMarked();

			this._increaseValueBy(this._getLongStep());
			this._showTooltipsIfNeeded();
		};

		/**
		 * Handles the <code>sapdecrease</code> event when left arrow or down arrow are pressed.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onsapdecrease = function(oEvent) {
			var fValue,
				fNewValue;

			// note: prevent document scrolling when arrow keys are pressed
			oEvent.preventDefault();

			// mark the event for components that needs to know if the event was handled
			oEvent.setMarked();

			if (this.getEnabled()) {
				fValue = this.getValue();
				this.stepDown(1);
				fNewValue = this.getValue();

				if (fValue > fNewValue) {
					this._fireChangeAndLiveChange({ value: fNewValue });
				}
			}

			this._showTooltipsIfNeeded();
		};

		/**
		 * Handles the <code>sapdecreasemodifiers</code> event when Ctrl + left or Ctrl + down keys are pressed.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onsapdecreasemodifiers = function(oEvent) {

			if (oEvent.altKey) {
				return;
			}

			// note: prevent document scrolling when arrow keys are pressed
			oEvent.preventDefault();
			oEvent.stopPropagation();

			// mark the event for components that needs to know if the event was handled
			oEvent.setMarked();

			this._decreaseValueBy(this._getLongStep());

			this._showTooltipsIfNeeded();
		};

		/**
		 * Handles the <code>onsapplus</code> event when "+" is pressed.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onsapplus = function(oEvent) {
			var fValue,
				fNewValue;

			// mark the event for components that needs to know if the event was handled
			oEvent.setMarked();

			if (this.getEnabled()) {

				fValue = this.getValue();
				this.stepUp(1);
				fNewValue = this.getValue();

				if (fValue < fNewValue) {
					this._fireChangeAndLiveChange({ value: fNewValue });
				}
			}

			this._showTooltipsIfNeeded();
		};

		/**
		 * Handles the <code>sapminus</code> event when "-" is pressed.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onsapminus = function(oEvent) {
			var fValue,
				fNewValue;

			// mark the event for components that needs to know if the event was handled
			oEvent.setMarked();

			if (this.getEnabled()) {

				fValue = this.getValue();
				this.stepDown(1);
				fNewValue = this.getValue();

				if (fValue > fNewValue) {
					this._fireChangeAndLiveChange({ value: fNewValue });
				}
			}


			this._showTooltipsIfNeeded();
		};

		/**
		 * Handles the <code>sapescape</code> event when escape key is pressed.
		 *
		 */
		Slider.prototype.onsapescape = function() {

			// reset the slider back to the value
			// which it had when it got the focus
			this.setValue(this._fValueBeforeFocus);
		};

		/**
		 * Handles the <code>sappageup</code> event when page up is pressed.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onsappageup = Slider.prototype.onsapincreasemodifiers;

		/**
		 * Handles the <code>sappagedown</code> event when when page down is pressed.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onsappagedown = Slider.prototype.onsapdecreasemodifiers;

		/**
		 * Handles the <code>saphome</code> event when home key is pressed.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onsaphome = function(oEvent) {

			// mark the event for components that needs to know if the event was handled
			oEvent.setMarked();

			var fMin = this.getMin();

			// note: prevent document scrolling when Home key is pressed
			oEvent.preventDefault();

			if (this.getEnabled() && this.getValue() > fMin) {
				this.setValue(fMin);
				this._fireChangeAndLiveChange({ value: fMin });
			}

			this._showTooltipsIfNeeded();
		};

		/**
		 * Handles the <code>sapend</code> event when the End key pressed.
		 *
		 * @param {jQuery.Event} oEvent The event object.
		 */
		Slider.prototype.onsapend = function(oEvent) {

			// mark the event for components that needs to know if the event was handled
			oEvent.setMarked();

			var fMax = this.getMax();

			// note: prevent document scrolling when End key is pressed
			oEvent.preventDefault();

			if (this.getEnabled() && this.getValue() < fMax) {
				this.setValue(fMax);
				this._fireChangeAndLiveChange({ value: fMax });
			}

			this._showTooltipsIfNeeded();
		};

		/* =========================================================== */
		/* API method                                                  */
		/* =========================================================== */

		/* ----------------------------------------------------------- */
		/* Public methods                                              */
		/* ----------------------------------------------------------- */

		Slider.prototype.getFocusDomRef = function() {
			return this.getDomRef("handle");
		};

		/**
		 * Increments the value by multiplying the <code>step</code> with the given parameter.
		 *
		 * @param {int} [iStep=1] The number of steps the slider goes up.
		 * @returns {this} <code>this</code> to allow method chaining.
		 * @public
		 */
		Slider.prototype.stepUp = function(iStep) {
			return this.setValue(this.getValue() + (this._validateStep(iStep) * this.getStep()), { snapValue: false });
		};

		/**
		 * Decrements the value by multiplying the step the <code>step</code> with the given parameter.
		 *
		 * @param {int} [iStep=1] The number of steps the slider goes down.
		 * @returns {this} <code>this</code> to allow method chaining.
		 * @public
		 */
		Slider.prototype.stepDown = function(iStep) {
			return this.setValue(this.getValue() - (this._validateStep(iStep) * this.getStep()), { snapValue: false });
		};

		/**
		 * Sets the property <code>value</code>.
		 *
		 * Default value is <code>0</code>.
		 *
		 * @param {float} fNewValue new value for property <code>value</code>.
		 * @param {object} mOptions The options object
		 * @returns {this} <code>this</code> to allow method chaining.
		 * @public
		 */
		Slider.prototype.setValue = function(fNewValue, mOptions) {

			// note: sometimes the setValue() method is call before the step, max and min
			// properties are set, in this case the value should not be adjusted
			if (this._bSetValueFirstCall) {
				this._bSetValueFirstCall = false;
				return this.setProperty("value", fNewValue, true);
			}

			var fMin = this.getMin(),
				fMax = this.getMax(),
				fStep = this.getStep(),
				fValue = this.getValue(),
				sNewValueFixedPoint,
				bSnapValue = true,
				fModStepVal;

			if (mOptions) {
				bSnapValue = !!mOptions.snapValue;
			}

			// validate the new value before arithmetic calculations
			if (typeof fNewValue !== "number" || !isFinite(fNewValue)) {
				return this;
			}

			fModStepVal = Math.abs((fNewValue - fMin) % fStep);

			if (bSnapValue && (fModStepVal !== 0) /* division with remainder */) {

				// snap the new value to the nearest step
				fNewValue = fModStepVal * 2 >= fStep ? fNewValue + fStep - fModStepVal : fNewValue - fModStepVal;
			}

			// constrain the new value between the minimum and maximum
			if (fNewValue < fMin) {
				fNewValue = fMin;
			} else if (fNewValue > fMax) {
				fNewValue = fMax;
			}

			sNewValueFixedPoint = this.toFixed(fNewValue, this.getDecimalPrecisionOfNumber(fStep));
			fNewValue = Number(sNewValueFixedPoint);

			// update the value
			this.setProperty("value", fNewValue);

			// update the value in DOM only when it has changed
			if (fValue !== this.getValue()) {
				this.setDomValue(sNewValueFixedPoint);
			}

			return this;
		};

		return Slider;
	});