diff --git a/packages/main/bundle.esm.js b/packages/main/bundle.esm.js index 263bf50218cb..5e95a600ede5 100644 --- a/packages/main/bundle.esm.js +++ b/packages/main/bundle.esm.js @@ -65,6 +65,7 @@ import RadioButton from "./dist/RadioButton.js"; import ResponsivePopover from "./dist/ResponsivePopover.js"; import SegmentedButton from "./dist/SegmentedButton.js"; import Select from "./dist/Select.js"; +import Slider from "./dist/Slider.js"; import Switch from "./dist/Switch.js"; import MessageStrip from "./dist/MessageStrip.js"; import MultiComboBox from "./dist/MultiComboBox.js"; diff --git a/packages/main/src/Slider.hbs b/packages/main/src/Slider.hbs new file mode 100644 index 000000000000..eea8490a5ea0 --- /dev/null +++ b/packages/main/src/Slider.hbs @@ -0,0 +1,11 @@ +{{>include "./SliderBase.hbs"}} + +{{#*inline "handles"}} +
+{{/inline}} \ No newline at end of file diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js new file mode 100644 index 000000000000..f73adaea270e --- /dev/null +++ b/packages/main/src/Slider.js @@ -0,0 +1,226 @@ +import Float from "@ui5/webcomponents-base/dist/types/Float.js"; +import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; +import SliderBase from "./SliderBase.js"; + +// Template +import SliderTemplate from "./generated/templates/SliderTemplate.lit.js"; + +/** + * @public + */ +const metadata = { + tag: "ui5-slider", + languageAware: true, + managedSlots: true, + properties: /** @lends sap.ui.webcomponents.main.Slider.prototype */ { + /** + * Current value of the slider + * + * @type {Float} + * @defaultvalue 0 + * @public + */ + value: { + type: Float, + defaultValue: 0, + }, + }, +}; + +/** + * @class + * + *ui5-slider
component 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:
+ * import "@ui5/webcomponents/dist/Slider";
+ *
+ * @constructor
+ * @author SAP SE
+ * @alias sap.ui.webcomponents.main.Slider
+ * @extends sap.ui.webcomponents.base.UI5Element
+ * @tagname ui5-slider
+ * @since 1.0.0-rc.11
+ * @appenddocs SliderBase
+ * @public
+ */
+class Slider extends SliderBase {
+ static get metadata() {
+ return metadata;
+ }
+
+ static get template() {
+ return SliderTemplate;
+ }
+
+ constructor() {
+ super();
+ this._stateStorage.value = null;
+ this.i18nBundle = getI18nBundle("@ui5/webcomponents");
+ }
+
+ /**
+ *
+ * Check if the previously saved state is outdated. That would mean
+ * either it is the initial rendering or that a property has been changed
+ * programatically - because the previous state is always updated in
+ * the interaction handlers.
+ *
+ * Normalize current properties, update the previously stored state.
+ * Update the visual UI representation of the Slider
+ *
+ */
+ onBeforeRendering() {
+ if (!this.isCurrentStateOutdated()) {
+ return;
+ }
+
+ this.notResized = true;
+ this.syncUIAndState("value");
+ this._updateHandleAndProgress(this.value);
+ }
+
+ /**
+ * Called when the user starts interacting with the slider
+ *
+ * @private
+ */
+ _onmousedown(event) {
+ // If step is 0 no interaction is available because there is no constant
+ // (equal for all user environments) quantitative representation of the value
+ if (this.disabled || this.step === 0) {
+ return;
+ }
+
+ const newValue = this.handleDownBase(event, this._effectiveMin, this._effectiveMax);
+
+ // Do not yet update the Slider if press is over a handle. It will be updated if the user drags the mouse.
+ if (!this._isHandlePressed(this.constructor.getPageXValueFromEvent(event))) {
+ this._updateHandleAndProgress(newValue);
+ this.updateValue("value", newValue);
+ }
+ }
+
+ /**
+ * Called when the user moves the slider
+ *
+ * @private
+ */
+ _handleMove(event) {
+ event.preventDefault();
+
+ // If step is 0 no interaction is available because there is no constant
+ // (equal for all user environments) quantitative representation of the value
+ if (this.disabled || this._effectiveStep === 0) {
+ return;
+ }
+
+ const newValue = this.constructor.getValueFromInteraction(event, this._effectiveStep, this._effectiveMin, this._effectiveMax, this.getBoundingClientRect(), this.directionStart);
+
+ this._updateHandleAndProgress(newValue);
+ this.updateValue("value", newValue);
+ }
+
+ /** Called when the user finish interacting with the slider
+ *
+ * @private
+ */
+ _handleUp(event) {
+ this.handleUpBase();
+ }
+
+ /** Determines if the press is over the handle
+ *
+ * @private
+ */
+ _isHandlePressed(clientX) {
+ const sliderHandle = this.shadowRoot.querySelector(".ui5-slider-handle");
+ const sliderHandleDomRect = sliderHandle.getBoundingClientRect();
+
+ return clientX >= sliderHandleDomRect.left && clientX <= sliderHandleDomRect.right;
+ }
+
+
+ /** Updates the UI representation of the progress bar and handle position
+ *
+ * @private
+ */
+ _updateHandleAndProgress(newValue) {
+ const max = this._effectiveMax;
+ const min = this._effectiveMin;
+
+ // The progress (completed) percentage of the slider.
+ this._progressPercentage = (newValue - min) / (max - min);
+ // How many pixels from the left end of the slider will be the placed the affected by the user action handle
+ this._handlePositionFromStart = this._progressPercentage * 100;
+ }
+
+ get styles() {
+ return {
+ progress: {
+ "transform": `scaleX(${this._progressPercentage})`,
+ "transform-origin": `${this.directionStart} top`,
+ },
+ handle: {
+ [this.directionStart]: `${this._handlePositionFromStart}%`,
+ },
+ tickmarks: {
+ "background": `${this._tickmarks}`,
+ },
+ label: {
+ "width": `${this._labelWidth}%`,
+ },
+ labelContainer: {
+ "width": `100%`,
+ [this.directionStart]: `-${this._labelWidth / 2}%`,
+ },
+ tooltip: {
+ "visibility": `${this._tooltipVisibility}`,
+ },
+ };
+ }
+
+ get labelItems() {
+ return this._labelItems;
+ }
+
+ get tooltipValue() {
+ const stepPrecision = this.constructor._getDecimalPrecisionOfNumber(this._effectiveStep);
+ return this.value.toFixed(stepPrecision);
+ }
+
+ static async onDefine() {
+ await fetchI18nBundle("@ui5/webcomponents");
+ }
+}
+
+Slider.define();
+
+export default Slider;
diff --git a/packages/main/src/SliderBase.hbs b/packages/main/src/SliderBase.hbs
new file mode 100644
index 000000000000..d06d8436734f
--- /dev/null
+++ b/packages/main/src/SliderBase.hbs
@@ -0,0 +1,30 @@
+
+
+{{#*inline "handles"}}{{/inline}}
diff --git a/packages/main/src/SliderBase.js b/packages/main/src/SliderBase.js
new file mode 100644
index 000000000000..43db710dfd6b
--- /dev/null
+++ b/packages/main/src/SliderBase.js
@@ -0,0 +1,659 @@
+import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js";
+import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js";
+import Float from "@ui5/webcomponents-base/dist/types/Float.js";
+import Integer from "@ui5/webcomponents-base/dist/types/Integer.js";
+import ResizeHandler from "@ui5/webcomponents-base/dist/delegate/ResizeHandler.js";
+import { getTheme } from "@ui5/webcomponents-base/dist/config/Theme.js";
+
+// Styles
+import styles from "./generated/themes/SliderBase.css.js";
+
+/**
+ * @public
+ */
+const metadata = {
+ properties: /** @lends sap.ui.webcomponents.main.SliderBase.prototype */ {
+ /**
+ * Defines the minimum value of the slider
+ *
+ * @type {Float}
+ * @defaultvalue 0
+ * @public
+ */
+ min: {
+ type: Float,
+ defaultValue: 0,
+ },
+ /**
+ * Defines the maximum value of the slider
+ *
+ * @type {Float}
+ * @defaultvalue 100
+ * @public
+ */
+ max: {
+ type: Float,
+ defaultValue: 100,
+ },
+ /**
+ * 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).
+ * ui5-range-slider
.
+ * showTooltip
property is set to true
+ *
+ * @private
+ */
+ _onmouseover(event) {
+ if (this.showTooltip) {
+ this._tooltipVisibility = "visible";
+ }
+ }
+
+ /**
+ * Hides the tooltip(s) if the showTooltip
property is set to true
+ *
+ * @private
+ */
+ _onmouseout(event) {
+ if (this.showTooltip) {
+ this._tooltipVisibility = "hidden";
+ }
+ }
+
+ /**
+ * Handle the responsiveness of the Slider's UI elements when resizing
+ *
+ * @private
+ */
+ _handleResize() {
+ if (!this.showTickmarks) {
+ return;
+ }
+
+ // Mark resizing to avoid unneccessary calls to that function after rendering
+ this.notResized = false;
+
+ // Convert the string represented calculation expression to a normal one
+ // Check the distance in pixels exist between every tickmark
+ const spaceBetweenTickmarks = this._spaceBetweenTickmarks();
+
+ // If the pixels between the tickmarks are less than 8 only the first and the last one should be visible
+ // In such case the labels must correspond to the tickmarks, only the first and the last one should exist.
+ if (spaceBetweenTickmarks < SliderBase.MIN_SPACE_BETWEEN_TICKMARKS) {
+ this._hiddenTickmarks = true;
+ this._labelsOverlapping = true;
+ } else {
+ this._hiddenTickmarks = false;
+ }
+
+ if (this.labelInterval <= 0 || this._hiddenTickmarks) {
+ return;
+ }
+
+
+ // Check if there are any overlapping labels.
+ // If so - only the first and the last one should be visible
+ const labelItems = this.shadowRoot.querySelectorAll(".ui5-slider-labels li");
+ this._labelsOverlapping = [...labelItems].some(label => label.scrollWidth > label.clientWidth);
+ }
+
+ /**
+ * Called when the user starts interacting with the slider.
+ * After a down event on the slider root, listen for move events on window, so the slider value
+ * is updated even if the user drags the pointer outside the slider root.
+ *
+ * @protected
+ */
+ handleDownBase(event, min, max) {
+ // Only allow one type of move event to be listened to (the first one registered after the down event)
+ this._moveEventType = !this._moveEventType ? SliderBase.MOVE_EVENT_MAP[event.type] : this._moveEventType;
+
+ SliderBase.UP_EVENTS.forEach(upEventType => window.addEventListener(upEventType, this._upHandler));
+ window.addEventListener(this._moveEventType, this._moveHandler);
+
+ this._boundingClientRect = this.getBoundingClientRect();
+ const newValue = SliderBase.getValueFromInteraction(event, this.step, min, max, this._boundingClientRect, this.directionStart);
+
+ this._valueOnInteractionStart = newValue;
+ return newValue;
+ }
+
+ /**
+ * Called when the user finish interacting with the slider
+ * Fires an change
event indicating a final value change, after user interaction is finished.
+ *
+ * @protected
+ */
+ handleUpBase() {
+ if (this._valueOnInteractionStart !== this.value) {
+ this.fireEvent("change");
+ }
+
+ SliderBase.UP_EVENTS.forEach(upEventType => window.removeEventListener(upEventType, this._upHandler));
+ window.removeEventListener(this._moveEventType, this._moveHandler);
+
+ this._moveEventType = null;
+ this._valueOnInteractionStart = null;
+ }
+
+ /**
+ * Updates value property of the component that has been changed due to a user action.
+ * Fires an input
event indicating a value change via interaction that is not yet finished.
+ *
+ * @protected
+ */
+ updateValue(valueType, value) {
+ this[valueType] = value;
+ this.storePropertyState(valueType);
+ this.fireEvent("input");
+ }
+
+ /**
+ * Locks the given value between min and max boundaries based on slider properties
+ *
+ * @protected
+ */
+ static clipValue(value, min, max) {
+ value = Math.min(Math.max(value, min), max);
+ return value;
+ }
+
+ /**
+ * Sets the slider value from an event
+ *
+ * @protected
+ */
+ static getValueFromInteraction(event, stepSize, min, max, boundingClientRect, directionStart) {
+ const pageX = this.getPageXValueFromEvent(event);
+ const value = this.computedValueFromPageX(pageX, min, max, boundingClientRect, directionStart);
+ const steppedValue = this.getSteppedValue(value, stepSize, min);
+
+ return this.clipValue(steppedValue, min, max);
+ }
+
+ /**
+ * "Stepify" the raw value - calculate the new value depending on the specified step property
+ *
+ * @protected
+ */
+ static getSteppedValue(value, stepSize, min) {
+ const stepModuloValue = Math.abs((value - min) % stepSize);
+
+ if (stepSize === 0 || stepModuloValue === 0) {
+ return value;
+ }
+
+ // Clip (snap) the new value to the nearest step
+ value = (stepModuloValue * 2 >= stepSize) ? (value + stepSize) - stepModuloValue : value - stepModuloValue;
+
+ // If the step value is not a round number get its precision
+ const stepPrecision = SliderBase._getDecimalPrecisionOfNumber(stepSize);
+ return value.toFixed(stepPrecision);
+ }
+
+ /**
+ * Gets pageX value from event on user interaction with the Slider
+ *
+ * @protected
+ */
+ static getPageXValueFromEvent(event) {
+ if (event.targetTouches && event.targetTouches.length > 0) {
+ return event.targetTouches[0].pageX;
+ }
+
+ return event.pageX;
+ }
+
+ /**
+ * Computes the new value (in %) from the pageX position of the cursor.
+ * Returns the value with rounded to a precision of at most 2 digits after decimal point.
+ *
+ * @protected
+ */
+ static computedValueFromPageX(pageX, min, max, boundingClientRect, directionStart) {
+ // Determine pageX position relative to the Slider DOM
+ const xRelativePosition = directionStart === "left" ? pageX - boundingClientRect[directionStart] : boundingClientRect[directionStart] - pageX;
+ // Calculate the percentage complete (the "progress")
+ const percentageComplete = xRelativePosition / boundingClientRect.width;
+ // Fit (map) the complete percentage between the min/max value range
+ return min + percentageComplete * (max - min);
+ }
+
+ /**
+ * Calculates the precision (decimal places) of a number, returns 0 if integer
+ * Handles scientific notation cases.
+ * @private
+ */
+ static _getDecimalPrecisionOfNumber(value) {
+ if (Number.isInteger(value)) {
+ return 0;
+ }
+ const match = (String(value)).match(/(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/);
+ return Math.max(0, (match[1] ? match[1].length : 0) - (match[2] ? Number(match[2]) : 0));
+ }
+
+ /**
+ * Normalize current properties, update the previously stored state.
+ *
+ * @protected
+ */
+ syncUIAndState(...values) {
+ // Validate step and update the stored state for the step property.
+ if (this.isPropertyUpdated("step")) {
+ this._validateStep(this.step);
+ this.storePropertyState("step");
+ }
+
+ // Recalculate the tickmarks and labels and update the stored state.
+ if (this.isPropertyUpdated("min", "max", ...values)) {
+ this.storePropertyState("min", "max");
+ this._createLabels();
+
+ // Here the value props are changed programatically (not by user interaction)
+ // and it won't be "stepified" (rounded to the nearest step). 'Clip' them within
+ // min and max bounderies and update the previous state reference.
+ values.forEach(valueType => {
+ const normalizedValue = SliderBase.clipValue(this[valueType], this._effectiveMin, this._effectiveMax);
+ this.updateValue(valueType, normalizedValue);
+ this.storePropertyState(valueType);
+ });
+ }
+ }
+
+ /**
+ * In order to always keep the visual UI representation and the internal
+ * state in sync, the component has a 'state storage' that is updated when the
+ * current state is changed due to a user action.
+ *
+ * Check if the previously saved state is outdated. That would mean
+ * a property has been changed programatically because the previous state
+ * is always updated in the interaction handlers.
+ *
+ * Will return true if any of the properties is not equal to its previously
+ * stored value.
+ *
+ * @protected
+ */
+ isCurrentStateOutdated() {
+ return Object.entries(this._stateStorage).some(([propName, propValue]) => this[propName] !== propValue);
+ }
+
+ /**
+ * Returns the last stored value of a property
+ *
+ * @protected
+ */
+ getStoredPropertyState(property) {
+ return this._stateStorage[property];
+ }
+
+ /**
+ * Check if one or more properties have been updated compared to their last
+ * saved values in the state storage.
+ *
+ * @protected
+ */
+ isPropertyUpdated(...properties) {
+ return properties.some(prop => this.getStoredPropertyState(prop) !== this[prop]);
+ }
+
+ /**
+ * Updates the previously saved in the _stateStorage values of one or more properties.
+ *
+ * @protected
+ */
+ storePropertyState(...props) {
+ props.forEach(property => {
+ this._stateStorage[property] = this[property];
+ });
+ }
+
+ /**
+ * Returns the start side of a direction - left for LTR, right for RTL
+ *
+ * @protected
+ */
+ get directionStart() {
+ return this.effectiveDir === "rtl" ? "right" : "left";
+ }
+
+ /**
+ * Calculates and draws the tickmarks with a CSS gradient style
+ *
+ * @private
+ */
+ get _tickmarks() {
+ if (!this.showTickmarks || !this._effectiveStep) {
+ return;
+ }
+
+ if (this._hiddenTickmarks) {
+ return `linear-gradient(to right, currentColor 1px, transparent 0) 0 center / calc(100% - 1px) 100% repeat-x`;
+ }
+
+ // Convert number values to strings to let the CSS do calculations better
+ // rounding/subpixel behavior" and the most precise tickmarks distribution
+ const maxStr = String(this._effectiveMax);
+ const minStr = String(this._effectiveMin);
+ const stepStr = String(this._effectiveStep);
+ const tickmarkWidth = "1px";
+
+ // There is a CSS bug with the 'currentcolor' value of a CSS gradient that does not
+ // respect the variable for more than one theme. It has to be set here for now.
+ const currentTheme = getTheme();
+ const currentColor = SliderBase.TICKMARK_COLOR_MAP[currentTheme];
+
+ this._tickmarksAmount = `${maxStr - minStr} / ${stepStr}`;
+ this._hiddenTickmarks = false;
+
+ // Transparent CSS gradient background
+ const tickmarksGradientBase = `linear-gradient(to right, ${currentColor} ${tickmarkWidth}, transparent 0) `;
+
+ // Draw the tickmarks as a patern over the gradient background
+ const tickmarksGradientdPattern = `0 center / calc((100% - ${tickmarkWidth}) / (${this._tickmarksAmount})) 100% repeat-x`;
+
+ // Combine to get the complete CSS background gradient property value
+ return `${tickmarksGradientBase + tickmarksGradientdPattern}`;
+ }
+
+ /**
+ * Calculates the labels amout, width and text and creates them
+ *
+ * @private
+ */
+ _createLabels() {
+ if (!this.labelInterval || !this.showTickmarks) {
+ return;
+ }
+
+ const labelInterval = this.labelInterval;
+ const step = this._effectiveStep;
+ const newNumberOfLabels = (this._effectiveMax - this._effectiveMin) / (step * labelInterval);
+
+ // If the required labels are already rendered
+ if (newNumberOfLabels === this._oldNumberOfLabels) {
+ return;
+ }
+
+ this._oldNumberOfLabels = newNumberOfLabels;
+ this._labelWidth = 100 / newNumberOfLabels;
+ this._labelValues = [];
+
+ // If the step value is not a round number get its precision
+ const stepPrecision = SliderBase._getDecimalPrecisionOfNumber(step);
+
+ // numberOfLabels below can be float so that the "distance betweenlabels labels"
+ // calculation to be precize (exactly the same as the distance between the tickmarks).
+ // That's ok as the loop stop condition is set to an integer, so it will practically
+ // "floor" the number of labels anyway.
+ for (let i = 0; i <= newNumberOfLabels; i++) {
+ // Format the label numbers with the same decimal precision as the value of the step property
+ const labelItemNumber = ((i * step * labelInterval) + this._effectiveMin).toFixed(stepPrecision);
+ this._labelValues.push(labelItemNumber);
+ }
+ }
+
+ get _labels() {
+ return this._labelValues || [];
+ }
+
+ /**
+ * Calculates space between tickmarks
+ *
+ * @private
+ */
+ _spaceBetweenTickmarks() {
+ const tickmarksAmountStrCalc = this._tickmarksAmount.split("/");
+ const tickmarksAmount = tickmarksAmountStrCalc[0] / tickmarksAmountStrCalc[1];
+
+ return this.getBoundingClientRect().width / tickmarksAmount;
+ }
+
+ /**
+ * Notify in case of a invalid step value type
+ *
+ * @private
+ */
+ _validateStep(step) {
+ if (step === 0) {
+ console.warn("The 'step' property must be a positive float number"); // eslint-disable-line
+ }
+
+ if (step < 0) {
+ console.warn("The 'step' property must be a positive float number. The provided negative number has been converted to its positve equivalent"); // eslint-disable-line
+ }
+
+ if (typeof step !== "number" || Number.isNaN(step)) {
+ console.warn("The 'step' property must be a positive float number. It has been set to its default value of 1"); // eslint-disable-line
+ }
+ }
+
+ /**
+ * Normalizes a new step
property value.
+ * If tickmarks are enabled recreates them according to it.
+ *
+ * @private
+ */
+ get _effectiveStep() {
+ let step = this.step;
+
+ if (step === 0) {
+ return;
+ }
+
+ if (step < 0) {
+ step = Math.abs(step);
+ }
+
+ if (typeof step !== "number" || Number.isNaN(step)) {
+ step = 1;
+ }
+
+ return step;
+ }
+
+ get _effectiveMin() {
+ return Math.min(this.min, this.max);
+ }
+
+ get _effectiveMax() {
+ return Math.max(this.min, this.max);
+ }
+}
+
+export default SliderBase;
diff --git a/packages/main/src/themes/SliderBase.css b/packages/main/src/themes/SliderBase.css
new file mode 100644
index 000000000000..dd46d1ae22c9
--- /dev/null
+++ b/packages/main/src/themes/SliderBase.css
@@ -0,0 +1,127 @@
+:host([disabled]) {
+ opacity: var(--_ui5_slider_disabled_opacity);
+ cursor: default;
+ pointer-events: none;
+}
+
+:host {
+ box-sizing: border-box;
+ cursor: pointer;
+ vertical-align: top;
+ width: 100%;
+ height: 3.3125rem;
+}
+
+:host(:not([hidden])) {
+ display: inline-block;
+}
+
+.ui5-slider-root {
+ box-sizing: border-box;
+ height: 3.3125rem;
+ padding: 1rem 0;
+ touch-action: none;
+ -ms-touch-action: pan-y;
+}
+
+.ui5-slider-inner {
+ background-repeat: no-repeat;
+ position: relative;
+ min-width: var(--_ui5_slider_inner_min_width);
+ height: 100%;
+}
+
+.ui5-slider-progress-container {
+ width: 100%;
+ background: var(--_ui5_slider_progress_container_background);
+ border: var(--_ui5_slider_progress_border);
+ border-radius: var(--_ui5_slider_progress_border_radius);
+ height: var(--_ui5_slider_inner_height);
+}
+
+.ui5-slider-progress {
+ background: var(--_ui5_slider_progress_background);
+ border-radius: var(--_ui5_slider_progress_border_radius);
+ height: var(--_ui5_slider_inner_height);
+ will-change: transform;
+ position: relative;
+}
+
+.ui5-slider-tickmarks {
+ color: var(--_ui5_slider_tickmark_color);
+ position: absolute;
+ width: 100%;
+ height: 1rem;
+ top: var(--_ui5_slider_tickmark_top);
+ z-index: -1;
+}
+
+.ui5-slider-handle {
+ background: var(--_ui5_slider_handle_background);
+ border: var(--_ui5_slider_handle_border);
+ border-radius: 1rem;
+ margin-left: var(--_ui5_slider_handle_margin_left);
+ top: var(--_ui5_slider_handle_top);
+ position: absolute;
+ outline: none;
+ height: var(--_ui5_slider_handle_height);
+ width: var(--_ui5_slider_handle_width);
+}
+
+[dir="rtl"] .ui5-slider-handle {
+ margin-right: var(--_ui5_slider_handle_margin_left);
+}
+
+.ui5-slider-root:hover .ui5-slider-handle {
+ background: var(--_ui5_slider_handle_hover_background);
+ border-color: var(--_ui5_slider_handle_hover_border);
+}
+
+.ui5-slider-tooltip {
+ text-align: center;
+ visibility: hidden;
+ pointer-events: none;
+ line-height: 1rem;
+ position: absolute;
+ left: 50%;
+ transform: translate(-50%);
+ bottom: var(--_ui5_slider_tooltip_bottom);
+ background: var(--_ui5_slider_tooltip_background);
+ border: 1px solid var(--_ui5_slider_tooltip_border_color);
+ border-radius: var(--_ui5_slider_tooltip_border_radius);
+ box-shadow: var(--_ui5_slider_tooltip_box_shadow);
+ font-size: var(--_ui5_slider_tooltip_fontsize);
+ color: var(--_ui5_slider_tooltip_color);
+ height: var(--_ui5_slider_tooltip_height);
+ min-width: var(--_ui5_slider_tooltip_min_width);
+ padding: var(--_ui5_slider_tooltip_padding);
+}
+
+.ui5-slider-tooltip-value {
+ position: relative;
+}
+
+
+.ui5-slider-labels {
+ position: absolute;
+ top: 1rem;
+ margin: 0;
+ padding: 0;
+ white-space: nowrap;
+}
+
+.ui5-slider-labels li {
+ position: relative;
+ list-style: none;
+ padding-top: 0.3125rem;
+ height: 1rem;
+ margin: 0;
+ text-align: center;
+ display: inline-block;
+ color: var(--_ui5_slider_label_color);
+ font-size: var(--_ui5_slider_label_fontsize);
+}
+
+.ui5-slider-hidden-labels li:not(:first-child):not(:last-child) {
+ visibility: hidden;
+}
\ No newline at end of file
diff --git a/packages/main/src/themes/base/SliderBase-parameters.css b/packages/main/src/themes/base/SliderBase-parameters.css
new file mode 100644
index 000000000000..6f4c45a78e11
--- /dev/null
+++ b/packages/main/src/themes/base/SliderBase-parameters.css
@@ -0,0 +1,31 @@
+
+:root {
+ --_ui5_slider_progress_container_background: var(--sapField_BorderColor);
+ --_ui5_slider_progress_border: none;
+ --_ui5_slider_inner_height: 0.25rem;
+ --_ui5_slider_progress_border_radius: 0.25rem;
+ --_ui5_slider_progress_background: var(--sapActiveColor);
+ --_ui5_slider_handle_height: 1.25rem;
+ --_ui5_slider_handle_width: 1.25rem;
+ --_ui5_slider_handle_border: solid 0.125rem var(--sapField_BorderColor);
+ --_ui5_slider_handle_background: var(--sapButton_Background);
+ --_ui5_slider_handle_top: -0.6425rem;
+ --_ui5_slider_handle_margin_left: -0.8125rem;
+ --_ui5_slider_handle_hover_background: var(--sapButton_Hover_Background);
+ --_ui5_slider_handle_hover_border: var(--sapButton_Hover_BorderColor);
+ --_ui5_slider_tickmark_color: #89919a;
+ --_ui5_slider_tickmark_top: -0.375rem;
+ --_ui5_slider_disabled_opacity: 0.4;
+ --_ui5_slider_tooltip_fontsize: var(--sapFontSmallSize);
+ --_ui5_slider_tooltip_color: var(--sapContent_LabelColor);
+ --_ui5_slider_tooltip_background: var(--sapField_Background);
+ --_ui5_slider_tooltip_border_radius: var(--sapElement_BorderCornerRadius);
+ --_ui5_slider_tooltip_border_color: var(--sapField_BorderColor);
+ --_ui5_slider_tooltip_padding: 0.25rem;
+ --_ui5_slider_tooltip_height: 1rem;
+ --_ui5_slider_tooltip_box_shadow: none;
+ --_ui5_slider_tooltip_min_width: 2rem;
+ --_ui5_slider_tooltip_bottom: 1.825rem;
+ --_ui5_slider_label_fontsize: var(--sapFontSmallSize);
+ --_ui5_slider_label_color: var(--sapContent_LabelColor);
+}
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_belize/SliderBase-parameters.css b/packages/main/src/themes/sap_belize/SliderBase-parameters.css
new file mode 100644
index 000000000000..ee915f1b023f
--- /dev/null
+++ b/packages/main/src/themes/sap_belize/SliderBase-parameters.css
@@ -0,0 +1,22 @@
+@import "../base/SliderBase-parameters.css";
+
+:root {
+ --_ui5_slider_progress_container_background: #dddddd;
+ --_ui5_slider_inner_height: 0.1875rem;
+ --_ui5_slider_progress_border-radius: 0;
+ --_ui5_slider_progress_background: var(--sapField_Active_BorderColor);
+ --_ui5_slider_handle_height: 1.375rem;
+ --_ui5_slider_handle_width: 1.375rem;
+ --_ui5_slider_handle_border: solid 0.125rem #999;
+ --_ui5_slider_handle_background: rgba(var(--sapField_Background), 0.1);
+ --_ui5_slider_handle_top: -0.725rem;
+ --_ui5_slider_handle_margin_left: -1rem;
+ --_ui5_slider_handle_hover_background: rgba(217,217,217,0.6);
+ --_ui5_slider_handle_hover_border: #999;
+ --_ui5_slider_tickmark_color: #bfbfbf;
+ --_ui5_slider_disabled_opacity: 0.5;
+ --_ui5_slider_tooltip_padding: 0.25rem 0.50rem;
+ --_ui5_slider_tooltip_height: 1.325rem;
+ --_ui5_slider_tooltip_box_shadow: 0 0.625rem 2rem 0 rgba(0, 0, 0, 0.15);
+ --_ui5_slider_tooltip_border_radius: 0;
+}
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_belize/parameters-bundle.css b/packages/main/src/themes/sap_belize/parameters-bundle.css
index 4ac39a6d34fe..0dcf4f9c025b 100644
--- a/packages/main/src/themes/sap_belize/parameters-bundle.css
+++ b/packages/main/src/themes/sap_belize/parameters-bundle.css
@@ -37,3 +37,4 @@
@import "../base/Token-parameters.css";
@import "../base/ValueStateMessage-parameters.css";
@import "../base/MultiComboBox-parameters.css";
+@import "./SliderBase-parameters.css";
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_belize_hcb/SliderBase-parameters.css b/packages/main/src/themes/sap_belize_hcb/SliderBase-parameters.css
new file mode 100644
index 000000000000..84eeec102db5
--- /dev/null
+++ b/packages/main/src/themes/sap_belize_hcb/SliderBase-parameters.css
@@ -0,0 +1,17 @@
+@import "../base/SliderBase-parameters.css";
+
+:root {
+ --_ui5_slider_inner_height: 0.375rem;
+ --_ui5_slider_progress_border: solid 0.0625rem var(--sapField_BorderColor);
+ --_ui5_slider_progress_border_radius: 0.125rem;
+ --_ui5_slider_handle_background: transparent;
+ --_ui5_slider_handle_hover_background: var(--sapField_Hover_Background);
+ --_ui5_slider_handle_border: solid 0.125rem var(--sapField_BorderColor);
+ --_ui5_slider_handle_hover_border: var(--sapField_Active_BorderColor);
+ --_ui5_slider_handle_height: 1.375rem;
+ --_ui5_slider_handle_width: 1.375rem;
+ --_ui5_slider_handle_top: -0.6rem;
+ --_ui5_slider_handle_margin_left: -0.775rem;
+ --_ui5_slider_progress_background: var(--sapField_Background);
+ --_ui5_slider_tickmark_top: -0.2525rem;
+}
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_belize_hcb/parameters-bundle.css b/packages/main/src/themes/sap_belize_hcb/parameters-bundle.css
index 57cb6d9e3f28..c69fde3e793f 100644
--- a/packages/main/src/themes/sap_belize_hcb/parameters-bundle.css
+++ b/packages/main/src/themes/sap_belize_hcb/parameters-bundle.css
@@ -36,3 +36,4 @@
@import "./Token-parameters.css";
@import "./ValueStateMessage-parameters.css";
@import "../base/MultiComboBox-parameters.css";
+@import "./SliderBase-parameters.css";
diff --git a/packages/main/src/themes/sap_belize_hcw/SliderBase-parameters.css b/packages/main/src/themes/sap_belize_hcw/SliderBase-parameters.css
new file mode 100644
index 000000000000..47004923e26b
--- /dev/null
+++ b/packages/main/src/themes/sap_belize_hcw/SliderBase-parameters.css
@@ -0,0 +1,17 @@
+@import "../base/SliderBase-parameters.css";
+
+:root {
+ --_ui5_slider_inner_height: 0.375rem;
+ --_ui5_slider_progress_border: solid 0.0625rem var(--sapField_BorderColor);
+ --_ui5_slider_progress_border_radius: 0.125rem;
+ --_ui5_slider_handle_background: transparent;
+ --_ui5_slider_handle_hover_background: var(--sapField_Hover_Background);
+ --_ui5_slider_handle_border: solid 0.125rem var(--sapField_BorderColor);
+ --_ui5_slider_handle_hover_border: var(--sapField_Active_BorderColor);
+ --_ui5_slider_handle_height: 1.375rem;
+ --_ui5_slider_handle_width: 1.375rem;
+ --_ui5_slider_handle_top: -0.6rem;
+ --_ui5_slider_handle_margin_left: -0.775rem;
+ --_ui5_slider_progress_background: var(--sapField_Background);
+ --_ui5_slider_tickmark_top: -0.2525rem;
+}
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_belize_hcw/parameters-bundle.css b/packages/main/src/themes/sap_belize_hcw/parameters-bundle.css
index 0326a6927e9e..205e9c1c6e95 100644
--- a/packages/main/src/themes/sap_belize_hcw/parameters-bundle.css
+++ b/packages/main/src/themes/sap_belize_hcw/parameters-bundle.css
@@ -36,3 +36,4 @@
@import "./Token-parameters.css";
@import "./ValueStateMessage-parameters.css";
@import "../base/MultiComboBox-parameters.css";
+@import "./SliderBase-parameters.css";
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_fiori_3/SliderBase-parameters.css b/packages/main/src/themes/sap_fiori_3/SliderBase-parameters.css
new file mode 100644
index 000000000000..c343957767d2
--- /dev/null
+++ b/packages/main/src/themes/sap_fiori_3/SliderBase-parameters.css
@@ -0,0 +1,5 @@
+@import "../base/SliderBase-parameters.css";
+
+:root {
+ --_ui5_slider_inner_min_width: 4rem;
+}
diff --git a/packages/main/src/themes/sap_fiori_3/parameters-bundle.css b/packages/main/src/themes/sap_fiori_3/parameters-bundle.css
index a61c0311bf43..60a95952a9a3 100644
--- a/packages/main/src/themes/sap_fiori_3/parameters-bundle.css
+++ b/packages/main/src/themes/sap_fiori_3/parameters-bundle.css
@@ -37,3 +37,4 @@
@import "./Token-parameters.css";
@import "../base/ValueStateMessage-parameters.css";
@import "./MultiComboBox-parameters.css";
+@import "./SliderBase-parameters.css";
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_fiori_3_dark/SliderBase-parameters.css b/packages/main/src/themes/sap_fiori_3_dark/SliderBase-parameters.css
new file mode 100644
index 000000000000..9a82413460dc
--- /dev/null
+++ b/packages/main/src/themes/sap_fiori_3_dark/SliderBase-parameters.css
@@ -0,0 +1,5 @@
+@import "../base/SliderBase-parameters.css";
+
+:root {
+ --_ui5_slider_inner_min_width: 4rem;
+}
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_fiori_3_dark/parameters-bundle.css b/packages/main/src/themes/sap_fiori_3_dark/parameters-bundle.css
index 90ff6fd177e7..55221c1b9cb3 100644
--- a/packages/main/src/themes/sap_fiori_3_dark/parameters-bundle.css
+++ b/packages/main/src/themes/sap_fiori_3_dark/parameters-bundle.css
@@ -36,3 +36,4 @@
@import "./Token-parameters.css";
@import "../base/ValueStateMessage-parameters.css";
@import "./MultiComboBox-parameters.css";
+@import "./SliderBase-parameters.css";
diff --git a/packages/main/src/themes/sap_fiori_3_hcb/SliderBase-parameters.css b/packages/main/src/themes/sap_fiori_3_hcb/SliderBase-parameters.css
new file mode 100644
index 000000000000..b8bab664439c
--- /dev/null
+++ b/packages/main/src/themes/sap_fiori_3_hcb/SliderBase-parameters.css
@@ -0,0 +1,14 @@
+@import "../base/SliderBase-parameters.css";
+
+:root {
+ --_ui5_slider_inner_height: 0.375rem;
+ --_ui5_slider_progress_border: solid 0.0625rem var(--sapField_BorderColor);
+ --_ui5_slider_progress_border_radius: 0.375rem;
+ --_ui5_slider_handle_hover_background: var(--sapButton_Hover_Background);
+ --_ui5_slider_handle_border: solid 0.125rem var(--sapField_BorderColor);
+ --_ui5_slider_handle_height: 1.65rem;
+ --_ui5_slider_handle_width: 1.65rem;
+ --_ui5_slider_handle_top: -0.75rem;
+ --_ui5_slider_progress_background: var(--sapSelectedColor);
+ --_ui5_slider_tickmark_top: -0.2525rem;
+}
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_fiori_3_hcb/parameters-bundle.css b/packages/main/src/themes/sap_fiori_3_hcb/parameters-bundle.css
index d3156fd4f7e9..60c684019532 100644
--- a/packages/main/src/themes/sap_fiori_3_hcb/parameters-bundle.css
+++ b/packages/main/src/themes/sap_fiori_3_hcb/parameters-bundle.css
@@ -37,3 +37,4 @@
@import "./Token-parameters.css";
@import "./ValueStateMessage-parameters.css";
@import "../base/MultiComboBox-parameters.css";
+@import "./SliderBase-parameters.css";
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_fiori_3_hcw/SliderBase-parameters.css b/packages/main/src/themes/sap_fiori_3_hcw/SliderBase-parameters.css
new file mode 100644
index 000000000000..8f33d9666405
--- /dev/null
+++ b/packages/main/src/themes/sap_fiori_3_hcw/SliderBase-parameters.css
@@ -0,0 +1,13 @@
+@import "../base/SliderBase-parameters.css";
+
+:root {
+ --_ui5_slider_inner_height: 0.375rem;
+ --_ui5_slider_progress_border: solid 0.0625rem var(--sapField_BorderColor);
+ --_ui5_slider_progress_border_radius: 0.375rem;
+ --_ui5_slider_handle_border: solid 0.125rem var(--sapField_BorderColor);
+ --_ui5_slider_handle_height: 1.65rem;
+ --_ui5_slider_handle_width: 1.65rem;
+ --_ui5_slider_handle_top: -0.75rem;
+ --_ui5_slider_progress_background: var(--sapSelectedColor);
+ --_ui5_slider_tickmark_top: -0.2525rem;
+}
\ No newline at end of file
diff --git a/packages/main/src/themes/sap_fiori_3_hcw/parameters-bundle.css b/packages/main/src/themes/sap_fiori_3_hcw/parameters-bundle.css
index d3156fd4f7e9..60c684019532 100644
--- a/packages/main/src/themes/sap_fiori_3_hcw/parameters-bundle.css
+++ b/packages/main/src/themes/sap_fiori_3_hcw/parameters-bundle.css
@@ -37,3 +37,4 @@
@import "./Token-parameters.css";
@import "./ValueStateMessage-parameters.css";
@import "../base/MultiComboBox-parameters.css";
+@import "./SliderBase-parameters.css";
\ No newline at end of file
diff --git a/packages/main/test/pages/Slider.html b/packages/main/test/pages/Slider.html
new file mode 100644
index 000000000000..7035fd0578c5
--- /dev/null
+++ b/packages/main/test/pages/Slider.html
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+ ++ +
++ +
++ +
++ +