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"}} +
+ {{#if showTooltip}} +
+ {{tooltipValue}} +
+ {{/if}} +
+{{/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 + * + *

Overview

+ * The Slider component represents a numerical range and a handle (grip). + * The purpose of the component is to enable visual selection of a value in + * a continuous numerical range by moving an adjustable handle. + * + *

Structure

+ * The most important properties of the Slider are: + * + * + *

Usage

+ * The most common usecase is to select values on a continuous numerical scale (e.g. temperature, volume, etc. ). + * + *

Responsive Behavior

+ * The 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: + * + * + *

ES6 Module Import

+ * + * 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 @@ +
+
+
+
+
+ + {{#if step}} + {{#if showTickmarks}} +
+ {{#if labelInterval}} + + {{/if}} + {{/if}} + {{/if}} + {{> handles}} +
+
+ +{{#*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). + *

+ * Note: If set to 0 the slider handle movement is disabled. When negative number or value other than a number, the component fallbacks to its default value. + * + * @type {Integer} + * @defaultvalue 1 + * @public + */ + step: { + type: Float, + defaultValue: 1, + }, + /** + * Displays a label with a value on every N-th step. + *

+ * Note: The step and tickmarks properties must be enabled. + * Example - if the step value is set to 2 and the label interval is also specified to 2 - then every second + * tickmark will be labelled, which means every 4th value number. + * + * @type {Integer} + * @defaultvalue 0 + * @public + */ + labelInterval: { + type: Integer, + defaultValue: 0, + }, + /** + * Enables tick marks visualization for each step. + *

+ * Note: The step must be a positive number. + * + * @type {boolean} + * @defaultvalue false + * @public + */ + showTickmarks: { + type: Boolean, + }, + /** + * Enables handle tooltip displaying the current value. + * + * @type {boolean} + * @defaultvalue false + * @public + */ + showTooltip: { + type: Boolean, + }, + /** + * Defines whether the slider is in disabled state. + * + * @type {boolean} + * @defaultvalue false + * @public + */ + disabled: { + type: Boolean, + }, + /** + * @private + */ + _tooltipVisibility: { + type: String, + defaultValue: "hidden", + }, + _labelsOverlapping: { + type: Boolean, + }, + _hiddenTickmarks: { + type: Boolean, + }, + }, + slots: /** @lends sap.ui.webcomponents.main.SliderBase.prototype */ { + /** + * Defines the text of the ui5-range-slider. + *
Note: Although this slot accepts HTML Elements, it is strongly recommended that you only use text in order to preserve the intended design. + * + * @type {Node[]} + * @slot + * @public + */ + "default": { + type: Node, + }, + }, + events: /** @lends sap.ui.webcomponents.main.SliderBase.prototype */ { + /** + * Fired when the value changes and the user has finished interacting with the slider. + * + * @event + * @public + */ + change: {}, + /** + * Fired when the value changes due to user interaction that is not yet finished - during mouse/touch dragging. + * + * @event + * @public + */ + input: {}, + }, +}; + +/** + * @class + * + *

Overview

+ * + * + * @constructor + * @author SAP SE + * @alias sap.ui.webcomponents.main.SliderBase + * @extends sap.ui.webcomponents.base.UI5Element + * @tagname ui5-slider + * @public + */ +class SliderBase extends UI5Element { + constructor() { + super(); + this._resizeHandler = this._handleResize.bind(this); + this._moveHandler = this._handleMove.bind(this); + this._upHandler = this._handleUp.bind(this); + + this._stateStorage = { + step: null, + min: null, + max: null, + }; + } + + static get metadata() { + return metadata; + } + + static get render() { + return litRender; + } + + static get styles() { + return styles; + } + + static get TICKMARK_COLOR_MAP() { + return { + sap_fiori_3: "#89919a", + sap_fiori_3_dark: "#89919a", + sap_fiori_3_hcw: "#000000", + sap_fiori_3_hcb: "#ffffff", + sap_belize: "#bfbfbf", + sap_belize_hcw: "#000000", + sap_belize_hcb: "#ffffff", + }; + } + + static get UP_EVENTS() { + return ["mouseup", "touchend"]; + } + + static get MOVE_EVENT_MAP() { + return { + mousedown: "mousemove", + touchstart: "touchmove", + }; + } + + static get MIN_SPACE_BETWEEN_TICKMARKS() { + return 8; + } + + get classes() { + return { + labelContainer: { + "ui5-slider-hidden-labels": this._labelsOverlapping, + }, + }; + } + + onEnterDOM() { + ResizeHandler.register(this, this._resizeHandler); + } + + onExitDOM() { + ResizeHandler.deregister(this, this._handleResize); + } + + onAfterRendering() { + // Only call if the resize is triggered by a state changes other than + // the ones that occured on the previous resize and those caused by user interaction. + if (this.notResized) { + this._resizeHandler(); + } + } + + _ontouchstart(event) { + this._onmousedown(event); + } + + /** Shows the tooltip(s) if the 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 @@ + + + + + + + + UI5 Slider + + + + + + + + + + + + + + + + +
+

Basic Slider

+ + +

Basic Slider with tooltip

+ + +

Disabled Slider with tickmarks and labels

+ + +

Slider with tickmarks

+ + +

Slider with many steps and small width

+ + + +

Inactive slider with no steps and tooltip

+ + +

Slider with steps, tooltips, tickmarks and labels

+ + +

Slider with float number step (1.25), tooltips, tickmarks and labels between 3 steps (3.75 value)

+ +
+ + diff --git a/packages/main/test/samples/Slider.sample.html b/packages/main/test/samples/Slider.sample.html new file mode 100644 index 000000000000..76661a7de7f8 --- /dev/null +++ b/packages/main/test/samples/Slider.sample.html @@ -0,0 +1,53 @@ +
+

Slider

+
+ +
+
+ +
@ui5/webcomponents
+ +
<ui5-slider>
+ +
+

Basic Slider

+
+ +
+ +

+<ui5-slider></ui5-slider>
+	
+
+ +
+

Slider with Tooltip

+
+ +
+

+<ui5-slider min="0" max="20" show-tooltip></ui5-slider>
+	
+
+ +
+

Disabled Slider with Tickmarks and Labels

+
+ +
+

+<ui5-slider min="20" max="100" label-interval="5" disabled show-tickmarks></ui5-slider>
+	
+
+ +
+

Slider tooltip, tickmarks and labels

+
+ +
+

+<ui5-slider min="-20" max="20" step="2" value="12" show-tooltip label-interval="2" show-tickmarks></ui5-slider>
+	
+
+ + diff --git a/packages/main/test/specs/Slider.spec.js b/packages/main/test/specs/Slider.spec.js new file mode 100644 index 000000000000..e5894a588a46 --- /dev/null +++ b/packages/main/test/specs/Slider.spec.js @@ -0,0 +1,151 @@ +const assert = require("chai").assert; + +describe("Slider basic interactions", () => { + + it("Changing the current value is reflected", () => { + browser.url("http://localhost:8080/test-resources/pages/Slider.html"); + + const slider = browser.$("#basic-slider"); + const sliderHandle = slider.shadow$(".ui5-slider-handle"); + + assert.strictEqual(sliderHandle.getAttribute("style"), "left: 0%;", "Initially if no value is set, the Slider handle is at the beginning of the Slider"); + + browser.setWindowSize(1257, 2000); + slider.setProperty("value", 3); + + assert.strictEqual(sliderHandle.getAttribute("style"), "left: 30%;", "Slider handle should be 30% from the start"); + + slider.click(); + + assert.strictEqual(sliderHandle.getAttribute("style"), "left: 50%;", "Slider handle should be in the middle of the slider"); + assert.strictEqual(slider.getProperty("value"), 5, "Slider current value should be 5"); + + sliderHandle.dragAndDrop({ x: 300, y: 1 }); + + assert.strictEqual(sliderHandle.getAttribute("style"), "left: 80%;", "Slider handle should be 80% from the start of the slider"); + assert.strictEqual(slider.getProperty("value"), 8, "Slider current value should be 8"); + + sliderHandle.dragAndDrop({ x: 100, y: 1 }); + + assert.strictEqual(sliderHandle.getAttribute("style"), "left: 90%;", "Slider handle should be 90% from the start"); + assert.strictEqual(slider.getProperty("value"), 9, "Slider current value should be 9"); + + sliderHandle.dragAndDrop({ x:-100, y: 1 }); + + assert.strictEqual(sliderHandle.getAttribute("style"), "left: 80%;", "Slider handle should be at the end of the slider and not beyond its boundaries"); + assert.strictEqual(slider.getProperty("value"), 8, "Slider current value should be 8"); + }); + + it("Slider with floating min, max and step property", () => { + const slider = browser.$("#basic-slider"); + + slider.setProperty("min", -12.5); + slider.setProperty("max", 47.5); + slider.setProperty("step", 1.25); + slider.setProperty("value", 21.25); + + slider.click({ x: -100 }); + assert.strictEqual(slider.getProperty("value"), 12.5, "Slider value should be lowered to 12.5"); + }); + + it("Slider should not be interactive if the step property is 0", () => { + const slider = browser.$("#inactive-slider"); + + slider.click(); + + assert.strictEqual(slider.getProperty("value"), 0, "Slider with 0 step should still has its default value of 0"); + }); + + it("Disabled slider is not interactive", () => { + const slider = browser.$("#disabled-slider-with-tickmarks"); + + assert.strictEqual(slider.isClickable(), false, "Range Slider should be disabled"); + }); +}); + + +describe("Slider elements - tooltip, step, tickmarks, labels", () => { + + it("Slider Tooltip is displayed showing the current value", () => { + const slider = browser.$("#basic-slider-with-tooltip"); + const sliderTooltip = slider.shadow$(".ui5-slider-tooltip"); + const sliderHandle = slider.shadow$(".ui5-slider-handle"); + const sliderTooltipValue = slider.shadow$(".ui5-slider-tooltip-value"); + + slider.moveTo(); + + assert.strictEqual(slider.getProperty("_tooltipVisibility"), "visible", "Slider tooltip visibility property should be 'visible'"); + assert.strictEqual(sliderTooltip.getAttribute("style"), "visibility: visible;", "Slider tooltip should be shown"); + + sliderHandle.dragAndDrop({ x: 100, y: 1 }); + + assert.strictEqual(sliderTooltipValue.getText(), "2", "Slider tooltip should display value of 2"); + }); + + it("Slider have correct number of labels and tickmarks based on the defined step and labelInterval properties", () => { + const slider = browser.$("#slider-tickmarks-tooltips-labels"); + const labelsContainer = slider.shadow$(".ui5-slider-labels"); + const numberOfLabels = labelsContainer.$$("li").length; + + assert.strictEqual(numberOfLabels, 17, "17 labels should be rendered, 1 between each 3 tickmarks"); + }); +}); + +describe("Properties synchronization and normalization", () => { + it("Should not 'stepify' current value if it is not in result of user interaction", () => { + const slider = browser.$("#tickmarks-slider"); + + slider.setProperty("value", 13); + + assert.strictEqual(slider.getProperty("value"), 13, "current value should not be stepped to the next step (14)"); + }); +}); + +describe("Testing resize handling and RTL support", () => { + it("Testing RTL support", () => { + const slider = browser.$("#basic-slider"); + const sliderHandle = slider.shadow$(".ui5-slider-handle"); + + slider.setAttribute("dir", "rtl"); + slider.setProperty("min", 0); + slider.setProperty("max", 10); + slider.setProperty("step", 1); + slider.setProperty("value", 0); + + assert.strictEqual(sliderHandle.getAttribute("style"), "right: 0%;", "Initially if no value is set, the Slider handle is at the right of the Slider"); + + slider.setProperty("value", 3); + + assert.strictEqual(sliderHandle.getAttribute("style"), "right: 30%;", "Slider handle should be 30% from the right"); + + slider.moveTo(); + slider.click(); + + assert.strictEqual(sliderHandle.getAttribute("style"), "right: 50%;", "Slider handle should be in the middle of the slider"); + assert.strictEqual(slider.getProperty("value"), 5, "Slider current value should be 5"); + + sliderHandle.dragAndDrop({ x: -300, y: 1 }); + + assert.strictEqual(sliderHandle.getAttribute("style"), "right: 80%;", "Slider handle should be 80% from the right of the slider"); + assert.strictEqual(slider.getProperty("value"), 8, "Slider current value should be 8"); + + sliderHandle.dragAndDrop({ x: -100, y: 1 }); + + assert.strictEqual(sliderHandle.getAttribute("style"), "right: 90%;", "Slider handle should be 90% from the right"); + assert.strictEqual(slider.getProperty("value"), 9, "Slider current value should be 9"); + + sliderHandle.dragAndDrop({ x: -150, y: 1 }); + + assert.strictEqual(sliderHandle.getAttribute("style"), "right: 100%;", "Slider handle should be at the left of the slider and not beyond its boundaries"); + assert.strictEqual(slider.getProperty("value"), 10, "Slider current value should be 10"); + }); + + it("Should hide all labels except the first and the last one, if there is not enough space for all of them", () => { + const slider = browser.$("#slider-tickmarks-tooltips-labels"); + + browser.setWindowSize(400, 2000); + + assert.strictEqual(slider.getProperty("_labelsOverlapping"), true, "state should reflect if any of the labels is overlapping with another"); + assert.strictEqual(slider.getProperty("_hiddenTickmarks"), true, "state should reflect if the tickmarks has less than 8px space between each of them"); + }); +}); \ No newline at end of file diff --git a/packages/theme-base/css-vars-usage.json b/packages/theme-base/css-vars-usage.json index 3e04c65da29a..2edfcb613e23 100644 --- a/packages/theme-base/css-vars-usage.json +++ b/packages/theme-base/css-vars-usage.json @@ -9,6 +9,7 @@ "--sapAccentColor8", "--sapAccentColor9", "--sapAccentColor10", + "--sapActiveColor", "--sapActiveColor_Lighten3", "--sapBackgroundColor", "--sapBlockLayer_Background", @@ -85,8 +86,10 @@ "--sapCriticalColor", "--sapCriticalElementColor", "--sapCriticalTextColor", + "--sapElement_BorderCornerRadius", "--sapErrorBackground", "--sapErrorBorderColor", + "--sapField_Active_BorderColor", "--sapField_Background", "--sapField_BorderColor", "--sapField_BorderWidth",