From 92d3e956ad30c79229b0d4893a00ab5712519213 Mon Sep 17 00:00:00 2001 From: Deshev Date: Wed, 7 Oct 2020 08:42:20 +0300 Subject: [PATCH 01/52] Initial Slider POC essentials --- packages/main/src/Slider.hbs | 17 + packages/main/src/Slider.js | 361 ++++++++++++++++++ packages/main/src/themes/Slider.css | 107 ++++++ .../src/themes/base/Slider-parameters.css | 5 + packages/main/test/pages/Slider.html | 49 +++ 5 files changed, 539 insertions(+) create mode 100644 packages/main/src/Slider.hbs create mode 100644 packages/main/src/Slider.js create mode 100644 packages/main/src/themes/Slider.css create mode 100644 packages/main/src/themes/base/Slider-parameters.css create mode 100644 packages/main/test/pages/Slider.html diff --git a/packages/main/src/Slider.hbs b/packages/main/src/Slider.hbs new file mode 100644 index 000000000000..8dee3c1747c3 --- /dev/null +++ b/packages/main/src/Slider.hbs @@ -0,0 +1,17 @@ +
+
+
+
+
+
+
+
+ +
\ 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..200653b882df --- /dev/null +++ b/packages/main/src/Slider.js @@ -0,0 +1,361 @@ +import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; +import Integer from "@ui5/webcomponents-base/dist/types/Integer.js"; +import Float from "@ui5/webcomponents-base/dist/types/Float.js"; +import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; + +// Template +import SliderTemplate from "./generated/templates/SliderTemplate.lit.js"; + +// Styles +import SliderStyles from "./generated/themes/Slider.css.js"; + +/** + * @public + */ +const metadata = { + tag: "ui5-slider", + altTag: "ui5-slider", + languageAware: true, + managedSlots: true, + properties: /** @lends sap.ui.webcomponents.main.Slider.prototype */ { + /** + * Minimum value of the slider + *

+ * + * @type {Float} + * @defaultvalue 0 + * @public + */ + min: { + type: Float, + }, + /** + * Maximum value of the slider + *

+ * + * @type {Float} + * @defaultvalue 100 + * @public + */ + max: { + type: Float, + }, + /** + * 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). + * When 0 or a negative number, the component fallbacks to its default value. + *

+ * + * @type {Integer} + * @defaultvalue 1 + * @public + */ + step: { + type: Float, + defaultValue: 1, + }, + /** + * Current value of the slider + *

+ * + * @type {Float} + * @defaultvalue 0 + * @public + */ + value: { + type: Float, + }, + /** + * Determines end point of a selection - position of a second handle on the slider. Allow defining of a selected range. + * When endValue is specified, the value property is used as a "start value" to determine a selected range on the slider. + * If the values is lower/higher than the allowed minimum/maximum, they will be set to the corresponding min/max values of the slider. + *

+ * + * @type {Float} + * @defaultvalue [0, 0] + * @public + */ + endValue: { + type: Float, + }, + /** + * Enables tick marks visualization for each step. The step value must not be set to 0 + *

+ * + * @type {boolean} + * @defaultvalue false + * @public + */ + tickmarks: { + type: Boolean, + }, + /** + * Defines whether the ui5-slider is in disabled state. + *

+ * + * @type {boolean} + * @defaultvalue false + * @public + */ + disabled: { + type: Boolean + }, + }, + slots: /** @lends sap.ui.webcomponents.main.Slider.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.Slider.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. + * + * @event + * @public + */ + input: {}, + }, +}; + +/** + * @class + * + *

Overview

+ * + * The ui5-label is a component used to represent a label, + * providing valuable information to the user. + * Usually it is placed next to a value holder, such as a text field. + * It informs the user about what data is displayed or expected in the value holder. + *

+ * The ui5-label appearance can be influenced by properties, + * such as required and wrap. + * The appearance of the Label can be configured in a limited way by using the design property. + * For a broader choice of designs, you can use custom styles. + * + *

ES6 Module Import

+ * + * import "@ui5/webcomponents/dist/Label"; + * + * @constructor + * @author SAP SE + * @alias sap.ui.webcomponents.main.Slider + * @extends sap.ui.webcomponents.base.UI5Element + * @tagname ui5-range-slider + * @public + */ +class Slider extends UI5Element { + static get metadata() { + return metadata; + } + + static get render() { + return litRender; + } + + static get template() { + return SliderTemplate; + } + + static get styles() { + return SliderStyles; + } + + static async onDefine() { + await Promise.all([ + fetchI18nBundle("@ui5/webcomponents"), + ]); + } + + constructor() { + super(); + this.i18nBundle = getI18nBundle("@ui5/webcomponents"); + } + + _handleChange() { + + } + + _handleInput() { + + } + + // TODO Include touch events + /** + * Called when the user starts interacting with the slider + */ + _handleDown(event) { + let _this = this; + + if (this.disabled) { + return; + } + + this.boundingDOMRect = this.getBoundingClientRect(); + const moveHandler = function (moveEvent) { + _this._handleMove(moveEvent); + }; + const upHandler = function () { + _this._handleUp(); + _this.removeEventListener("mouseup", upHandler); + document.body.removeEventListener("mousemove", moveHandler); + }; + + // After a down event on the slider root, listen for move events on + // body, so the slider value is updated even if the user drags the pointer + // outside the slider root + document.body.addEventListener("mousemove", moveHandler); + document.body.addEventListener("mouseup", upHandler); + + this._calculateValueFromInteraction(event); + } + + /** + * Called when the user moves the slider + * @private + */ + _handleMove(event) { + event.preventDefault(); + console.warn("move"); + + this._calculateValueFromInteraction(event); + }; + + _handleUp() { + console.warn("up"); + }; + + /** + * Sets the slider value from an event + */ + _calculateValueFromInteraction(event) { + let pageX = this._getPageXValueFromEvent(event); + let value = this._computeValueFromPageX(pageX); + this._setValue(value, true); + }; + + /** + * Gets pageX value from event on user interaction with the Slider + */ + _getPageXValueFromEvent(event) { + if (event.targetTouches && event.targetTouches.length > 0) { + return event.targetTouches[0].pageX; + } + return event.pageX; + } + + _setValue(value) { + let min = this.min, max = this.max, step = this.step; + + // "Stepihfy" the raw value - calculate a step value + if (this.step !== 0) { + let numSteps = Math.round(value / this.step); + value = numSteps * this.step; + } + + // Normalize value + if (value < min) { + value = min; + } + else if (value > max) { + value = max; + } + + this.value = value; + this._updateUI(); + } + + /** + * Computes the new value (in %) from the pageX position of the cursor + */ + _computeValueFromPageX(pageX) { + let max = this.max, min = this.min; + + // Determine pageX position relative to the Slider DOM + let xPositionRelative = pageX - this.boundingDOMRect.left; + // Calculate the percentage complete (the "progress") + let percentageComplete = xPositionRelative / this.boundingDOMRect.width; + + //TODO RTL support? + + // Fit (map) the complete percentage between the min/max value range + return min + percentageComplete * (max - min); + }; + + _updateUI() { + let max = this.max, min = this.min, value = this.value; + let percentageComplete = (value - min) / (max - min); + let translatePx = percentageComplete * this.boundingDOMRect.width; + + // Update the position of the handle depending on the value + // Center the Slider Handle position under the cursor/pointer + this.shadowRoot.querySelector(".ui5-slider-handle").style.setProperty("left", translatePx + "px"); + this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("transform", "scaleX(" + percentageComplete + ")"); + }; + + /** + * Calculates and draws the tickmarks with a CSS gradient style + */ + drawDefaultTickmarks(step, max, min) { + const stepStr = String(step); + const maxStr = String(max); + const minStr = String(min); + + // Calculate how many tickmarks have to be drawn (max - min / stepValue) + const tickmarksAmount = `${maxStr - minStr} / ${stepStr}`; + + // TODO* Make it themable? + const tickmarkWidth = "1px"; + + // 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}) / (${tickmarksAmount})) 100% repeat-x`; + + // Combine to get the complete CSS background gradient property value + const tickmarksBackground = `${tickmarksGradientBase + tickmarksGradientdPattern}`; + + // Apply the style to the container + this.shadowRoot.querySelector(".ui5-slider-tickmarks").style.setProperty('background', tickmarksBackground); + }; + + setStep(step) { + if (typeof step !== 'number' || step < 0) { + step = 1; + } + + this.step = step; + }; + + onBeforeRendering() { + if (this.step !== 1) { + this.setStep(this.step) + } + }; + + onAfterRendering() { + if (this.step && this.tickmarks) { + this.drawDefaultTickmarks(this.step, this.max, this.min); + } + }; + +} + +Slider.define(); + +export default Slider; diff --git a/packages/main/src/themes/Slider.css b/packages/main/src/themes/Slider.css new file mode 100644 index 000000000000..0636a3622465 --- /dev/null +++ b/packages/main/src/themes/Slider.css @@ -0,0 +1,107 @@ +:host(:not([hidden])) { + display: inline-block; +} + + +:host { + cursor: pointer; + vertical-align: top; + position: relative; + width: 100%; + z-index: 0; +} + +.ui5-slider-root { + box-sizing: border-box; + height: 2rem; + padding: 1rem 0; + +} + +.ui5-slider-inner { + width: 100%; + position: relative; + min-width: 4rem; + height: 2rem; +} + +.ui5-slider-progress-container { + width: 100%; + position: relative; + background-repeat: no-repeat; + z-index: 1; + height: 0.25rem; + + background: #89919a; + border-radius: 0.25rem; +} + +.ui5-slider-progress { + background: #0854a0; + border-radius: 0.25rem; + position: absolute; + width: 100%; + height: 100%; + transform: scaleX(0); + transform-origin: left top; + will-change: transform; +} + + +.ui5-slider-progress:before { + content: ''; + position: absolute; + width: 100%; + height: 500%; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); +} + + +.ui5-slider-tickmarks { + /* margin: 0; + padding: 0; + list-style: none; + position: absolute; + left: 1.0625rem; + right: 1.0625rem;; + display: block; + height: 1.75rem; + z-index: 0; */ + color: #89919a; + position: absolute; + width: 100%; + height: 1rem; + top: -0.35rem; +} + +.ui5-slider-marker { + /* display: inline-block; + height: 1rem; + border-left: 0.0625rem solid #000; + margin: 0.4375rem 0 0 -0.0625rem; */ +} + +.ui5-slider-handle { + margin-left: -1rem; + top: -0.91rem; + height: 1.75rem; + width: 1.75rem; + direction: ltr; + border-radius: 1rem; + background: #FFF; + border: solid 0.125rem #89919a; + opacity: 0.5; + position: absolute; +} + +.ui5-slider-handle:before { + content: ''; + position: absolute; + width: 100%; + left: 50%; + top: 40%; + transform: translate(-50%, -50%); + cursor: pointer; +} \ No newline at end of file diff --git a/packages/main/src/themes/base/Slider-parameters.css b/packages/main/src/themes/base/Slider-parameters.css new file mode 100644 index 000000000000..403c50043cd2 --- /dev/null +++ b/packages/main/src/themes/base/Slider-parameters.css @@ -0,0 +1,5 @@ + +:root { + --_ui5_range-slider_base_min_width: 12.25rem; + --_ui5_range-slider_base_padding: 1.406rem 1.0625rem; +} diff --git a/packages/main/test/pages/Slider.html b/packages/main/test/pages/Slider.html new file mode 100644 index 000000000000..3c2be4cf70da --- /dev/null +++ b/packages/main/test/pages/Slider.html @@ -0,0 +1,49 @@ + + + + + + + + Label + + + + + + + + + + + + + + + + +
+

Basic Slider

+ +

Slider with tickmarks

+ +

Slider with many steps and small width

+ +

Smooth slider with no steps

+ +
+ + From c67f585fc3ec400b0b8dfd2e688647be3b454efb Mon Sep 17 00:00:00 2001 From: Deshev Date: Wed, 7 Oct 2020 16:51:59 +0300 Subject: [PATCH 02/52] Minor formatting fixes --- packages/main/bundle.esm.js | 1 + packages/main/src/Slider.js | 102 +++++++++++++-------------- packages/main/src/themes/Slider.css | 11 +-- packages/main/test/pages/Slider.html | 2 +- 4 files changed, 58 insertions(+), 58 deletions(-) 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.js b/packages/main/src/Slider.js index 200653b882df..ea9af16cf28c 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -1,6 +1,5 @@ import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; import litRender from "@ui5/webcomponents-base/dist/renderer/LitRenderer.js"; -import Integer from "@ui5/webcomponents-base/dist/types/Integer.js"; import Float from "@ui5/webcomponents-base/dist/types/Float.js"; import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; @@ -98,7 +97,7 @@ const metadata = { * @public */ disabled: { - type: Boolean + type: Boolean, }, }, slots: /** @lends sap.ui.webcomponents.main.Slider.prototype */ { @@ -137,18 +136,6 @@ const metadata = { * *

Overview

* - * The ui5-label is a component used to represent a label, - * providing valuable information to the user. - * Usually it is placed next to a value holder, such as a text field. - * It informs the user about what data is displayed or expected in the value holder. - *

- * The ui5-label appearance can be influenced by properties, - * such as required and wrap. - * The appearance of the Label can be configured in a limited way by using the design property. - * For a broader choice of designs, you can use custom styles. - * - *

ES6 Module Import

- * * import "@ui5/webcomponents/dist/Label"; * * @constructor @@ -199,17 +186,18 @@ class Slider extends UI5Element { * Called when the user starts interacting with the slider */ _handleDown(event) { - let _this = this; + const _this = this; if (this.disabled) { return; } this.boundingDOMRect = this.getBoundingClientRect(); - const moveHandler = function (moveEvent) { + const moveHandler = moveEvent => { _this._handleMove(moveEvent); }; - const upHandler = function () { + + const upHandler = () => { _this._handleUp(); _this.removeEventListener("mouseup", upHandler); document.body.removeEventListener("mousemove", moveHandler); @@ -230,23 +218,23 @@ class Slider extends UI5Element { */ _handleMove(event) { event.preventDefault(); - console.warn("move"); + // console.warn("move"); this._calculateValueFromInteraction(event); - }; + } _handleUp() { - console.warn("up"); - }; + // console.warn("up"); + } /** * Sets the slider value from an event */ _calculateValueFromInteraction(event) { - let pageX = this._getPageXValueFromEvent(event); - let value = this._computeValueFromPageX(pageX); + const pageX = this._getPageXValueFromEvent(event); + const value = this._computeValueFromPageX(pageX); this._setValue(value, true); - }; + } /** * Gets pageX value from event on user interaction with the Slider @@ -257,56 +245,60 @@ class Slider extends UI5Element { } return event.pageX; } - + _setValue(value) { - let min = this.min, max = this.max, step = this.step; + const min = this.min; + const max = this.max; + const step = this.step; // "Stepihfy" the raw value - calculate a step value if (this.step !== 0) { - let numSteps = Math.round(value / this.step); - value = numSteps * this.step; + const numSteps = Math.round(value / step); + value = numSteps * step; } // Normalize value if (value < min) { value = min; - } - else if (value > max) { + } else if (value > max) { value = max; } - this.value = value; this._updateUI(); + // this.debounce(() => this.value = value, 100); } /** * Computes the new value (in %) from the pageX position of the cursor */ _computeValueFromPageX(pageX) { - let max = this.max, min = this.min; + const max = this.max; + const min = this.min; // Determine pageX position relative to the Slider DOM - let xPositionRelative = pageX - this.boundingDOMRect.left; + const xPositionRelative = pageX - this.boundingDOMRect.left; // Calculate the percentage complete (the "progress") - let percentageComplete = xPositionRelative / this.boundingDOMRect.width; + const percentageComplete = xPositionRelative / this.boundingDOMRect.width; - //TODO RTL support? + // TODO RTL support? // Fit (map) the complete percentage between the min/max value range return min + percentageComplete * (max - min); - }; - + } + _updateUI() { - let max = this.max, min = this.min, value = this.value; - let percentageComplete = (value - min) / (max - min); - let translatePx = percentageComplete * this.boundingDOMRect.width; + const max = this.max; + const min = this.min; + const value = this.value; + const percentageComplete = (value - min) / (max - min); + const translatePx = percentageComplete * this.boundingDOMRect.width; // Update the position of the handle depending on the value // Center the Slider Handle position under the cursor/pointer - this.shadowRoot.querySelector(".ui5-slider-handle").style.setProperty("left", translatePx + "px"); - this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("transform", "scaleX(" + percentageComplete + ")"); - }; - + this.shadowRoot.querySelector(".ui5-slider-handle").style.setProperty("left", `${translatePx}px`); + this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("transform", `scaleX(${percentageComplete})`); + } + /** * Calculates and draws the tickmarks with a CSS gradient style */ @@ -331,29 +323,35 @@ class Slider extends UI5Element { const tickmarksBackground = `${tickmarksGradientBase + tickmarksGradientdPattern}`; // Apply the style to the container - this.shadowRoot.querySelector(".ui5-slider-tickmarks").style.setProperty('background', tickmarksBackground); - }; + this.shadowRoot.querySelector(".ui5-slider-tickmarks").style.setProperty("background", tickmarksBackground); + } setStep(step) { - if (typeof step !== 'number' || step < 0) { + if (typeof step !== "number" || step < 0) { step = 1; } - this.step = step; - }; + } onBeforeRendering() { if (this.step !== 1) { - this.setStep(this.step) + this.setStep(this.step); } - }; + } onAfterRendering() { if (this.step && this.tickmarks) { this.drawDefaultTickmarks(this.step, this.max, this.min); } - }; + } + debounce(fn, delay) { + clearTimeout(this.debounceFn); + this.debounceFn = setTimeout(() => { + this.debounceFn = null; + fn(); + }, delay); + } } Slider.define(); diff --git a/packages/main/src/themes/Slider.css b/packages/main/src/themes/Slider.css index 0636a3622465..3096977f2c46 100644 --- a/packages/main/src/themes/Slider.css +++ b/packages/main/src/themes/Slider.css @@ -75,20 +75,21 @@ height: 1rem; top: -0.35rem; } - +/* .ui5-slider-marker { - /* display: inline-block; + display: inline-block; height: 1rem; border-left: 0.0625rem solid #000; - margin: 0.4375rem 0 0 -0.0625rem; */ -} + margin: 0.4375rem 0 0 -0.0625rem; +} +*/ .ui5-slider-handle { margin-left: -1rem; top: -0.91rem; height: 1.75rem; width: 1.75rem; - direction: ltr; + direction: ltr; border-radius: 1rem; background: #FFF; border: solid 0.125rem #89919a; diff --git a/packages/main/test/pages/Slider.html b/packages/main/test/pages/Slider.html index 3c2be4cf70da..eb150da32e0c 100644 --- a/packages/main/test/pages/Slider.html +++ b/packages/main/test/pages/Slider.html @@ -5,7 +5,7 @@ - Label + UI5 Slider From 831da572dcfe0d59f056ce9a490baa3deb6ec2f5 Mon Sep 17 00:00:00 2001 From: Deshev Date: Thu, 8 Oct 2020 09:21:53 +0300 Subject: [PATCH 03/52] RangeSlider functionality improvements --- packages/main/src/Slider.hbs | 12 +- packages/main/src/Slider.js | 198 ++++++++++++++++++++++----- packages/main/src/themes/Slider.css | 20 +-- packages/main/test/pages/Slider.html | 8 +- 4 files changed, 178 insertions(+), 60 deletions(-) diff --git a/packages/main/src/Slider.hbs b/packages/main/src/Slider.hbs index 8dee3c1747c3..4587b0164270 100644 --- a/packages/main/src/Slider.hbs +++ b/packages/main/src/Slider.hbs @@ -10,8 +10,18 @@
-
+ + {{#if tickmarks}} + {{#if step}} +
+ {{/if}} + {{/if}} +
+ {{#if endValue}} +
+ {{/if}} + \ No newline at end of file diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index ea9af16cf28c..818647f97433 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -63,19 +63,21 @@ const metadata = { */ value: { type: Float, + defaultValue: 0, }, /** - * Determines end point of a selection - position of a second handle on the slider. Allow defining of a selected range. - * When endValue is specified, the value property is used as a "start value" to determine a selected range on the slider. + * Determines end point of a selection - position of a second handle on the slider. Allows defining of a selected range. + * When endValue is specified to > 0, the value property is used as a "start value" to determine a selected range on the slider. * If the values is lower/higher than the allowed minimum/maximum, they will be set to the corresponding min/max values of the slider. *

* * @type {Float} - * @defaultvalue [0, 0] + * @defaultvalue 0 * @public */ endValue: { type: Float, + defaultValue: 0, }, /** * Enables tick marks visualization for each step. The step value must not be set to 0 @@ -181,7 +183,7 @@ class Slider extends UI5Element { } - // TODO Include touch events + // TODO Include touch/pointer events, keyboard handling /** * Called when the user starts interacting with the slider */ @@ -192,11 +194,19 @@ class Slider extends UI5Element { return; } - this.boundingDOMRect = this.getBoundingClientRect(); + const oldEndValue = _this.endValue; + const oldStartValue = _this.value; + const clientX = event.clientX != null ? event.clientX : event.targetTouches[0].clientX; + + _this.boundingDOMRect = _this.getBoundingClientRect(); + const newValue = _this._calculateValueFromInteraction(event); + + // In case of Range Slider assign the handle clossest to the press point, otherwise - the single handle's DOM + _this.handle = _this._getClosestHandle(clientX, newValue); + const moveHandler = moveEvent => { _this._handleMove(moveEvent); }; - const upHandler = () => { _this._handleUp(); _this.removeEventListener("mouseup", upHandler); @@ -209,7 +219,17 @@ class Slider extends UI5Element { document.body.addEventListener("mousemove", moveHandler); document.body.addEventListener("mouseup", upHandler); - this._calculateValueFromInteraction(event); + // Do not update Slider if press is in range - only for range sliders (meaning that endValue property is set) + if (oldEndValue && newValue >= oldStartValue && newValue <= oldEndValue) { + return; + } + + this._updateUI(newValue); + if (this.valueAffected === "startValue") { + this._setValue(newValue); + } else { + this._setEndValue(newValue); + } } /** @@ -218,13 +238,54 @@ class Slider extends UI5Element { */ _handleMove(event) { event.preventDefault(); - // console.warn("move"); - this._calculateValueFromInteraction(event); + const value = this._calculateValueFromInteraction(event); + const updateValueAndFireEvent = () => { + if (this.valueAffected === "startValue") { + this._setValue(value); + } else { + this._setEndValue(value); + } + } + + // Update Slider UI in real-time (decoupled with rendering) + this._updateUI(value); + // Prevent re-rendering on every move event fired + this.debounce(updateValueAndFireEvent, 100); } _handleUp() { - // console.warn("up"); + this.fireEvent("change"); + } + + _getClosestHandle(clientX, value) { + const handleStart = this.shadowRoot.querySelector(".ui5-slider-handle"); + const handleEnd = this.shadowRoot.querySelector(".ui5-slider-end-handle"); + + // If the slider is not a range slider return the single handle + if (!this.endValue) { + this.valueAffected = "startValue"; + return handleStart; + } + + // Check if the press point is in the bounds of any handle + const handleStartDomRect = handleStart.getBoundingClientRect(); + const handleEndDomRect = handleEnd.getBoundingClientRect(); + const inHandleStartDom = clientX >= handleStartDomRect.left && clientX <= handleStartDomRect.right; + const inHandleEndDom = clientX >= handleEndDomRect.left && clientX <= handleEndDomRect.right; + + // Return that handle that is closer to the press point + // If the two handles are overlapping return the second (end) one as in general the more common drag move is to the right + if (inHandleEndDom || value > this.endValue) { + this.valueAffected = "endValue"; + return handleEnd; + } + + // If one of the handle is pressed return that one + if (inHandleStartDom || value < this.value) { + this.valueAffected = "startValue"; + return handleStart; + } } /** @@ -232,24 +293,10 @@ class Slider extends UI5Element { */ _calculateValueFromInteraction(event) { const pageX = this._getPageXValueFromEvent(event); - const value = this._computeValueFromPageX(pageX); - this._setValue(value, true); - } - - /** - * Gets pageX value from event on user interaction with the Slider - */ - _getPageXValueFromEvent(event) { - if (event.targetTouches && event.targetTouches.length > 0) { - return event.targetTouches[0].pageX; - } - return event.pageX; - } - - _setValue(value) { const min = this.min; const max = this.max; const step = this.step; + let value = this._computeValueFromPageX(pageX); // "Stepihfy" the raw value - calculate a step value if (this.step !== 0) { @@ -264,13 +311,33 @@ class Slider extends UI5Element { value = max; } - this._updateUI(); - // this.debounce(() => this.value = value, 100); + return value; } + /** + * Gets pageX value from event on user interaction with the Slider + */ + _getPageXValueFromEvent(event) { + if (event.targetTouches && event.targetTouches.length > 0) { + return event.targetTouches[0].pageX; + } + return event.pageX; + } + + _setValue(value) { + this.value = value; + this.fireEvent("input"); + } + + _setEndValue(value) { + this.endValue = value; + this.fireEvent("input"); +} + /** * Computes the new value (in %) from the pageX position of the cursor */ + // TODO RTL support? _computeValueFromPageX(pageX) { const max = this.max; const min = this.min; @@ -279,26 +346,77 @@ class Slider extends UI5Element { const xPositionRelative = pageX - this.boundingDOMRect.left; // Calculate the percentage complete (the "progress") const percentageComplete = xPositionRelative / this.boundingDOMRect.width; - - // TODO RTL support? - // Fit (map) the complete percentage between the min/max value range return min + percentageComplete * (max - min); } - _updateUI() { + // TODO Refactor the 2 functions below + + // Update UI after user interaction + _updateUI(value) { const max = this.max; const min = this.min; - const value = this.value; - const percentageComplete = (value - min) / (max - min); - const translatePx = percentageComplete * this.boundingDOMRect.width; + const startValue = this.value; + const endValue = this.endValue; + + // The value according to which we update the UI can be either the (start) value + // or the endValue property in case of a range. Otherwise just the single "value" prop in case + // specified (the single handle slider case). It is determined in _getClosestHandle() + // depending on to which handle is closer the user interaction. The same goes for the handle. + const handleDom = this.handle; + const sliderDomRect = this.boundingDOMRect; + let percentageComplete = (value - min) / (max - min); + let translateProgressPx = percentageComplete * sliderDomRect.width; + // Update the progress indication width. In the case of a range slider + // Use the already calculated progress of the first handle to determine + // starting point of the progress indicator. + if (this.valueAffected === "startValue") { + this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("transform", `scaleX(${percentageComplete})`); + } else { + let percentageComplete = (endValue - min) / (max - min); + let translateProgressPx = percentageComplete * sliderDomRect.width; + const startProgressPoint = translateProgressPx; + const translateRangeProgressPx = ((value - startValue) - min) / (max - min); + this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("transform", `scaleX(${translateRangeProgressPx})`); + this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("left", `${startProgressPoint}`); + } // Update the position of the handle depending on the value - // Center the Slider Handle position under the cursor/pointer - this.shadowRoot.querySelector(".ui5-slider-handle").style.setProperty("left", `${translatePx}px`); - this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("transform", `scaleX(${percentageComplete})`); + handleDom.style.setProperty("left", `${translateProgressPx}px`); } + // Update Slider UI after entering the DOM + // TODO Refactor this + _initialUISync() { + const max = this.max; + const min = this.min; + const startValue = this.value; + const endValue = this.endValue; + const sliderDomRect = this.getBoundingClientRect(); + + if (!endValue) { + const handleDom = this.shadowRoot.querySelector(".ui5-slider-handle"); + const percentageComplete = (startValue - min) / (max - min); + const translatePx = percentageComplete * sliderDomRect.width; + + // Update the position of the handle depending on the value + // Center the Slider Handle position under the cursor/pointer + handleDom.style.setProperty("left", `${translatePx}px`); + this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("transform", `scaleX(${percentageComplete})`); + } else { + const percentageCompleteSecondHandle = (endValue - min) / (max - min); + const percentageComplete = (startValue - min) / (max - min); + const translatePx = percentageComplete * sliderDomRect.width; + const translatePxSecondHandle = percentageCompleteSecondHandle * sliderDomRect.width; + const rangeProgress = ((endValue - startValue) - min) / (max - min); + this.shadowRoot.querySelector(".ui5-slider-handle").style.setProperty("left", `${translatePx}px`); + this.shadowRoot.querySelector(".ui5-slider-end-handle").style.setProperty("left", `${translatePxSecondHandle}px`); + this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("left", `${translatePx}px`); + this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("transform", `scaleX(${rangeProgress})`); + } + } + + // TODO Allow tickmark labeling? /** * Calculates and draws the tickmarks with a CSS gradient style */ @@ -310,7 +428,7 @@ class Slider extends UI5Element { // Calculate how many tickmarks have to be drawn (max - min / stepValue) const tickmarksAmount = `${maxStr - minStr} / ${stepStr}`; - // TODO* Make it themable? + // TODO* Make it themable const tickmarkWidth = "1px"; // Transparent CSS gradient background @@ -333,6 +451,10 @@ class Slider extends UI5Element { this.step = step; } + onEnterDOM() { + this._initialUISync() + } + onBeforeRendering() { if (this.step !== 1) { this.setStep(this.step); diff --git a/packages/main/src/themes/Slider.css b/packages/main/src/themes/Slider.css index 3096977f2c46..df1e7e17fa64 100644 --- a/packages/main/src/themes/Slider.css +++ b/packages/main/src/themes/Slider.css @@ -75,16 +75,8 @@ height: 1rem; top: -0.35rem; } -/* -.ui5-slider-marker { - display: inline-block; - height: 1rem; - border-left: 0.0625rem solid #000; - margin: 0.4375rem 0 0 -0.0625rem; -} -*/ -.ui5-slider-handle { +.ui5-slider-handle, .ui5-slider-end-handle { margin-left: -1rem; top: -0.91rem; height: 1.75rem; @@ -95,14 +87,4 @@ border: solid 0.125rem #89919a; opacity: 0.5; position: absolute; -} - -.ui5-slider-handle:before { - content: ''; - position: absolute; - width: 100%; - left: 50%; - top: 40%; - transform: translate(-50%, -50%); - cursor: pointer; } \ No newline at end of file diff --git a/packages/main/test/pages/Slider.html b/packages/main/test/pages/Slider.html index eb150da32e0c..dac21a51ba03 100644 --- a/packages/main/test/pages/Slider.html +++ b/packages/main/test/pages/Slider.html @@ -39,11 +39,15 @@

Basic Slider

Slider with tickmarks

- +

Slider with many steps and small width

-

Smooth slider with no steps

+

Smooth Slider with no steps

+

Range Slider with steps and tickmarks

+ +

Smooth Range Slider with no steps

+ From cd7e00d98adaef5dac557a9ebd7c965c25015554 Mon Sep 17 00:00:00 2001 From: Deshev Date: Thu, 8 Oct 2020 12:49:21 +0300 Subject: [PATCH 04/52] Range selection functionality wip --- packages/main/src/Slider.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index 818647f97433..cc6fe3f0165f 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -183,7 +183,7 @@ class Slider extends UI5Element { } - // TODO Include touch/pointer events, keyboard handling + // TODO Include touch/pointer events, keyboard handling, include more comments for better readibility, refactor it, improve formatiting /** * Called when the user starts interacting with the slider */ @@ -225,6 +225,7 @@ class Slider extends UI5Element { } this._updateUI(newValue); + if (this.valueAffected === "startValue") { this._setValue(newValue); } else { From 19549791cc62bb77ef3adfa273f13dd5e7354c0a Mon Sep 17 00:00:00 2001 From: Deshev Date: Fri, 9 Oct 2020 00:39:57 +0300 Subject: [PATCH 05/52] Range Slider concepts implemented --- packages/main/src/Slider.js | 213 ++++++++++++++++++++++-------------- 1 file changed, 130 insertions(+), 83 deletions(-) diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index cc6fe3f0165f..aa525d632de4 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -175,57 +175,45 @@ class Slider extends UI5Element { this.i18nBundle = getI18nBundle("@ui5/webcomponents"); } - _handleChange() { - + _onMouseMove(event) { + this._handleMove(event); } - _handleInput() { - + _onMouseUp() { + this._handleUp(); + window.removeEventListener("mouseup", this._upHandler); + window.removeEventListener("mousemove", this._moveHandler); } - // TODO Include touch/pointer events, keyboard handling, include more comments for better readibility, refactor it, improve formatiting /** * Called when the user starts interacting with the slider */ _handleDown(event) { - const _this = this; - if (this.disabled) { return; } - const oldEndValue = _this.endValue; - const oldStartValue = _this.value; + const oldEndValue = this.endValue; + const oldStartValue = this.value; const clientX = event.clientX != null ? event.clientX : event.targetTouches[0].clientX; - _this.boundingDOMRect = _this.getBoundingClientRect(); - const newValue = _this._calculateValueFromInteraction(event); + this._boundingDOMRect = this.getBoundingClientRect(); + const newValue = this._calculateValueFromInteraction(event); + this._isNewValueInCurrentRange = oldEndValue && newValue >= oldStartValue && newValue <= oldEndValue; // In case of Range Slider assign the handle clossest to the press point, otherwise - the single handle's DOM - _this.handle = _this._getClosestHandle(clientX, newValue); - - const moveHandler = moveEvent => { - _this._handleMove(moveEvent); - }; - const upHandler = () => { - _this._handleUp(); - _this.removeEventListener("mouseup", upHandler); - document.body.removeEventListener("mousemove", moveHandler); - }; + this._sliderHandle = this._correctHandleAndValue(clientX, newValue); // After a down event on the slider root, listen for move events on // body, so the slider value is updated even if the user drags the pointer // outside the slider root - document.body.addEventListener("mousemove", moveHandler); - document.body.addEventListener("mouseup", upHandler); + window.addEventListener("mousemove",this._moveHandler); + window.addEventListener("mouseup", this._upHandler); // Do not update Slider if press is in range - only for range sliders (meaning that endValue property is set) - if (oldEndValue && newValue >= oldStartValue && newValue <= oldEndValue) { - return; - } + if (this._isNewValueInCurrentRange) return; this._updateUI(newValue); - if (this.valueAffected === "startValue") { this._setValue(newValue); } else { @@ -240,7 +228,10 @@ class Slider extends UI5Element { _handleMove(event) { event.preventDefault(); - const value = this._calculateValueFromInteraction(event); + // Do not update Slider if press is in range - only for range sliders (meaning that endValue property is set) + if (this._isNewValueInCurrentRange) return; + + const value = this._clipValue(this._calculateValueFromInteraction(event)); const updateValueAndFireEvent = () => { if (this.valueAffected === "startValue") { this._setValue(value); @@ -248,7 +239,6 @@ class Slider extends UI5Element { this._setEndValue(value); } } - // Update Slider UI in real-time (decoupled with rendering) this._updateUI(value); // Prevent re-rendering on every move event fired @@ -259,7 +249,14 @@ class Slider extends UI5Element { this.fireEvent("change"); } - _getClosestHandle(clientX, value) { + + /** + * Returns the correct handle DOM and sets the value that has to be modified after user interaction + * Returns that handle that is pressed or closer to the press point + * + * Determines which one from the value/endValue properties has to be updated after the user action (based on closest handle) + */ + _correctHandleAndValue(clientX, value) { const handleStart = this.shadowRoot.querySelector(".ui5-slider-handle"); const handleEnd = this.shadowRoot.querySelector(".ui5-slider-end-handle"); @@ -275,6 +272,12 @@ class Slider extends UI5Element { const inHandleStartDom = clientX >= handleStartDomRect.left && clientX <= handleStartDomRect.right; const inHandleEndDom = clientX >= handleEndDomRect.left && clientX <= handleEndDomRect.right; + // Allow updating the slider even if the value is in current range, + // but at the same time the press action is over one of the handles + if (inHandleEndDom || inHandleStartDom) { + this._isNewValueInCurrentRange = false; + } + // Return that handle that is closer to the press point // If the two handles are overlapping return the second (end) one as in general the more common drag move is to the right if (inHandleEndDom || value > this.endValue) { @@ -326,98 +329,142 @@ class Slider extends UI5Element { } _setValue(value) { - this.value = value; - this.fireEvent("input"); + this.value = value; + this.fireEvent("input"); } _setEndValue(value) { this.endValue = value; this.fireEvent("input"); } + /** + * Locks the given value for the given handle between boundaries based on slider properties: + * 1. Restricts value within the min & max properties. + * 2. If range slider, keep start value lower than end value, and the opposite. + * 3. If range slider keep the endValue greater than 0 to prevent removing of the second handle + */ + _clipValue(value) { + value = Math.min(Math.max(value, this.min), this.max); + + // If not a range slider return the value as it is + if (!this.endValue) return value; + + // If the start value is become equal or greater than the endValue + if (this.valueAffected === "startValue" && value > this.endValue) { + return this.valueEnd; + } + + // If the endValue is become equal or less than 1. Do not let + // the end value to become 0 because the second handle will be removed + if (this.valueAffected === "endValue" && value <= 1) { + return 1; + } + + // If the endValue is become equal or less than the start value + if (this.valueAffected === "endValue" && value < this.value) { + return this.value; + } + return value; + } /** * Computes the new value (in %) from the pageX position of the cursor */ - // TODO RTL support? _computeValueFromPageX(pageX) { const max = this.max; const min = this.min; // Determine pageX position relative to the Slider DOM - const xPositionRelative = pageX - this.boundingDOMRect.left; + const xPositionRelative = pageX - this._boundingDOMRect.left; // Calculate the percentage complete (the "progress") - const percentageComplete = xPositionRelative / this.boundingDOMRect.width; + const percentageComplete = xPositionRelative / this._boundingDOMRect.width; // Fit (map) the complete percentage between the min/max value range return min + percentageComplete * (max - min); } - // TODO Refactor the 2 functions below - - // Update UI after user interaction - _updateUI(value) { + // Update UI after user interaction. + _updateUI(newValue) { const max = this.max; const min = this.min; - const startValue = this.value; - const endValue = this.endValue; + const oldStartValue = this.value; + const oldEndValue = this.endValue; + + // The progress (completed) percentage of the slider. In case of a range slider it is the range selection + let percentageComplete; + // How many pixels from the left end of the slider will be the placed the affected by the user action handle + let handlePositionFromLeft; // The value according to which we update the UI can be either the (start) value // or the endValue property in case of a range. Otherwise just the single "value" prop in case // specified (the single handle slider case). It is determined in _getClosestHandle() - // depending on to which handle is closer the user interaction. The same goes for the handle. - const handleDom = this.handle; - const sliderDomRect = this.boundingDOMRect; - let percentageComplete = (value - min) / (max - min); - let translateProgressPx = percentageComplete * sliderDomRect.width; - // Update the progress indication width. In the case of a range slider - // Use the already calculated progress of the first handle to determine - // starting point of the progress indicator. - if (this.valueAffected === "startValue") { - this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("transform", `scaleX(${percentageComplete})`); - } else { - let percentageComplete = (endValue - min) / (max - min); - let translateProgressPx = percentageComplete * sliderDomRect.width; - const startProgressPoint = translateProgressPx; - const translateRangeProgressPx = ((value - startValue) - min) / (max - min); - this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("transform", `scaleX(${translateRangeProgressPx})`); - this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("left", `${startProgressPoint}`); + // depending on to which handle is closer the user interaction. + const sliderHandle = this._sliderHandle; + const sliderDomRect = this._boundingDOMRect; + const sliderProgressBar = this.shadowRoot.querySelector(".ui5-slider-progress"); + + // In case of a range the newValue can be either the value (as a "startValue") or the endValue property + // Update the progress indication width in case of a non-range slider with a single handle + if (this.valueAffected === "startValue" && !oldEndValue) { + percentageComplete = (newValue - min) / (max - min); + handlePositionFromLeft = percentageComplete * sliderDomRect.width; + sliderProgressBar.style.setProperty("transform", `scaleX(${percentageComplete})`); + } else if (this.valueAffected === "startValue") { + // In the case of a range slider when the value changing is the start value: + percentageComplete = (oldEndValue - newValue) / (max - min); + handlePositionFromLeft = ((newValue - min) / (max - min)) * sliderDomRect.width; + sliderProgressBar.style.setProperty("transform", `scaleX(${percentageComplete})`); + sliderProgressBar.style.setProperty("left", `${handlePositionFromLeft}px`); + } + + // If the value modified by the user action is the endValue + if (this.valueAffected === "endValue") { + percentageComplete = ((newValue - oldStartValue) - min) / (max - min); + handlePositionFromLeft = (newValue - min) / (max - min) * sliderDomRect.width; + sliderProgressBar.style.setProperty("transform", `scaleX(${percentageComplete})`); } - // Update the position of the handle depending on the value - handleDom.style.setProperty("left", `${translateProgressPx}px`); + // Update the position of the handle whit the calculated left offset + sliderHandle.style.setProperty("left", `${handlePositionFromLeft}px`); } - // Update Slider UI after entering the DOM - // TODO Refactor this + /** + * Update initial Slider UI representation on entering the DOM + */ _initialUISync() { const max = this.max; const min = this.min; const startValue = this.value; const endValue = this.endValue; const sliderDomRect = this.getBoundingClientRect(); - + const sliderStartHandle = this.shadowRoot.querySelector(".ui5-slider-handle"); + const sliderEndHandle = this.shadowRoot.querySelector(".ui5-slider-end-handle"); + const sliderProgressBar = this.shadowRoot.querySelector(".ui5-slider-progress"); + + // The progress (completed) percentage of the slider. In case of a range slider it is the range selection + let percentageComplete; + // How many pixels from the left end of the slider will be the placed the affected by the user action handle + let handlePositionFromLeft; + + // Update the positions of the handle and the size and position of the progress bar + // Note: In case of a Slider with a single handle the progress (completed) bar width + // is the same as the position of the handle if (!endValue) { - const handleDom = this.shadowRoot.querySelector(".ui5-slider-handle"); - const percentageComplete = (startValue - min) / (max - min); - const translatePx = percentageComplete * sliderDomRect.width; - - // Update the position of the handle depending on the value - // Center the Slider Handle position under the cursor/pointer - handleDom.style.setProperty("left", `${translatePx}px`); - this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("transform", `scaleX(${percentageComplete})`); + percentageComplete = (startValue - min) / (max - min); + handlePositionFromLeft = percentageComplete * sliderDomRect.width; + sliderStartHandle.style.setProperty("left", `${handlePositionFromLeft}px`); + sliderProgressBar.style.setProperty("transform", `scaleX(${percentageComplete})`); } else { - const percentageCompleteSecondHandle = (endValue - min) / (max - min); - const percentageComplete = (startValue - min) / (max - min); - const translatePx = percentageComplete * sliderDomRect.width; - const translatePxSecondHandle = percentageCompleteSecondHandle * sliderDomRect.width; - const rangeProgress = ((endValue - startValue) - min) / (max - min); - this.shadowRoot.querySelector(".ui5-slider-handle").style.setProperty("left", `${translatePx}px`); - this.shadowRoot.querySelector(".ui5-slider-end-handle").style.setProperty("left", `${translatePxSecondHandle}px`); - this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("left", `${translatePx}px`); - this.shadowRoot.querySelector(".ui5-slider-progress").style.setProperty("transform", `scaleX(${rangeProgress})`); + const startHandlePositionFromLeft = (startValue - min) / (max - min) * sliderDomRect.width; + const endHandlePositionFromLeft = (endValue - min) / (max - min) * sliderDomRect.width; + const rangeSelected = ((endValue - startValue) - min) / (max - min); + + sliderStartHandle.style.setProperty("left", `${startHandlePositionFromLeft}px`); + sliderEndHandle.style.setProperty("left", `${endHandlePositionFromLeft}px`); + sliderProgressBar.style.setProperty("left", `${startHandlePositionFromLeft}px`); + sliderProgressBar.style.setProperty("transform", `scaleX(${rangeSelected})`); } } - // TODO Allow tickmark labeling? /** * Calculates and draws the tickmarks with a CSS gradient style */ @@ -428,8 +475,6 @@ class Slider extends UI5Element { // Calculate how many tickmarks have to be drawn (max - min / stepValue) const tickmarksAmount = `${maxStr - minStr} / ${stepStr}`; - - // TODO* Make it themable const tickmarkWidth = "1px"; // Transparent CSS gradient background @@ -453,6 +498,8 @@ class Slider extends UI5Element { } onEnterDOM() { + this._moveHandler = this._onMouseMove.bind(this); + this._upHandler = this._onMouseUp.bind(this); this._initialUISync() } From 415da4e7b18c33bbe8685250c99bfcb1d9e66ff3 Mon Sep 17 00:00:00 2001 From: Deshev Date: Fri, 9 Oct 2020 09:28:14 +0300 Subject: [PATCH 06/52] Most of the comments fixed --- packages/main/src/Slider.hbs | 4 +--- packages/main/src/Slider.js | 46 +++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/main/src/Slider.hbs b/packages/main/src/Slider.hbs index 4587b0164270..e72ef992e81d 100644 --- a/packages/main/src/Slider.hbs +++ b/packages/main/src/Slider.hbs @@ -1,7 +1,5 @@

* * @type {Integer} @@ -124,7 +125,7 @@ const metadata = { */ change: {}, /** - * Fired when the value changes due to user interaction. + * Fired when the value changes due to user interaction that is not yet finished - during mouse/touch dragging. * * @event * @public @@ -175,6 +176,25 @@ class Slider extends UI5Element { this.i18nBundle = getI18nBundle("@ui5/webcomponents"); } + + onEnterDOM() { + this._moveHandler = this._onMouseMove.bind(this); + this._upHandler = this._onMouseUp.bind(this); + this._initialUISync() + } + + onBeforeRendering() { + if (this.step !== 1) { + this.setStep(this.step); + } + } + + onAfterRendering() { + if (this.step && this.tickmarks) { + this.drawDefaultTickmarks(this.step, this.max, this.min); + } + } + _onMouseMove(event) { this._handleMove(event); } @@ -395,9 +415,9 @@ class Slider extends UI5Element { let handlePositionFromLeft; // The value according to which we update the UI can be either the (start) value - // or the endValue property in case of a range. Otherwise just the single "value" prop in case - // specified (the single handle slider case). It is determined in _getClosestHandle() + // or the endValue property in case of a range. It is determined in _getClosestHandle() // depending on to which handle is closer the user interaction. + // Otherwise it's a single "value" prop (the single handle slider case). const sliderHandle = this._sliderHandle; const sliderDomRect = this._boundingDOMRect; const sliderProgressBar = this.shadowRoot.querySelector(".ui5-slider-progress"); @@ -497,24 +517,6 @@ class Slider extends UI5Element { this.step = step; } - onEnterDOM() { - this._moveHandler = this._onMouseMove.bind(this); - this._upHandler = this._onMouseUp.bind(this); - this._initialUISync() - } - - onBeforeRendering() { - if (this.step !== 1) { - this.setStep(this.step); - } - } - - onAfterRendering() { - if (this.step && this.tickmarks) { - this.drawDefaultTickmarks(this.step, this.max, this.min); - } - } - debounce(fn, delay) { clearTimeout(this.debounceFn); this.debounceFn = setTimeout(() => { From f99f6668c2b0e00b6aa5c265ff9876fd4b386a3b Mon Sep 17 00:00:00 2001 From: Deshev Date: Fri, 9 Oct 2020 10:45:15 +0300 Subject: [PATCH 07/52] showTooltip property added for tooltiping current value, minor code cleaning, some more comments fixes --- packages/main/src/Slider.js | 33 +++++++++++++++++------------ packages/main/src/themes/Slider.css | 17 +++++++++++++++ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index ee5d68caea39..241f878be929 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -14,7 +14,7 @@ import SliderStyles from "./generated/themes/Slider.css.js"; */ const metadata = { tag: "ui5-slider", - altTag: "ui5-slider", + altTag: "ui5-range-slider", languageAware: true, managedSlots: true, properties: /** @lends sap.ui.webcomponents.main.Slider.prototype */ { @@ -34,12 +34,12 @@ const metadata = { *

* * @type {Float} - * @defaultvalue 100 + * @defaultvalue 50 * @public */ max: { type: Float, - defaultValue: 100, + defaultValue: 50, }, /** * 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). @@ -91,6 +91,17 @@ const metadata = { tickmarks: { type: Boolean, }, + /** + * Enables handle tooltip displaying the current value. + *

+ * + * @type {boolean} + * @defaultvalue false + * @public + */ + showTooltip: { + type: Boolean, + }, /** * Defines whether the ui5-slider is in disabled state. *

@@ -145,7 +156,7 @@ const metadata = { * @author SAP SE * @alias sap.ui.webcomponents.main.Slider * @extends sap.ui.webcomponents.base.UI5Element - * @tagname ui5-range-slider + * @tagname ui5-slider * @public */ class Slider extends UI5Element { @@ -251,7 +262,7 @@ class Slider extends UI5Element { // Do not update Slider if press is in range - only for range sliders (meaning that endValue property is set) if (this._isNewValueInCurrentRange) return; - const value = this._clipValue(this._calculateValueFromInteraction(event)); + const value = this._calculateValueFromInteraction(event); const updateValueAndFireEvent = () => { if (this.valueAffected === "startValue") { this._setValue(value); @@ -293,7 +304,7 @@ class Slider extends UI5Element { const inHandleEndDom = clientX >= handleEndDomRect.left && clientX <= handleEndDomRect.right; // Allow updating the slider even if the value is in current range, - // but at the same time the press action is over one of the handles + // but at the same time the press action is over one of the handles if (inHandleEndDom || inHandleStartDom) { this._isNewValueInCurrentRange = false; } @@ -328,14 +339,8 @@ class Slider extends UI5Element { value = numSteps * step; } - // Normalize value - if (value < min) { - value = min; - } else if (value > max) { - value = max; - } - - return value; + // Normalize value and keep it under constrains defined by the slider's properties + return this._clipValue(value); } /** diff --git a/packages/main/src/themes/Slider.css b/packages/main/src/themes/Slider.css index df1e7e17fa64..67a5b626b211 100644 --- a/packages/main/src/themes/Slider.css +++ b/packages/main/src/themes/Slider.css @@ -87,4 +87,21 @@ border: solid 0.125rem #89919a; opacity: 0.5; position: absolute; +} + +/* ------------------------------ */ +/* Slider Tooltip styles */ +/* ------------------------------ */ + +.ui5-slider-tooltip { + position: relative; + display: block; + flex-shrink: 0; + height: 1.625rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + text-align: center; + visibility: visible; + background-color: #666; + pointer-events: none; } \ No newline at end of file From 15f3eaf9cc4fc334f4792600e802ff7837eb1a08 Mon Sep 17 00:00:00 2001 From: Deshev Date: Fri, 9 Oct 2020 14:42:18 +0300 Subject: [PATCH 08/52] Tooltip functionality is introduced --- packages/main/src/Slider.hbs | 16 ++++++++-- packages/main/src/Slider.js | 46 ++++++++++++++++++++++++++-- packages/main/src/themes/Slider.css | 35 ++++++++++----------- packages/main/test/pages/Slider.html | 12 ++++++-- 4 files changed, 85 insertions(+), 24 deletions(-) diff --git a/packages/main/src/Slider.hbs b/packages/main/src/Slider.hbs index e72ef992e81d..e6262ae47253 100644 --- a/packages/main/src/Slider.hbs +++ b/packages/main/src/Slider.hbs @@ -15,9 +15,21 @@ {{/if}} {{/if}} -
+
+ {{#if showTooltip}} +
+ {{value}} +
+ {{/if}} +
{{#if endValue}} -
+
+ {{#if showTooltip}} +
+ {{endValue}} +
+ {{/if}} +
{{/if}}
diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index 241f878be929..f7311cb3e09f 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -186,11 +186,15 @@ class Slider extends UI5Element { super(); this.i18nBundle = getI18nBundle("@ui5/webcomponents"); } - onEnterDOM() { this._moveHandler = this._onMouseMove.bind(this); this._upHandler = this._onMouseUp.bind(this); + this._mouseOverHandler = this._onMouseOver.bind(this); + this._mouseOutHandler= this._onMouseOut.bind(this); + + this.addEventListener("mouseover", this._mouseOverHandler); + this.addEventListener("mouseout", this._mouseOutHandler); this._initialUISync() } @@ -216,6 +220,14 @@ class Slider extends UI5Element { window.removeEventListener("mousemove", this._moveHandler); } + _onMouseOver() { + this._handleMouseOver(); + } + + _onMouseOut() { + this._handleMouseOut(); + } + /** * Called when the user starts interacting with the slider */ @@ -244,7 +256,9 @@ class Slider extends UI5Element { // Do not update Slider if press is in range - only for range sliders (meaning that endValue property is set) if (this._isNewValueInCurrentRange) return; + this._updateTooltipValue(newValue); this._updateUI(newValue); + if (this.valueAffected === "startValue") { this._setValue(newValue); } else { @@ -270,6 +284,8 @@ class Slider extends UI5Element { this._setEndValue(value); } } + + this._updateTooltipValue(value); // Update Slider UI in real-time (decoupled with rendering) this._updateUI(value); // Prevent re-rendering on every move event fired @@ -280,6 +296,23 @@ class Slider extends UI5Element { this.fireEvent("change"); } + _handleMouseOver(event) { + if (!this.showTooltip) return; + + this.shadowRoot.querySelector(".ui5-slider-tooltip").style.setProperty("visibility", "visible"); + if (this.endValue) { + this.shadowRoot.querySelector(".ui5-slider-end-tooltip").style.setProperty("visibility", "visible"); + } + } + + _handleMouseOut(event) { + if (!this.showTooltip) return; + + this.shadowRoot.querySelector(".ui5-slider-tooltip").style.setProperty("visibility", "hidden"); + if (this.endValue) { + this.shadowRoot.querySelector(".ui5-slider-end-tooltip").style.setProperty("visibility", "hidden"); + } + } /** * Returns the correct handle DOM and sets the value that has to be modified after user interaction @@ -392,6 +425,15 @@ class Slider extends UI5Element { return value; } + + _updateTooltipValue(newValue) { + if (!this.showTooltip) return; + const tooltipToUpdate = this.valueAffected === "startValue" ? + this.shadowRoot.querySelector(".ui5-slider-tooltip-value") : this.shadowRoot.querySelector(".ui5-slider-end-tooltip-value"); + + tooltipToUpdate.textContent = newValue; + } + /** * Computes the new value (in %) from the pageX position of the cursor */ @@ -416,7 +458,7 @@ class Slider extends UI5Element { // The progress (completed) percentage of the slider. In case of a range slider it is the range selection let percentageComplete; - // How many pixels from the left end of the slider will be the placed the affected by the user action handle + // How many pixels from the left end of the slider will be the placed the affected by the user action handle let handlePositionFromLeft; // The value according to which we update the UI can be either the (start) value diff --git a/packages/main/src/themes/Slider.css b/packages/main/src/themes/Slider.css index 67a5b626b211..c22c840c39ef 100644 --- a/packages/main/src/themes/Slider.css +++ b/packages/main/src/themes/Slider.css @@ -59,16 +59,7 @@ } -.ui5-slider-tickmarks { - /* margin: 0; - padding: 0; - list-style: none; - position: absolute; - left: 1.0625rem; - right: 1.0625rem;; - display: block; - height: 1.75rem; - z-index: 0; */ +.ui5-slider-tickmarks { color: #89919a; position: absolute; width: 100%; @@ -85,23 +76,31 @@ border-radius: 1rem; background: #FFF; border: solid 0.125rem #89919a; - opacity: 0.5; position: absolute; + opacity: 0.65; } /* ------------------------------ */ /* Slider Tooltip styles */ /* ------------------------------ */ -.ui5-slider-tooltip { - position: relative; +.ui5-slider-tooltip, .ui5-slider-end-tooltip { + position: absolute; + border: 1px solid #333; + border-radius: 0.25rem; + bottom: 2.25rem; + left: -0.5rem; display: block; - flex-shrink: 0; + color: #333; + font-size: 1rem; + font-weight: bold; + font-family: sans-serif; height: 1.625rem; - padding-left: 0.5rem; - padding-right: 0.5rem; + line-height: 1.625rem; + padding: 0.25rem; text-align: center; - visibility: visible; - background-color: #666; + visibility: hidden; + background-color: #FFF; + min-width: 2.225rem; pointer-events: none; } \ No newline at end of file diff --git a/packages/main/test/pages/Slider.html b/packages/main/test/pages/Slider.html index dac21a51ba03..3d386ae9ed80 100644 --- a/packages/main/test/pages/Slider.html +++ b/packages/main/test/pages/Slider.html @@ -28,6 +28,10 @@ margin: 0; } + h2 { + margin: 25px 0; + } + .group { padding: 1rem; } @@ -38,6 +42,8 @@

Basic Slider

+

Basic Slider with tooltip

+

Slider with tickmarks

Slider with many steps and small width

@@ -46,8 +52,10 @@

Smooth Slider with no steps

Range Slider with steps and tickmarks

-

Smooth Range Slider with no steps

- +

Smooth Range Slider with tooltips and no steps

+ +

Range Slider with steps, tooltips and tickmarks

+
From 68d3400d2ac63535df2e66ab5a6f55584638fb31 Mon Sep 17 00:00:00 2001 From: Deshev Date: Sun, 11 Oct 2020 10:41:06 +0300 Subject: [PATCH 09/52] Labels introduced --- packages/main/src/Slider.hbs | 7 +- packages/main/src/Slider.js | 143 +++++++++++++++------------ packages/main/src/themes/Slider.css | 32 +++++- packages/main/test/pages/Slider.html | 23 +++-- 4 files changed, 130 insertions(+), 75 deletions(-) diff --git a/packages/main/src/Slider.hbs b/packages/main/src/Slider.hbs index e6262ae47253..4506d834fbfe 100644 --- a/packages/main/src/Slider.hbs +++ b/packages/main/src/Slider.hbs @@ -9,9 +9,12 @@
- {{#if tickmarks}} - {{#if step}} + {{#if step}} + {{#if tickmarks}}
+ {{#if labelDistance}} +
    + {{/if}} {{/if}} {{/if}} diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index f7311cb3e09f..761471c49f21 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -1,6 +1,7 @@ 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 { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js"; // Template @@ -51,9 +52,22 @@ const metadata = { * @public */ step: { - type: Float, + type: Integer, defaultValue: 1, }, + /** + * Put a label with a value on every N-th step. 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 - than every second + * tickmark will be labelled, which means every 4th round value number. + * + * @type {Integer} + * @defaultvalue 1 + * @public + */ + labelInterval: { + type: Integer, + defaultValue: 0, + }, /** * Current value of the slider *

    @@ -73,12 +87,12 @@ const metadata = { *

    * * @type {Float} - * @defaultvalue 0 + * @defaultvalue null * @public */ endValue: { type: Float, - defaultValue: 0, + defaultValue: null, }, /** * Enables tick marks visualization for each step. The step value must not be set to 0 @@ -191,23 +205,15 @@ class Slider extends UI5Element { this._moveHandler = this._onMouseMove.bind(this); this._upHandler = this._onMouseUp.bind(this); this._mouseOverHandler = this._onMouseOver.bind(this); - this._mouseOutHandler= this._onMouseOut.bind(this); + this._mouseOutHandler= this._onMouseOut.bind(this); this.addEventListener("mouseover", this._mouseOverHandler); this.addEventListener("mouseout", this._mouseOutHandler); - this._initialUISync() - } - - onBeforeRendering() { - if (this.step !== 1) { - this.setStep(this.step); - } - } - onAfterRendering() { - if (this.step && this.tickmarks) { - this.drawDefaultTickmarks(this.step, this.max, this.min); - } + // Normalize Slider value according to min/max properties + this.value = this._clipValue(this.value) + this._initialUISync() + this._setStep(); } _onMouseMove(event) { @@ -232,9 +238,7 @@ class Slider extends UI5Element { * Called when the user starts interacting with the slider */ _handleDown(event) { - if (this.disabled) { - return; - } + if (this.disabled) return; const oldEndValue = this.endValue; const oldStartValue = this.value; @@ -256,7 +260,6 @@ class Slider extends UI5Element { // Do not update Slider if press is in range - only for range sliders (meaning that endValue property is set) if (this._isNewValueInCurrentRange) return; - this._updateTooltipValue(newValue); this._updateUI(newValue); if (this.valueAffected === "startValue") { @@ -272,32 +275,29 @@ class Slider extends UI5Element { */ _handleMove(event) { event.preventDefault(); + if (this.disabled) return; // Do not update Slider if press is in range - only for range sliders (meaning that endValue property is set) if (this._isNewValueInCurrentRange) return; - const value = this._calculateValueFromInteraction(event); - const updateValueAndFireEvent = () => { - if (this.valueAffected === "startValue") { - this._setValue(value); - } else { - this._setEndValue(value); - } - } - - this._updateTooltipValue(value); // Update Slider UI in real-time (decoupled with rendering) this._updateUI(value); - // Prevent re-rendering on every move event fired - this.debounce(updateValueAndFireEvent, 100); + + if (this.valueAffected === "startValue") { + this._setValue(value); + } else { + this._setEndValue(value); + } } _handleUp() { + if (this.disabled) return; + this.fireEvent("change"); } _handleMouseOver(event) { - if (!this.showTooltip) return; + if (this.disabled || !this.showTooltip) return; this.shadowRoot.querySelector(".ui5-slider-tooltip").style.setProperty("visibility", "visible"); if (this.endValue) { @@ -367,6 +367,8 @@ class Slider extends UI5Element { let value = this._computeValueFromPageX(pageX); // "Stepihfy" the raw value - calculate a step value + // Steps are integers, so when Slider is "quantized" (stepped) + // the value is also rounded to an integer. if (this.step !== 0) { const numSteps = Math.round(value / step); value = numSteps * step; @@ -394,12 +396,12 @@ class Slider extends UI5Element { _setEndValue(value) { this.endValue = value; this.fireEvent("input"); -} + } + /** * Locks the given value for the given handle between boundaries based on slider properties: * 1. Restricts value within the min & max properties. * 2. If range slider, keep start value lower than end value, and the opposite. - * 3. If range slider keep the endValue greater than 0 to prevent removing of the second handle */ _clipValue(value) { value = Math.min(Math.max(value, this.min), this.max); @@ -412,12 +414,6 @@ class Slider extends UI5Element { return this.valueEnd; } - // If the endValue is become equal or less than 1. Do not let - // the end value to become 0 because the second handle will be removed - if (this.valueAffected === "endValue" && value <= 1) { - return 1; - } - // If the endValue is become equal or less than the start value if (this.valueAffected === "endValue" && value < this.value) { return this.value; @@ -426,16 +422,9 @@ class Slider extends UI5Element { return value; } - _updateTooltipValue(newValue) { - if (!this.showTooltip) return; - const tooltipToUpdate = this.valueAffected === "startValue" ? - this.shadowRoot.querySelector(".ui5-slider-tooltip-value") : this.shadowRoot.querySelector(".ui5-slider-end-tooltip-value"); - - tooltipToUpdate.textContent = newValue; - } - /** - * Computes the new value (in %) from the pageX position of the cursor + * 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. */ _computeValueFromPageX(pageX) { const max = this.max; @@ -446,7 +435,7 @@ class Slider extends UI5Element { // Calculate the percentage complete (the "progress") const percentageComplete = xPositionRelative / this._boundingDOMRect.width; // Fit (map) the complete percentage between the min/max value range - return min + percentageComplete * (max - min); + return (min + percentageComplete * (max - min)).toFixed(2); } // Update UI after user interaction. @@ -535,12 +524,11 @@ class Slider extends UI5Element { /** * Calculates and draws the tickmarks with a CSS gradient style */ - drawDefaultTickmarks(step, max, min) { + _drawDefaultTickmarks(step, max, min) { + // Let the CSS do all calculations for more precise browser results const stepStr = String(step); const maxStr = String(max); const minStr = String(min); - - // Calculate how many tickmarks have to be drawn (max - min / stepValue) const tickmarksAmount = `${maxStr - minStr} / ${stepStr}`; const tickmarkWidth = "1px"; @@ -555,21 +543,52 @@ class Slider extends UI5Element { // Apply the style to the container this.shadowRoot.querySelector(".ui5-slider-tickmarks").style.setProperty("background", tickmarksBackground); + + // If labelsInterval is specified draw labels for the necessary tickmarks + if (this.labelInterval) { + this._drawDefaultLabels(parseInt(tickmarkWidth)); + } + } + /** + * Calculates the labels amout, width and text and creates them + */ + _drawDefaultLabels(tickmarkWidth) { + const labelContainer = this.shadowRoot.querySelector(".ui5-slider-labels"); + const labelInterval = this.labelInterval; + const numberOfLabels = (this.max - this.min) / (this.step * labelInterval); + + // If the required labels are already rendered return + if(labelContainer.childElementCount === numberOfLabels) return; + + // 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 int, so it will "floor" + // the number of labels anyway. + const spaceBetweenLabelsPx = this.getBoundingClientRect().width / numberOfLabels; + + for(let i = 0; i <= numberOfLabels; i++) { + const labelItem = document.createElement("li"); + labelItem.textContent = (i * labelInterval) + Math.round(this.min); + labelContainer.appendChild(labelItem); + + // Make every label width as the distance between the tickmarks + labelItem.style.setProperty("width", `${spaceBetweenLabelsPx}px`); + // Set negative left offset to center evey label to be in the middle of the tickmark above it + labelContainer.style.setProperty("left", `-${spaceBetweenLabelsPx / 2}px`); + // Set additional width space of the label container to contain the centered labels + labelContainer.style.setProperty("width", `calc(100% + ${spaceBetweenLabelsPx}px)`); + } } - setStep(step) { + _setStep(step) { if (typeof step !== "number" || step < 0) { step = 1; } this.step = step; - } - debounce(fn, delay) { - clearTimeout(this.debounceFn); - this.debounceFn = setTimeout(() => { - this.debounceFn = null; - fn(); - }, delay); + if (this.tickmarks) { + this._drawDefaultTickmarks(this.step, this.max, this.min); + } } } diff --git a/packages/main/src/themes/Slider.css b/packages/main/src/themes/Slider.css index c22c840c39ef..b0aa66fddd39 100644 --- a/packages/main/src/themes/Slider.css +++ b/packages/main/src/themes/Slider.css @@ -2,6 +2,11 @@ display: inline-block; } +:host([disabled]) { + opacity: 0.5; + cursor: default; + pointer-events: none; +} :host { cursor: pointer; @@ -15,7 +20,6 @@ box-sizing: border-box; height: 2rem; padding: 1rem 0; - } .ui5-slider-inner { @@ -30,7 +34,7 @@ position: relative; background-repeat: no-repeat; z-index: 1; - height: 0.25rem; + height: 0.25rem; background: #89919a; border-radius: 0.25rem; @@ -89,18 +93,36 @@ border: 1px solid #333; border-radius: 0.25rem; bottom: 2.25rem; - left: -0.5rem; + left: -0.725rem; display: block; color: #333; font-size: 1rem; font-weight: bold; - font-family: sans-serif; height: 1.625rem; line-height: 1.625rem; - padding: 0.25rem; + padding: 0.25rem 0.5rem; text-align: center; visibility: hidden; background-color: #FFF; min-width: 2.225rem; pointer-events: none; +} + +.ui5-slider-labels { + position: absolute; + top: 1rem; + padding: 0; + margin: 0; +} + +.ui5-slider-labels li { + color: #666; + position: relative; + list-style: none; + padding: 0; + margin:0; + font-size: 0.75rem; + font-weight: bold; + text-align: center; + display: inline-block; } \ No newline at end of file diff --git a/packages/main/test/pages/Slider.html b/packages/main/test/pages/Slider.html index 3d386ae9ed80..882f0036c616 100644 --- a/packages/main/test/pages/Slider.html +++ b/packages/main/test/pages/Slider.html @@ -25,11 +25,12 @@ + + + +
    +

    Basic Range Slider

    + + +

    Range Slider with custom min and max properties and tooltip

    + + +

    Range Slider with small step and tooltip

    + + +

    Range Slider with tickmarks

    + + +

    Disabled Range Slider

    + + +

    Range Slider with steps, tooltips, tickmarks and labels

    + +
    + + diff --git a/packages/main/test/samples/RangeSlider.sample.html b/packages/main/test/samples/RangeSlider.sample.html new file mode 100644 index 000000000000..67b8bc2805b3 --- /dev/null +++ b/packages/main/test/samples/RangeSlider.sample.html @@ -0,0 +1,63 @@ +
    +

    Range Slider

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

    Basic Range Slider

    +
    + +
    + +
    
    +<ui5-range-slider end-value="20"></ui5-range-slider>
    +	
    +
    + +
    +

    Range Slider with Custom 'min', 'max', 'startValue' and 'endValue' Properties

    +
    + +
    +
    
    +<ui5-range-slider  min="100" max="200" start-value="120" end-value="150"></ui5-range-slider>
    +	
    +
    + +
    +

    Range Slider with Tooltips

    +
    + +
    +
    
    +<ui5-range-slider start-value="3" end-value="13" show-tooltip></ui5-range-slider>
    +	
    +
    + +
    +

    Range Slider with Tickmarks and Custom Step

    +
    + +
    +
    
    +<ui5-range-slider step="2" start-value="4" end-value="12" show-tckmarks></ui5-range-slider>
    +	
    +
    + +
    +

    Range Slider with Tooltips, Tickmarks and Labels

    +
    + +
    +
    
    +<ui5-range-slider min="0" max="112" step="2" start-value="4" end-value="12" show-tooltip label-interval="2" show-tckmarks></ui5-range-slider>
    +	
    +
    + + \ No newline at end of file diff --git a/packages/main/test/specs/RangeSlider.spec.js b/packages/main/test/specs/RangeSlider.spec.js new file mode 100644 index 000000000000..4f44a728dae4 --- /dev/null +++ b/packages/main/test/specs/RangeSlider.spec.js @@ -0,0 +1,278 @@ +const assert = require("chai").assert; + +describe("Testing Range Slider interactions", () => { + + it("Changing the current startValue is reflected", () => { + browser.url("http://localhost:8080/test-resources/pages/RangeSlider.html"); + browser.setWindowSize(1257, 2000); + + const rangeSlider = browser.$("#range-slider-tickmarks"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); + + assert.strictEqual(startHandle.getAttribute("style"), "left: 0%;", "Initially if no value is set, the Range Slider start-handle is at the beginning of the Range Slider"); + + rangeSlider.setProperty("startValue", 5); + + assert.strictEqual(startHandle.getAttribute("style"), "left: 12.5%;", "Start-handle should be 12.5% from the start"); + + startHandle.dragAndDrop({ x: 100, y: 1 }); + + assert.strictEqual(startHandle.getAttribute("style"), "left: 20%;", "Start-handle should be 20% from the start of the Range Slider"); + assert.strictEqual(rangeSlider.getProperty("startValue"), 8, "Range Slider startValue should be 8"); + + startHandle.click({ x: -100 }); + + assert.strictEqual(startHandle.getAttribute("style"), "left: 12.5%;", "Start-handle should be again 12.5% from the start"); + assert.strictEqual(rangeSlider.getProperty("startValue"), 5, "Current startValue should be again 5"); + }); + + it("Changing the endValue is reflected", () => { + const rangeSlider = browser.$("#range-slider-tickmarks"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); + + assert.strictEqual(endHandle.getAttribute("style"), "left: 50%;", "Range Slider end-handle is should be 50% from the start the Range Slider"); + rangeSlider.setProperty("endValue", 10); + + assert.strictEqual(endHandle.getAttribute("style"), "left: 25%;", "End-handle should be 25% from the start"); + + rangeSlider.click(); + + assert.strictEqual(endHandle.getAttribute("style"), "left: 50%;", "Range Slider end-handle should be in the middle of the slider"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 20, "Range Slider endValue should be 20"); + + endHandle.click({ x: 100 }); + + assert.strictEqual(endHandle.getAttribute("style"), "left: 57.5%;", "End-handle should be 57.5%% from the start of the Range slider"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 23, "Range Slider current endValue should be 23"); + + endHandle.dragAndDrop({ x: -100, y: 1 }); + + assert.strictEqual(endHandle.getAttribute("style"), "left: 50%;", "End-handle should be back to 50% from the start of the Range Slider"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 20, "Current endValue should be 20"); + }); + + it("Click within the selected range should not change any value", () => { + const rangeSlider = browser.$("#range-slider-tickmarks"); + + rangeSlider.setProperty("endValue", 30); + + assert.strictEqual(rangeSlider.getProperty("startValue"), 5, "startValue should be 5"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 30, "endValue value should be 30"); + + rangeSlider.click(); + + assert.strictEqual(rangeSlider.getProperty("startValue"), 5, "startValue should still be 5"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 30, "endValue value should still be 30"); + }); + + it("Dragging the selected range should change both values and handles", () => { + const rangeSlider = browser.$("#range-slider-tickmarks"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); + + rangeSlider.dragAndDrop({ x: 100, y: 0 }); + + assert.strictEqual(rangeSlider.getProperty("startValue"), 8, "startValue should be 8"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 33, "endValue should be 33"); + }); + + it("Dragging the start-handle pass the end-handle should swap the values", () => { + const rangeSlider = browser.$("#range-slider-tickmarks"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); + + rangeSlider.setProperty("endValue", 9); + + startHandle.dragAndDrop({ x: 100, y: 1 }); + + assert.strictEqual(rangeSlider.getProperty("startValue"), 9, "startValue should swapped with the endValue and should be 9"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 11, "endValue should swapped with the startValue and should be 11"); + }); + + it("Dragging the whole range selection should always keep the initially selected range and be within min/max values", () => { + const rangeSlider = browser.$("#range-slider-tickmarks"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); + + rangeSlider.setProperty("endValue", 30); + + rangeSlider.dragAndDrop({ x: -500, y: 1 }); + + assert.strictEqual(rangeSlider.getProperty("startValue"), 0, "startValue should be 0 as the selected range has reached the start of the Range Slider"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 21, "endValue should be 21 and no less, the initially selected range should be preserved"); + + rangeSlider.dragAndDrop({ x: 600, y: 1 }); + + assert.strictEqual(rangeSlider.getProperty("startValue"), 19, "startValue should be 19 and no more, the initially selected range should be preserved"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 40, "endValue should be 40 as the selected range has reached the end of the Range Slider"); + }); + + it("Range Slider should not be interactive if the step property is 0", () => { + const rangeSlider = browser.$("#basic-range-slider"); + + rangeSlider.setProperty("step", 0); + rangeSlider.click(); + + assert.strictEqual(rangeSlider.getProperty("endValue"), 20, "Range Slider endValue should be the same"); + }); + + it("Disabled Range Slider is not interactive", () => { + const rangeSlider = browser.$("#disabled-range-slider"); + + assert.strictEqual(rangeSlider.isClickable(), false, "Range Slider should be disabled"); + }); +}); + +describe("Range Slider elements - tooltip, step, tickmarks, labels", () => { + + it("Range Slider tooltips are displayed showing the current value", () => { + const rangeSlider = browser.$("#basic-range-slider-with-tooltip"); + const rangeSliderStartTooltip = rangeSlider.shadow$(".ui5-slider-tooltip.start-tooltip"); + const rangeSliderStartTooltipValue = rangeSliderStartTooltip.shadow$(".ui5-slider-tooltip-value"); + const rangeSliderEndTooltip = rangeSlider.shadow$(".ui5-slider-tooltip.end-tooltip"); + const rangeSliderEndTooltipValue = rangeSliderEndTooltip.shadow$(".ui5-slider-tooltip-value"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); + + rangeSlider.moveTo(); + + assert.strictEqual(rangeSlider.getProperty("_tooltipVisibility"), "visible", "Range Slider tooltips visibility property should be 'visible'"); + assert.strictEqual(rangeSliderStartTooltip.getAttribute("style"), "visibility: visible;", "Range Slider start tooltip should be shown"); + assert.strictEqual(rangeSliderEndTooltip.getAttribute("style"), "visibility: visible;", "Range Slider end tooltip should be shown"); + + rangeSlider.setProperty("startValue", 65); + rangeSlider.moveTo(); + + assert.strictEqual(rangeSliderStartTooltipValue.getText(), "65", "Range Slider start tooltip should display value of 65"); + + rangeSlider.setProperty("endValue", 115); + rangeSlider.moveTo(); + + assert.strictEqual(rangeSliderEndTooltipValue.getText(), "115", "Range Slider end tooltip should display value of 65"); + }); + + it("Range Slider have correct number of labels and tickmarks based on the defined step and labelInterval properties", () => { + const rangeSlider = browser.$("#range-slider-tickmarks-labels"); + const labelsContainer = rangeSlider.shadow$(".ui5-slider-labels"); + const numberOfLabels = labelsContainer.$$("li").length; + + assert.strictEqual(numberOfLabels, 12, "12 labels should be rendered, 1 between every 4 tickmarks"); + }); +}); + +describe("Properties synchronization and normalization", () => { + + it("If a negative number is set to the step property it should beconverted it to its positive equivalent", () => { + const rangeSlider = browser.$("#range-slider-tickmarks-labels"); + + rangeSlider.setProperty("step", -15); + + assert.strictEqual(rangeSlider.getProperty("step"), 15, "Step value should be a positive number"); + }); + + it("Should fallback to default value of 1 if step property is not a valid number", () => { + const rangeSlider = browser.$("#range-slider-tickmarks-labels"); + + rangeSlider.setProperty("step", "String value"); + + assert.strictEqual(rangeSlider.getProperty("step"), 1, "Step value should be its default value"); + }); + + it("If min property is set to a greater number than the max property they should be swapped", () => { + const rangeSlider = browser.$("#range-slider-tickmarks-labels"); + + rangeSlider.setProperty("max", 10); + rangeSlider.setProperty("min", 100); + + assert.strictEqual(rangeSlider.getProperty("min"), 10, "min property should be less than the max one"); + assert.strictEqual(rangeSlider.getProperty("max"), 100, "max property should be greater than the min one"); + assert.strictEqual(rangeSlider.getProperty("startValue"), 10, "startValue property should be within the boundaries of the swapped min and max props"); + + rangeSlider.setProperty("min", 200); + + assert.strictEqual(rangeSlider.getProperty("min"), 100, "min property should be the 100"); + assert.strictEqual(rangeSlider.getProperty("max"), 200, "max property should be 200"); + assert.strictEqual(rangeSlider.getProperty("startValue"), 100, "value property should be within the boundaries of the swapped min and max props"); + }); + + it("Should keep the current values between the boundaries of min and max properties", () => { + const rangeSlider = browser.$("#range-slider-tickmarks-labels"); + + rangeSlider.setProperty("endValue", 300); + + assert.strictEqual(rangeSlider.getProperty("endValue"), 200, "value prop should always be lower than the max value"); + + rangeSlider.setProperty("startValue", 99); + + assert.strictEqual(rangeSlider.getProperty("startValue"), 100, "value prop should always be greater than the min value"); + }); + + it("Should not 'stepify' current value if it is not in result of user interaction", () => { + const rangeSlider = browser.$("#range-slider-tickmarks-labels"); + + rangeSlider.setProperty("min", 0); + rangeSlider.setProperty("max", 44); + rangeSlider.setProperty("step", 1.25); + rangeSlider.setProperty("startValue", 14); + rangeSlider.setProperty("endValue", 24); + + assert.strictEqual(rangeSlider.getProperty("startValue"), 14, "startValue should not be stepped to the next step (15)"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 24, "endValue should not be stepped to the next step (25)"); + }); +}); + +describe("Testing resize handling and RTL support", () => { + it("Testing RTL support", () => { + const rangeSlider = browser.$("#range-slider-tickmarks-labels"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); + + rangeSlider.setAttribute("dir", "rtl"); + rangeSlider.setProperty("min", 0); + rangeSlider.setProperty("max", 10); + rangeSlider.setProperty("step", 1); + rangeSlider.setProperty("startValue", 0); + rangeSlider.setProperty("endValue", 4); + + assert.strictEqual(startHandle.getAttribute("style"), "right: 0%;", "Initially if no value is set, the start-handle is 0% from the right side of the Range Slider"); + assert.strictEqual(endHandle.getAttribute("style"), "right: 40%;", "End-handle should be 40 percent from the right side of the Range Slider"); + + rangeSlider.setProperty("startValue", 3); + + assert.strictEqual(startHandle.getAttribute("style"), "right: 30%;", "Start-handle should be 30% from the right end of the Range Slider"); + + rangeSlider.click(); + + assert.strictEqual(endHandle.getAttribute("style"), "right: 50%;", "End-handle should be at the middle of the Range Slider"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 5, "endValue should be 5"); + + endHandle.dragAndDrop({ x: -200, y: 1 }); + + assert.strictEqual(endHandle.getAttribute("style"), "right: 80%;", "End-handle should be 80% from the right of the slider"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 8, "endValue should be 8"); + + endHandle.dragAndDrop({ x: -100, y: 1 }); + + assert.strictEqual(endHandle.getAttribute("style"), "right: 90%;", "End-handle should be 90% from the right of the slider"); + assert.strictEqual(rangeSlider.getProperty("endValue"), 9, "endValue should be 9"); + + startHandle.dragAndDrop({ x: 350, y: 1 }); + + assert.strictEqual(startHandle.getAttribute("style"), "right: 0%;", "Slider handle should be 0% at the right of the Range Slider"); + assert.strictEqual(rangeSlider.getProperty("startValue"), 0, "startValue should be 0"); + }); + + it("Should hide all labels except the first and the last one, if there is not enough space for all of them", () => { + const rangeSlider = browser.$("#range-slider-tickmarks-labels"); + + rangeSlider.setAttribute("dir", "ltr"); + rangeSlider.setProperty("min", 0); + rangeSlider.setProperty("max", 44); + rangeSlider.setProperty("step", 1.25); + + browser.setWindowSize(400, 2000); + + assert.strictEqual(rangeSlider.getProperty("_labelsOverlapping"), true, "state should reflect if any of the labels is overlapping with another"); + assert.strictEqual(rangeSlider.getProperty("_hiddenTickmarks"), true, "state should reflect if the tickmarks has less than 8px space between each of them"); + }); +}); From 2c9348cff7d433c2e6673376725eefc5abc0f7f8 Mon Sep 17 00:00:00 2001 From: ndeshev Date: Mon, 16 Nov 2020 16:54:25 +0200 Subject: [PATCH 43/52] Add appenddocs and since tags, sample fixes --- packages/main/src/RangeSlider.js | 2 ++ packages/main/src/Slider.js | 24 +++++++++++++++---- .../main/test/samples/RangeSlider.sample.html | 8 +++---- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/main/src/RangeSlider.js b/packages/main/src/RangeSlider.js index 2dbddb079b6c..840145da944b 100644 --- a/packages/main/src/RangeSlider.js +++ b/packages/main/src/RangeSlider.js @@ -80,6 +80,8 @@ const metadata = { * @alias sap.ui.webcomponents.main.RangeSlider * @extends sap.ui.webcomponents.base.UI5Element * @tagname ui5-range-slider + * @since 1.0.0-rc.11 + * @appenddocs SliderBase * @public */ class RangeSlider extends SliderBase { diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index 9b92da7cb29c..02294e5f7c76 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -43,7 +43,7 @@ const metadata = { *
  • value - The current value of the slider
  • *
  • step - Determines the increments in which the slider will move
  • *
  • showTooltip - Determines if a tooltip should be displayed above the handle
  • - *
  • tickmarks - Displays a visual divider between the step values
  • + *
  • showTickmarks - Displays a visual divider between the step values
  • *
  • labelInterval - Labels some or all of the tickmarks with their values.
  • * * @@ -67,6 +67,8 @@ const metadata = { * @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 { @@ -123,9 +125,11 @@ class Slider extends SliderBase { const newValue = this.handleDownBase(event, this.min, this.max); - // Update Slider UI and internal state - this._updateUI(newValue); - this.updateValue("value", newValue); + // 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(SliderBase.getPageXValueFromEvent(event))) { + this._updateUI(newValue); + this.updateValue("value", newValue); + } } /** @@ -156,6 +160,18 @@ class Slider extends SliderBase { 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 Slider according to its internal state. * * @private diff --git a/packages/main/test/samples/RangeSlider.sample.html b/packages/main/test/samples/RangeSlider.sample.html index 67b8bc2805b3..01f95f2ed102 100644 --- a/packages/main/test/samples/RangeSlider.sample.html +++ b/packages/main/test/samples/RangeSlider.sample.html @@ -43,20 +43,20 @@

    Range Slider with Tooltips

    Range Slider with Tickmarks and Custom Step

    - +
    
    -<ui5-range-slider step="2" start-value="4" end-value="12" show-tckmarks></ui5-range-slider>
    +<ui5-range-slider step="2" start-value="4" end-value="12" show-tickmarks></ui5-range-slider>
     	

    Range Slider with Tooltips, Tickmarks and Labels

    - +
    
    -<ui5-range-slider min="0" max="112" step="2" start-value="4" end-value="12" show-tooltip label-interval="2" show-tckmarks></ui5-range-slider>
    +<ui5-range-slider min="0" max="112" step="2" start-value="4" end-value="12" show-tooltip label-interval="2" show-tickmarks></ui5-range-slider>
     	
    From 262f91db4e31dbb11323e0884019eb2bd934d13b Mon Sep 17 00:00:00 2001 From: ndeshev Date: Mon, 16 Nov 2020 16:59:43 +0200 Subject: [PATCH 44/52] Minor lintix fix --- packages/main/src/RangeSlider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/main/src/RangeSlider.js b/packages/main/src/RangeSlider.js index 840145da944b..9b99df0664cb 100644 --- a/packages/main/src/RangeSlider.js +++ b/packages/main/src/RangeSlider.js @@ -80,7 +80,7 @@ const metadata = { * @alias sap.ui.webcomponents.main.RangeSlider * @extends sap.ui.webcomponents.base.UI5Element * @tagname ui5-range-slider - * @since 1.0.0-rc.11 + * @since 1.0.0-rc.11 * @appenddocs SliderBase * @public */ From bc28e30979e64f58c4bc1ba3643b327a4e5f8a13 Mon Sep 17 00:00:00 2001 From: ndeshev Date: Wed, 18 Nov 2020 07:54:35 +0200 Subject: [PATCH 45/52] Implement code reviews changes --- packages/main/src/RangeSlider.js | 54 +++---- packages/main/src/SliderBase.hbs | 3 +- packages/main/src/SliderBase.js | 149 +++++++++--------- packages/main/src/themes/SliderBase.css | 9 +- .../src/themes/base/SliderBase-parameters.css | 4 +- packages/main/test/specs/RangeSlider.spec.js | 28 +--- 6 files changed, 115 insertions(+), 132 deletions(-) diff --git a/packages/main/src/RangeSlider.js b/packages/main/src/RangeSlider.js index 9b99df0664cb..ed9d6be8f52f 100644 --- a/packages/main/src/RangeSlider.js +++ b/packages/main/src/RangeSlider.js @@ -56,7 +56,7 @@ const metadata = { *
  • value - The current value of the slider
  • *
  • step - Determines the increments in which the slider will move
  • *
  • showTooltip - Determines if a tooltip should be displayed above the handle
  • - *
  • tickmarks - Displays a visual divider between the step values
  • + *
  • showTickmarks - Displays a visual divider between the step values
  • *
  • labelInterval - Labels some or all of the tickmarks with their values.
  • * *

    Notes:

    @@ -93,10 +93,6 @@ class RangeSlider extends SliderBase { return RangeSliderTemplate; } - static get styles() { - return super.styles; - } - constructor() { super(); this._stateStorage.startValue = null; @@ -105,12 +101,12 @@ class RangeSlider extends SliderBase { } get tooltipStartValue() { - const stepPrecision = SliderBase._getDecimalPrecisionOfNumber(this.step); + const stepPrecision = this.constructor._getDecimalPrecisionOfNumber(this._effectiveStep); return this.startValue.toFixed(stepPrecision); } get tooltipEndValue() { - const stepPrecision = SliderBase._getDecimalPrecisionOfNumber(this.step); + const stepPrecision = this.constructor._getDecimalPrecisionOfNumber(this._effectiveStep); return this.endValue.toFixed(stepPrecision); } @@ -131,7 +127,7 @@ class RangeSlider extends SliderBase { this.notResized = true; this.syncUIAndState("startValue", "endValue"); - this._updateUI(null); + this._updateHandlesAndRange(null); } /** @@ -142,18 +138,18 @@ class RangeSlider extends SliderBase { _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) { + if (this.disabled || this._effectiveStep === 0) { return; } const oldEndValue = this.endValue; const oldStartValue = this.startValue; - const newValue = this.handleDownBase(event, this.min, this.max); + const newValue = this.handleDownBase(event, this._effectiveMin, this._effectiveMax); // Check if the new value is in the current select range of values this._isNewValueInCurrentRange = newValue > oldStartValue && newValue < oldEndValue; // Save the initial press point coordinates (position). - this._initialPageXPosition = SliderBase.getPageXValueFromEvent(event); + this._initialPageXPosition = this.constructor.getPageXValueFromEvent(event); // Determine value to be modified depending on the the handle clossest to the press point this._setCurrentValueType(this._initialPageXPosition, newValue); @@ -168,7 +164,7 @@ class RangeSlider extends SliderBase { } // Update Slider UI and internal state - this._updateUI(newValue); + this._updateHandlesAndRange(newValue); this.updateValue(this._valueAffected, newValue); this.storePropertyState(this._valueAffected); } @@ -182,16 +178,16 @@ class RangeSlider extends SliderBase { event.preventDefault(); // If 'step' is 0 no interaction is available as there is no constant quantitative representation of the value - if (this.disabled || this.step === 0) { + if (this.disabled || this._effectiveStep === 0) { return; } // If the user does not drag the whole range selection, // sync the internal state and the UI with the corresponding value change if (!this._isNewValueInCurrentRange) { - const newValue = SliderBase.getValueFromInteraction(event, this.step, this.min, this.max, this.getBoundingClientRect(), this.directionStart); + const newValue = this.constructor.getValueFromInteraction(event, this._effectiveStep, this._effectiveMin, this._effectiveMax, this.getBoundingClientRect(), this.directionStart); - this._updateUI(parseFloat(newValue)); + this._updateHandlesAndRange(newValue); this.updateValue(this._valueAffected, newValue); this.storePropertyState(this._valueAffected); @@ -200,7 +196,7 @@ class RangeSlider extends SliderBase { /* If the press is in current range when dragging occurs we move the whole selected range (progress indicator). Calculate the new 'start' and 'end' values from the offset between the original press point and the current position of the mouse */ - const currentPageXPos = SliderBase.getPageXValueFromEvent(event); + const currentPageXPos = this.constructor.getPageXValueFromEvent(event); const newValues = this._calculateRangeOffset(currentPageXPos, this._initialStartHandlePageX); // No matter the which value is set as the one to be modified (this._valueAffected) we want to modify both of them @@ -209,7 +205,7 @@ class RangeSlider extends SliderBase { // Update the UI and the state acccording to the calculated new values this.updateValue("startValue", newValues[0]); this.updateValue("endValue", newValues[1]); - this._updateUI(null); + this._updateHandlesAndRange(null); this.storePropertyState("startValue", "endValue"); } @@ -265,9 +261,9 @@ class RangeSlider extends SliderBase { return [this.startValue, this.endValue]; } - const min = this.min; - const max = this.max; - const step = this.step; + const min = this._effectiveMin; + const max = this._effectiveMax; + const step = this._effectiveStep; const dom = this.getBoundingClientRect(); const selectedRange = this.endValue - this.startValue; @@ -283,26 +279,26 @@ class RangeSlider extends SliderBase { Repeat the same calculations in case of a dragging in the opposite direction */ if (currentPageXPos > this._initialPageXPosition) { startValuePageX = initialStartHandlePageXPos + positionOffset; - startValue = SliderBase.computedValueFromPageX(startValuePageX, min, max, dom, this.directionStart); - startValue = SliderBase.getSteppedValue(startValue, step, min); + startValue = this.constructor.computedValueFromPageX(startValuePageX, min, max, dom, this.directionStart); + startValue = this.constructor.getSteppedValue(startValue, step, min); } else { startValuePageX = initialStartHandlePageXPos - positionOffset; - startValue = SliderBase.computedValueFromPageX(startValuePageX, min, max, dom, this.directionStart); - startValue = SliderBase.getSteppedValue(startValue, step, min); + startValue = this.constructor.computedValueFromPageX(startValuePageX, min, max, dom, this.directionStart); + startValue = this.constructor.getSteppedValue(startValue, step, min); } // When the end handle reaches the max possible value prevent the start handle from moving // And the opposite - if the start handle reaches the beginning of the slider keep the initially selected range. - startValue = SliderBase.clipValue(startValue, min, max - selectedRange); + startValue = this.constructor.clipValue(startValue, min, max - selectedRange); const endValue = startValue + selectedRange; this._prevCursorPosition = currentPageXPos; return [startValue, endValue]; } - _updateUI(newValue) { - const max = this.max; - const min = this.min; + _updateHandlesAndRange(newValue) { + const max = this._effectiveMax; + const min = this._effectiveMin; const prevStartValue = this.getStoredPropertyState("startValue"); const prevEndValue = this.getStoredPropertyState("endValue"); @@ -361,7 +357,7 @@ class RangeSlider extends SliderBase { [this.directionStart]: `${this._secondHandlePositionFromStart}%`, }, tickmarks: { - "background": `${this._tickmarksBackground}`, + "background": `${this._tickmarks}`, }, label: { "width": `${this._labelWidth}%`, diff --git a/packages/main/src/SliderBase.hbs b/packages/main/src/SliderBase.hbs index 861366cc83ba..d06d8436734f 100644 --- a/packages/main/src/SliderBase.hbs +++ b/packages/main/src/SliderBase.hbs @@ -2,7 +2,6 @@ class="ui5-slider-root" @mousedown="{{_onmousedown}}" @touchstart="{{_ontouchstart}}" - @pointerdown="{{_onpointerdown}}" @mouseover="{{_onmouseover}}" @mouseout="{{_onmouseout}}" dir="{{effectiveDir}}" @@ -17,7 +16,7 @@
    {{#if labelInterval}}
      - {{#each labelItems}} + {{#each _labels}}
    • {{this}}
    • {{/each}}
    diff --git a/packages/main/src/SliderBase.js b/packages/main/src/SliderBase.js index 6cccd45afe98..a42c7de05d93 100644 --- a/packages/main/src/SliderBase.js +++ b/packages/main/src/SliderBase.js @@ -36,8 +36,9 @@ const metadata = { 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). - * 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. + * 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 @@ -48,8 +49,10 @@ const metadata = { defaultValue: 1, }, /** - * Displays a label with a value on every N-th step. 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 - than every second + * 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} @@ -69,7 +72,7 @@ const metadata = { * @defaultvalue false * @public */ - showTickmarks: { + showTickmarks: { type: Boolean, }, /** @@ -83,7 +86,7 @@ const metadata = { type: Boolean, }, /** - * Defines whether the slider is in disabled state. + * Defines whether the slider is in disabled state. * * @type {boolean} * @defaultvalue false @@ -162,9 +165,6 @@ class SliderBase extends UI5Element { min: null, max: null, }; - - // Stores the label values for the tickmarks - this._labelItems = []; } static get metadata() { @@ -192,19 +192,18 @@ class SliderBase extends UI5Element { } static get UP_EVENTS() { - return ["mouseup", "pointerup", "touchend"]; + return ["mouseup", "touchend"]; } static get MOVE_EVENT_MAP() { return { mousedown: "mousemove", - pointerdown: "pointermove", touchstart: "touchmove", }; } - get labelItems() { - return this._labelItems; + static get MIN_SPACE_BETWEEN_TICKMARKS() { + return 8; } get classes() { @@ -217,15 +216,10 @@ class SliderBase extends UI5Element { onEnterDOM() { ResizeHandler.register(this, this._resizeHandler); - this.addEventListener("mouseover", this._mouseOverHandler); - this.addEventListener("mouseout", this._mouseOutHandler); } onExitDOM() { ResizeHandler.deregister(this, this._handleResize); - this.removeEventListener("mouseover", this._mouseOverHandler); - this.removeEventListener("mouseout", this._mouseOutHandler); - this._labelItems = null; } onAfterRendering() { @@ -240,10 +234,6 @@ class SliderBase extends UI5Element { this._onmousedown(event); } - _ontpointerdown(event) { - this._onmousedown(event); - } - /** Shows the tooltip(s) if the showTooltip property is set to true * * @private @@ -266,7 +256,7 @@ class SliderBase extends UI5Element { } /** - * Handle the responsiveness of the Slider's UI elements when resing + * Handle the responsiveness of the Slider's UI elements when resizing * * @private */ @@ -284,12 +274,10 @@ class SliderBase extends UI5Element { // 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 < 8) { - this._tickmarksBackground = `linear-gradient(to right, currentColor 1px, transparent 0) 0 center / calc(100% - 1px) 100% repeat-x`; + if (spaceBetweenTickmarks < SliderBase.MIN_SPACE_BETWEEN_TICKMARKS) { this._hiddenTickmarks = true; this._labelsOverlapping = true; } else { - this._drawDefaultTickmarks(this.step, this.max, this.min); this._hiddenTickmarks = false; } @@ -297,14 +285,11 @@ class SliderBase extends UI5Element { return; } - // Cache the labels if not yet fetched - if (!this._labels) { - this._labels = this.shadowRoot.querySelectorAll(".ui5-slider-labels li"); - } // Check if there are any overlapping labels. // If so - only the first and the last one should be visible - this._labelsOverlapping = Array.prototype.some.call(this._labels, label => label.scrollWidth > label.clientWidth); + const labelItems = this.shadowRoot.querySelectorAll(".ui5-slider-labels li"); + this._labelsOverlapping = [...labelItems].some(label => label.scrollWidth > label.clientWidth); } /** @@ -324,6 +309,7 @@ class SliderBase extends UI5Element { this._boundingClientRect = this.getBoundingClientRect(); const newValue = SliderBase.getValueFromInteraction(event, this.step, min, max, this._boundingClientRect, this.directionStart); + this._valueOnInteractionStart = newValue; return newValue; } @@ -334,11 +320,15 @@ class SliderBase extends UI5Element { * @protected */ handleUpBase() { - this.fireEvent("change"); - SliderBase.UP_EVENTS.forEach(upEventType => window.removeEventListener(upEventType, this._upHandler)); + 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; } /** @@ -411,7 +401,7 @@ class SliderBase extends UI5Element { /** * 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. + * Returns the value rounded to a precision of at most 2 digits after decimal point. * * @protected */ @@ -445,21 +435,20 @@ class SliderBase extends UI5Element { syncUIAndState(...values) { // Validate step and update the stored state for the step property. if (this.isPropertyUpdated("step")) { - this._setStep(this.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._normalizeMinMaxValues(this.min, this.max); - this._drawDefaultTickmarks(this.step, this.max, this.min); 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.min, this.max); + const normalizedValue = SliderBase.clipValue(this[valueType], this._effectiveMin, this._effectiveMax); this.updateValue(valueType, normalizedValue); this.storePropertyState(valueType); }); @@ -523,28 +512,25 @@ class SliderBase extends UI5Element { return this.effectiveDir === "rtl" ? "right" : "left"; } - _normalizeMinMaxValues(min, max) { - if (min > max) { - this.min = max; - this.max = min; - } - } - /** * Calculates and draws the tickmarks with a CSS gradient style * * @private */ - _drawDefaultTickmarks(step, max, min) { - if (!this.showTickmarks || !this.step) { + 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(max); - const minStr = String(min); - const stepStr = String(step); + 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 @@ -562,12 +548,7 @@ class SliderBase extends UI5Element { const tickmarksGradientdPattern = `0 center / calc((100% - ${tickmarkWidth}) / (${this._tickmarksAmount})) 100% repeat-x`; // Combine to get the complete CSS background gradient property value - this._tickmarksBackground = `${tickmarksGradientBase + tickmarksGradientdPattern}`; - - // If labelsInterval is specified draw labels for the necessary tickmarks - if (this.labelInterval > 0) { - this._drawDefaultLabels(); - } + return `${tickmarksGradientBase + tickmarksGradientdPattern}`; } /** @@ -575,10 +556,14 @@ class SliderBase extends UI5Element { * * @private */ - _drawDefaultLabels() { + _createLabels() { + if (!this.labelInterval || !this.showTickmarks) { + return; + } + const labelInterval = this.labelInterval; - const step = this.step; - const newNumberOfLabels = (this.max - this.min) / (step * labelInterval); + const step = this._effectiveStep; + const newNumberOfLabels = (this._effectiveMax - this._effectiveMin) / (step * labelInterval); // If the required labels are already rendered if (newNumberOfLabels === this._oldNumberOfLabels) { @@ -587,7 +572,7 @@ class SliderBase extends UI5Element { this._oldNumberOfLabels = newNumberOfLabels; this._labelWidth = 100 / newNumberOfLabels; - this._labelItems = []; + this._labelValues = []; // If the step value is not a round number get its precision const stepPrecision = SliderBase._getDecimalPrecisionOfNumber(step); @@ -598,11 +583,15 @@ class SliderBase extends UI5Element { // "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.min).toFixed(stepPrecision); - this._labelItems.push(labelItemNumber); + const labelItemNumber = ((i * step * labelInterval) + this._effectiveMin).toFixed(stepPrecision); + this._labelValues.push(labelItemNumber); } } + get _labels() { + return this._labelValues || []; + } + /** * Calculates space between tickmarks * @@ -616,32 +605,50 @@ class SliderBase extends UI5Element { } /** - * Normalizes a new step property value. - * If tickmarks are enabled recreates them according to it. + * Notify in case of a invalid step value type * * @private */ - _setStep(step) { + _validateStep(step) { if (step === 0) { console.warn("The 'step' property must be a positive float number"); // eslint-disable-line - return; } 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 - step = Math.abs(step); } 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 - step = 1; } + } - if (!this._initialRendering) { - this._drawDefaultTickmarks(step, this.max, this.min); + /** + * 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) { + step = Math.abs(step); } - this.step = 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); } } diff --git a/packages/main/src/themes/SliderBase.css b/packages/main/src/themes/SliderBase.css index 1e1dd28fc5ac..dd46d1ae22c9 100644 --- a/packages/main/src/themes/SliderBase.css +++ b/packages/main/src/themes/SliderBase.css @@ -5,7 +5,6 @@ } :host { - display: inline-block; box-sizing: border-box; cursor: pointer; vertical-align: top; @@ -13,10 +12,14 @@ height: 3.3125rem; } +:host(:not([hidden])) { + display: inline-block; +} + .ui5-slider-root { box-sizing: border-box; height: 3.3125rem; - padding: 1.406rem 0; + padding: 1rem 0; touch-action: none; -ms-touch-action: pan-y; } @@ -78,10 +81,10 @@ text-align: center; visibility: hidden; pointer-events: none; + line-height: 1rem; position: absolute; left: 50%; transform: translate(-50%); - line-height: 1.375rem; bottom: var(--_ui5_slider_tooltip_bottom); background: var(--_ui5_slider_tooltip_background); border: 1px solid var(--_ui5_slider_tooltip_border_color); diff --git a/packages/main/src/themes/base/SliderBase-parameters.css b/packages/main/src/themes/base/SliderBase-parameters.css index cbd2974ed32b..6f4c45a78e11 100644 --- a/packages/main/src/themes/base/SliderBase-parameters.css +++ b/packages/main/src/themes/base/SliderBase-parameters.css @@ -22,10 +22,10 @@ --_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: 1.375rem; + --_ui5_slider_tooltip_height: 1rem; --_ui5_slider_tooltip_box_shadow: none; --_ui5_slider_tooltip_min_width: 2rem; - --_ui5_slider_tooltip_bottom: 1.725rem; + --_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/test/specs/RangeSlider.spec.js b/packages/main/test/specs/RangeSlider.spec.js index 4f44a728dae4..a3947917ce7a 100644 --- a/packages/main/test/specs/RangeSlider.spec.js +++ b/packages/main/test/specs/RangeSlider.spec.js @@ -162,14 +162,6 @@ describe("Range Slider elements - tooltip, step, tickmarks, labels", () => { describe("Properties synchronization and normalization", () => { - it("If a negative number is set to the step property it should beconverted it to its positive equivalent", () => { - const rangeSlider = browser.$("#range-slider-tickmarks-labels"); - - rangeSlider.setProperty("step", -15); - - assert.strictEqual(rangeSlider.getProperty("step"), 15, "Step value should be a positive number"); - }); - it("Should fallback to default value of 1 if step property is not a valid number", () => { const rangeSlider = browser.$("#range-slider-tickmarks-labels"); @@ -178,30 +170,16 @@ describe("Properties synchronization and normalization", () => { assert.strictEqual(rangeSlider.getProperty("step"), 1, "Step value should be its default value"); }); - it("If min property is set to a greater number than the max property they should be swapped", () => { + it("Should keep the current values between the boundaries of min and max properties", () => { const rangeSlider = browser.$("#range-slider-tickmarks-labels"); - rangeSlider.setProperty("max", 10); rangeSlider.setProperty("min", 100); - - assert.strictEqual(rangeSlider.getProperty("min"), 10, "min property should be less than the max one"); - assert.strictEqual(rangeSlider.getProperty("max"), 100, "max property should be greater than the min one"); - assert.strictEqual(rangeSlider.getProperty("startValue"), 10, "startValue property should be within the boundaries of the swapped min and max props"); - - rangeSlider.setProperty("min", 200); - - assert.strictEqual(rangeSlider.getProperty("min"), 100, "min property should be the 100"); - assert.strictEqual(rangeSlider.getProperty("max"), 200, "max property should be 200"); - assert.strictEqual(rangeSlider.getProperty("startValue"), 100, "value property should be within the boundaries of the swapped min and max props"); - }); - - it("Should keep the current values between the boundaries of min and max properties", () => { - const rangeSlider = browser.$("#range-slider-tickmarks-labels"); + rangeSlider.setProperty("max", 200); rangeSlider.setProperty("endValue", 300); assert.strictEqual(rangeSlider.getProperty("endValue"), 200, "value prop should always be lower than the max value"); - + rangeSlider.setProperty("startValue", 99); assert.strictEqual(rangeSlider.getProperty("startValue"), 100, "value prop should always be greater than the min value"); From 68bfb3f6d8a5bb4ef5f9f5745ed9ba7bd7f8daa9 Mon Sep 17 00:00:00 2001 From: ndeshev Date: Wed, 18 Nov 2020 09:17:17 +0200 Subject: [PATCH 46/52] Sync Slider.js and Slider's test with the Slider's PR --- packages/main/src/Slider.js | 36 +++++----- packages/main/test/specs/RangeSlider.spec.js | 2 +- packages/main/test/specs/Slider.spec.js | 74 ++++---------------- 3 files changed, 30 insertions(+), 82 deletions(-) diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index 02294e5f7c76..f73adaea270e 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -80,10 +80,6 @@ class Slider extends SliderBase { return SliderTemplate; } - static get styles() { - return super.styles; - } - constructor() { super(); this._stateStorage.value = null; @@ -108,7 +104,7 @@ class Slider extends SliderBase { this.notResized = true; this.syncUIAndState("value"); - this._updateUI(this.value); + this._updateHandleAndProgress(this.value); } /** @@ -123,11 +119,11 @@ class Slider extends SliderBase { return; } - const newValue = this.handleDownBase(event, this.min, this.max); + 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(SliderBase.getPageXValueFromEvent(event))) { - this._updateUI(newValue); + if (!this._isHandlePressed(this.constructor.getPageXValueFromEvent(event))) { + this._updateHandleAndProgress(newValue); this.updateValue("value", newValue); } } @@ -142,13 +138,13 @@ class Slider extends SliderBase { // 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) { + if (this.disabled || this._effectiveStep === 0) { return; } - const newValue = SliderBase.getValueFromInteraction(event, this.step, this.min, this.max, this.getBoundingClientRect(), this.directionStart); + const newValue = this.constructor.getValueFromInteraction(event, this._effectiveStep, this._effectiveMin, this._effectiveMax, this.getBoundingClientRect(), this.directionStart); - this._updateUI(newValue); + this._updateHandleAndProgress(newValue); this.updateValue("value", newValue); } @@ -172,31 +168,31 @@ class Slider extends SliderBase { } - /** Updates the UI representation of the Slider according to its internal state. + /** Updates the UI representation of the progress bar and handle position * * @private */ - _updateUI(newValue) { - const max = this.max; - const min = this.min; + _updateHandleAndProgress(newValue) { + const max = this._effectiveMax; + const min = this._effectiveMin; // The progress (completed) percentage of the slider. - this._percentageComplete = (newValue - min) / (max - min); + 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._percentageComplete * 100; + this._handlePositionFromStart = this._progressPercentage * 100; } get styles() { return { progress: { - "transform": `scaleX(${this._percentageComplete})`, + "transform": `scaleX(${this._progressPercentage})`, "transform-origin": `${this.directionStart} top`, }, handle: { [this.directionStart]: `${this._handlePositionFromStart}%`, }, tickmarks: { - "background": `${this._tickmarksBackground}`, + "background": `${this._tickmarks}`, }, label: { "width": `${this._labelWidth}%`, @@ -216,7 +212,7 @@ class Slider extends SliderBase { } get tooltipValue() { - const stepPrecision = SliderBase._getDecimalPrecisionOfNumber(this.step); + const stepPrecision = this.constructor._getDecimalPrecisionOfNumber(this._effectiveStep); return this.value.toFixed(stepPrecision); } diff --git a/packages/main/test/specs/RangeSlider.spec.js b/packages/main/test/specs/RangeSlider.spec.js index a3947917ce7a..87a9266dbc5f 100644 --- a/packages/main/test/specs/RangeSlider.spec.js +++ b/packages/main/test/specs/RangeSlider.spec.js @@ -70,7 +70,7 @@ describe("Testing Range Slider interactions", () => { const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); - rangeSlider.dragAndDrop({ x: 100, y: 0 }); + rangeSlider.dragAndDrop({ x: 100, y: 1 }); assert.strictEqual(rangeSlider.getProperty("startValue"), 8, "startValue should be 8"); assert.strictEqual(rangeSlider.getProperty("endValue"), 33, "endValue should be 33"); diff --git a/packages/main/test/specs/Slider.spec.js b/packages/main/test/specs/Slider.spec.js index fb60f3623d33..e5894a588a46 100644 --- a/packages/main/test/specs/Slider.spec.js +++ b/packages/main/test/specs/Slider.spec.js @@ -1,9 +1,10 @@ const assert = require("chai").assert; describe("Slider basic interactions", () => { - browser.url("http://localhost:8080/test-resources/pages/Slider.html"); 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"); @@ -14,7 +15,6 @@ describe("Slider basic interactions", () => { assert.strictEqual(sliderHandle.getAttribute("style"), "left: 30%;", "Slider handle should be 30% from the start"); - slider.moveTo(); slider.click(); assert.strictEqual(sliderHandle.getAttribute("style"), "left: 50%;", "Slider handle should be in the middle of the slider"); @@ -30,10 +30,10 @@ describe("Slider basic interactions", () => { 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: 150, y: 1 }); + sliderHandle.dragAndDrop({ x:-100, y: 1 }); - assert.strictEqual(sliderHandle.getAttribute("style"), "left: 100%;", "Slider handle should be at the end of the slider and not beyond its boundaries"); - assert.strictEqual(slider.getProperty("value"), 10, "Slider current value should be 10"); + 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", () => { @@ -51,7 +51,7 @@ describe("Slider basic interactions", () => { it("Slider should not be interactive if the step property is 0", () => { const slider = browser.$("#inactive-slider"); - slider.click({ x: 200 }); + slider.click(); assert.strictEqual(slider.getProperty("value"), 0, "Slider with 0 step should still has its default value of 0"); }); @@ -59,9 +59,7 @@ describe("Slider basic interactions", () => { it("Disabled slider is not interactive", () => { const slider = browser.$("#disabled-slider-with-tickmarks"); - slider.click({ x: 100 }); - - assert.strictEqual(slider.getProperty("value"), 20, "Slider value should be the initially set one"); + assert.strictEqual(slider.isClickable(), false, "Range Slider should be disabled"); }); }); @@ -94,52 +92,6 @@ describe("Slider elements - tooltip, step, tickmarks, labels", () => { }); describe("Properties synchronization and normalization", () => { - - it("If a negative number is set to the step property it should beconverted it to its positive equivalent", () => { - const slider = browser.$("#slider-tickmarks-tooltips-labels"); - - slider.setProperty("step", -15); - - assert.strictEqual(slider.getProperty("step"), 15, "Step value should be a positive number"); - }); - - it("Should fallback to default value of 1 if step property is not a valid number", () => { - const slider = browser.$("#slider-tickmarks-tooltips-labels"); - - slider.setProperty("step", "String value"); - - assert.strictEqual(slider.getProperty("step"), 1, "Step value should be its default value"); - }); - - it("If min property is set to a greater number than the max property they should be swapped", () => { - const slider = browser.$("#basic-slider"); - - slider.setProperty("max", 10); - slider.setProperty("min", 100); - - assert.strictEqual(slider.getProperty("min"), 10, "min property should be less than the max one"); - assert.strictEqual(slider.getProperty("max"), 100, "max property should be greater than the min one"); - assert.strictEqual(slider.getProperty("value"), 10, "value property should be within the boundaries of the swapped min and max props"); - - slider.setProperty("min", 200); - - assert.strictEqual(slider.getProperty("min"), 100, "min property should be the 100"); - assert.strictEqual(slider.getProperty("max"), 200, "max property should be 200"); - assert.strictEqual(slider.getProperty("value"), 100, "value property should be within the boundaries of the swapped min and max props"); - }); - - it("Should keep the current value between the boundaries of min and max properties", () => { - const slider = browser.$("#basic-slider"); - - slider.setProperty("value", 300); - - assert.strictEqual(slider.getProperty("value"), 200, "value prop should always be lower than the max value"); - - slider.setProperty("value", 99); - - assert.strictEqual(slider.getProperty("value"), 100, "value prop should always be greater than the min value"); - }); - it("Should not 'stepify' current value if it is not in result of user interaction", () => { const slider = browser.$("#tickmarks-slider"); @@ -160,11 +112,11 @@ describe("Testing resize handling and RTL support", () => { 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 beginning of the Slider"); + 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 start"); + assert.strictEqual(sliderHandle.getAttribute("style"), "right: 30%;", "Slider handle should be 30% from the right"); slider.moveTo(); slider.click(); @@ -174,24 +126,24 @@ describe("Testing resize handling and RTL support", () => { sliderHandle.dragAndDrop({ x: -300, y: 1 }); - assert.strictEqual(sliderHandle.getAttribute("style"), "right: 80%;", "Slider handle should be 80% from the start of the slider"); + 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 start"); + 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 end of the slider and not beyond its boundaries"); + 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(500, 2000); + 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"); From 296a1ce129683e7f42aa24eda146b441a585cc71 Mon Sep 17 00:00:00 2001 From: ndeshev Date: Wed, 18 Nov 2020 14:39:09 +0200 Subject: [PATCH 47/52] Remove duplicated comment line --- packages/main/src/SliderBase.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/main/src/SliderBase.js b/packages/main/src/SliderBase.js index d9e71c7f58c1..a42c7de05d93 100644 --- a/packages/main/src/SliderBase.js +++ b/packages/main/src/SliderBase.js @@ -402,7 +402,6 @@ class SliderBase extends UI5Element { /** * Computes the new value (in %) from the pageX position of the cursor. * Returns the value rounded to a precision of at most 2 digits after decimal point. - * Returns the value rounded to a precision of at most 2 digits after decimal point. * * @protected */ From 24020e4cb2176bc2418c4d9000b44474acd0ea3d Mon Sep 17 00:00:00 2001 From: ndeshev Date: Wed, 18 Nov 2020 20:27:44 +0200 Subject: [PATCH 48/52] Code review improvements and refactoring --- packages/main/src/RangeSlider.js | 166 ++++++++++++++++++++++--------- 1 file changed, 117 insertions(+), 49 deletions(-) diff --git a/packages/main/src/RangeSlider.js b/packages/main/src/RangeSlider.js index ed9d6be8f52f..475af2281993 100644 --- a/packages/main/src/RangeSlider.js +++ b/packages/main/src/RangeSlider.js @@ -30,7 +30,7 @@ const metadata = { *

    * * @type {Float} - * @defaultvalue 0 + * @defaultvalue 100 * @public */ endValue: { @@ -46,7 +46,7 @@ const metadata = { * Represents a numerical interval and two handles (grips) to select a sub-range within it. * *

    Overview

    - * The purpose of the control is to enable visual selection of sub-ranges within a given interval. + * The purpose of the component to enable visual selection of sub-ranges within a given interval. * *

    Structure

    * The most important properties of the Range Slider are: @@ -78,7 +78,7 @@ const metadata = { * @constructor * @author SAP SE * @alias sap.ui.webcomponents.main.RangeSlider - * @extends sap.ui.webcomponents.base.UI5Element + * @extends sap.ui.webcomponents.main.SliderBase * @tagname ui5-range-slider * @since 1.0.0-rc.11 * @appenddocs SliderBase @@ -142,23 +142,14 @@ class RangeSlider extends SliderBase { return; } - const oldEndValue = this.endValue; - const oldStartValue = this.startValue; + // Calculate the new value from the press position of the event const newValue = this.handleDownBase(event, this._effectiveMin, this._effectiveMax); - // Check if the new value is in the current select range of values - this._isNewValueInCurrentRange = newValue > oldStartValue && newValue < oldEndValue; - // Save the initial press point coordinates (position). - this._initialPageXPosition = this.constructor.getPageXValueFromEvent(event); - // Determine value to be modified depending on the the handle clossest to the press point - this._setCurrentValueType(this._initialPageXPosition, newValue); + // Determine the rest of the needed details from the start of the interaction. + this._saveInteractionStartData(event, newValue); - // Use the progress bar to save the initial coordinates of the start-handle when the interaction begins. - // We will use it as a reference to calculate a moving offset if the whole range selection is dragged. - this._initialStartHandlePageX = this.directionStart === "left" ? this.shadowRoot.querySelector(".ui5-slider-progress").getBoundingClientRect().left : this.shadowRoot.querySelector(".ui5-slider-progress").getBoundingClientRect().right; - - // Do not yet update the RangeSlider if press is in range or over a handle. It will be updated if the user drags the mouse. - if (this._isNewValueInCurrentRange || this._handeIsPressed) { + // Do not yet update the RangeSlider if press is in range or over a handle. + if (this._inCurrentRange || this._handeIsPressed) { this._handeIsPressed = false; return; } @@ -169,6 +160,35 @@ class RangeSlider extends SliderBase { this.storePropertyState(this._valueAffected); } + + /** + * Determines and saves needed values from the start of the interaction: + * + * Is the value calculated is within the currently selected range; + * Initial pageX position of the start handle affected by the interaction; + * Initial pageX value of the pressed postion; + * Affected value property by the action; + * + * @private + */ + _saveInteractionStartData(event, newValue) { + const oldEndValue = this.endValue; + const oldStartValue = this.startValue; + const progressBarDom = this.shadowRoot.querySelector(".ui5-slider-progress").getBoundingClientRect(); + + // Check if the new value is in the current select range of values + this._inCurrentRange = newValue > oldStartValue && newValue < oldEndValue; + // Save the initial press point coordinates (position) + this._initialPageXPosition = this.constructor.getPageXValueFromEvent(event); + // Which element of the Range Slider is pressed and which value property to be modified on further interaction + this._pressTargetAndAffectedValue(this._initialPageXPosition, newValue); + + // Use the progress bar to save the initial coordinates of the start-handle when the interaction begins. + // We will use it as a reference to calculate a moving offset if the whole range selection is dragged. + this._initialStartHandlePageX = this.directionStart === "left" ? progressBarDom.left : progressBarDom.right; + } + + /** * Called when the user moves the slider * @@ -182,20 +202,36 @@ class RangeSlider extends SliderBase { return; } - // If the user does not drag the whole range selection, - // sync the internal state and the UI with the corresponding value change - if (!this._isNewValueInCurrentRange) { - const newValue = this.constructor.getValueFromInteraction(event, this._effectiveStep, this._effectiveMin, this._effectiveMax, this.getBoundingClientRect(), this.directionStart); - - this._updateHandlesAndRange(newValue); - this.updateValue(this._valueAffected, newValue); - this.storePropertyState(this._valueAffected); - + // Update UI and state when dragging a single Range Slider handle + if (!this._inCurrentRange) { + this._updateValueOnHandleDrag(event); return; } - /* If the press is in current range when dragging occurs we move the whole selected range (progress indicator). - Calculate the new 'start' and 'end' values from the offset between the original press point and the current position of the mouse */ + // Updates UI and state when dragging of the whole selected range + this._updateValueOnRangeDrag(event); + } + + /** + * Updates UI and state when dragging a single Range Slider handle + * + * @private + */ + _updateValueOnHandleDrag(event) { + const newValue = this.constructor.getValueFromInteraction(event, this._effectiveStep, this._effectiveMin, this._effectiveMax, this.getBoundingClientRect(), this.directionStart); + + this._updateHandlesAndRange(newValue); + this.updateValue(this._valueAffected, newValue); + this.storePropertyState(this._valueAffected); + } + + /** + * Updates UI and state when dragging of the whole selected range + * + * @private + */ + _updateValueOnRangeDrag(event) { + // Calculate the new 'start' and 'end' values from the offset between the original press point and the current position of the mouse const currentPageXPos = this.constructor.getPageXValueFromEvent(event); const newValues = this._calculateRangeOffset(currentPageXPos, this._initialStartHandlePageX); @@ -216,15 +252,24 @@ class RangeSlider extends SliderBase { } /** - * Determines which one from the value/endValue properties has to be updated after the user action (based on closest handle) + * Determines where the press occured and which values of the Range Slider + * handles should be updated on further interaction. + * + * If the press is not in the selected range or over one of the Range Slider handles + * determines which one from the value/endValue properties has to be updated + * after the user action (based on closest handle). + * + * Set flags if the press is over a handle or in the selected range, + * in such cases no values are changed on interaction start, but could be + * updated later when dragging. * * @private */ - _setCurrentValueType(clientX, value) { + _pressTargetAndAffectedValue(clientX, value) { const startHandle = this.shadowRoot.querySelector(".start-handle"); const endHandle = this.shadowRoot.querySelector(".end-handle"); - // Check if the press point is in the bounds of any handle + // Check if the press point is in the bounds of any of the Range Slider handles const handleStartDomRect = startHandle.getBoundingClientRect(); const handleEndDomRect = endHandle.getBoundingClientRect(); const inHandleStartDom = clientX >= handleStartDomRect.left && clientX <= handleStartDomRect.right; @@ -232,12 +277,11 @@ class RangeSlider extends SliderBase { // Remove the flag for value in current range if the press action is over one of the handles if (inHandleEndDom || inHandleStartDom) { - this._isNewValueInCurrentRange = false; + this._inCurrentRange = false; this._handeIsPressed = true; } // Return that handle that is closer to the press point - // If the two handles are overlapping return the second (end) one as in general the more common drag move is to the right if (inHandleEndDom || value > this.endValue) { this._valueAffected = "endValue"; } @@ -249,8 +293,13 @@ class RangeSlider extends SliderBase { } /** - * Computes new 'start' and 'end' values in case the user is moving the whole range progress indicator. - * Returns an array with the values + * Calculates startValue/endValue properties when the whole range is moved. + * + * Uses the change of the position of the start handle and adds the initially + * selected range to it, to determine the whole range offset. + * + * @param {Integer} currentPageXPos The current horizontal position of the cursor/touch + * @param {Integer} initialStartHandlePageXPos The initial horizontal position of the start handle * * @private */ @@ -261,39 +310,58 @@ class RangeSlider extends SliderBase { return [this.startValue, this.endValue]; } + const min = this._effectiveMin; + const max = this._effectiveMax; + const selectedRange = this.endValue - this.startValue; + + // Computes the new value based on the difference of the current cursor location from the start of the interaction + let startValue = this._calculateStartValueByOffset(currentPageXPos, initialStartHandlePageXPos); + + // When the end handle reaches the max possible value prevent the start handle from moving + // And the opposite - if the start handle reaches the beginning of the slider keep the initially selected range. + startValue = this.constructor.clipValue(startValue, min, max - selectedRange); + + return [startValue, startValue + selectedRange]; + } + + /** + * Computes the new value based on the difference of the current cursor location from the + * start of the interaction. + * + * @param {Integer} currentPageXPos The current horizontal position of the cursor/touch + * @param {Integer} initialStartHandlePageXPos The initial horizontal position of the start handle + * + * @private + */ + _calculateStartValueByOffset(currentPageXPos, initialStartHandlePageXPos) { const min = this._effectiveMin; const max = this._effectiveMax; const step = this._effectiveStep; const dom = this.getBoundingClientRect(); - const selectedRange = this.endValue - this.startValue; - // The difference between the new position of the pointer and when the press event initial occured, based on the dragging direction - const positionOffset = currentPageXPos > this._initialPageXPosition ? currentPageXPos - this._initialPageXPosition : this._initialPageXPosition - currentPageXPos; let startValue; - let startValuePageX; + let startValuePageX; + let positionOffset; - /* If the dragging direction is from min to max (left to right, by LTR default): + /* Depending on the dragging direction: - calculate the new position of the start handle from its old pageX value combined with the movement offset; - calculate the start value based on its new pageX coordinates; - - stepify the calculated value based on the specified step property; - Repeat the same calculations in case of a dragging in the opposite direction */ + - 'stepify' the calculated value based on the specified step property; */ if (currentPageXPos > this._initialPageXPosition) { + // Difference between the new position of the pointer and when the press event initial occured + positionOffset = currentPageXPos - this._initialPageXPosition; + startValuePageX = initialStartHandlePageXPos + positionOffset; startValue = this.constructor.computedValueFromPageX(startValuePageX, min, max, dom, this.directionStart); startValue = this.constructor.getSteppedValue(startValue, step, min); } else { + positionOffset = this._initialPageXPosition - currentPageXPos; startValuePageX = initialStartHandlePageXPos - positionOffset; startValue = this.constructor.computedValueFromPageX(startValuePageX, min, max, dom, this.directionStart); startValue = this.constructor.getSteppedValue(startValue, step, min); } - // When the end handle reaches the max possible value prevent the start handle from moving - // And the opposite - if the start handle reaches the beginning of the slider keep the initially selected range. - startValue = this.constructor.clipValue(startValue, min, max - selectedRange); - const endValue = startValue + selectedRange; - - this._prevCursorPosition = currentPageXPos; - return [startValue, endValue]; + return startValue; } _updateHandlesAndRange(newValue) { From dc03e3a495f2a45d86f850d236530d545fa7eaf0 Mon Sep 17 00:00:00 2001 From: ndeshev Date: Thu, 19 Nov 2020 08:14:15 +0200 Subject: [PATCH 49/52] Fix firing of change event for both type of Sliders --- packages/main/src/RangeSlider.js | 15 ++++++++++++--- packages/main/src/Slider.js | 6 ++++++ packages/main/src/SliderBase.js | 8 +------- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/packages/main/src/RangeSlider.js b/packages/main/src/RangeSlider.js index 475af2281993..11144340b2f0 100644 --- a/packages/main/src/RangeSlider.js +++ b/packages/main/src/RangeSlider.js @@ -172,12 +172,14 @@ class RangeSlider extends SliderBase { * @private */ _saveInteractionStartData(event, newValue) { - const oldEndValue = this.endValue; - const oldStartValue = this.startValue; const progressBarDom = this.shadowRoot.querySelector(".ui5-slider-progress").getBoundingClientRect(); + // Save the state of the value properties on the start of the interaction + this._prevStartValue = this.startValue; + this._prevEndValue = this.endValue; + // Check if the new value is in the current select range of values - this._inCurrentRange = newValue > oldStartValue && newValue < oldEndValue; + this._inCurrentRange = newValue > this._prevStartValue && newValue < this._prevEndValue; // Save the initial press point coordinates (position) this._initialPageXPosition = this.constructor.getPageXValueFromEvent(event); // Which element of the Range Slider is pressed and which value property to be modified on further interaction @@ -246,9 +248,16 @@ class RangeSlider extends SliderBase { } _handleUp() { + if (this.startValue !== this._prevStartValue || this.endValue !== this._prevEndValue) { + this.fireEvent("change"); + } + this._swapValues(); this.handleUpBase(); + this._valueAffected = null; + this._prevStartValue = null; + this._prevEndValue = null; } /** diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index f73adaea270e..6739ed8cf12f 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -120,6 +120,7 @@ class Slider extends SliderBase { } const newValue = this.handleDownBase(event, this._effectiveMin, this._effectiveMax); + this._valueOnInteractionStart = this.value; // 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))) { @@ -153,7 +154,12 @@ class Slider extends SliderBase { * @private */ _handleUp(event) { + if (this._valueOnInteractionStart !== this.value) { + this.fireEvent("change"); + } + this.handleUpBase(); + this._valueOnInteractionStart = null; } /** Determines if the press is over the handle diff --git a/packages/main/src/SliderBase.js b/packages/main/src/SliderBase.js index a42c7de05d93..2c43065237bb 100644 --- a/packages/main/src/SliderBase.js +++ b/packages/main/src/SliderBase.js @@ -309,7 +309,6 @@ class SliderBase extends UI5Element { this._boundingClientRect = this.getBoundingClientRect(); const newValue = SliderBase.getValueFromInteraction(event, this.step, min, max, this._boundingClientRect, this.directionStart); - this._valueOnInteractionStart = newValue; return newValue; } @@ -319,16 +318,11 @@ class SliderBase extends UI5Element { * * @protected */ - handleUpBase() { - if (this._valueOnInteractionStart !== this.value) { - this.fireEvent("change"); - } - + handleUpBase(valueType) { SliderBase.UP_EVENTS.forEach(upEventType => window.removeEventListener(upEventType, this._upHandler)); window.removeEventListener(this._moveEventType, this._moveHandler); this._moveEventType = null; - this._valueOnInteractionStart = null; } /** From 79628291c8f85ebc04c8f034917d0fdcb6060657 Mon Sep 17 00:00:00 2001 From: ndeshev Date: Fri, 20 Nov 2020 12:25:54 +0200 Subject: [PATCH 50/52] Fix tooltips on mobile, fix range-slider handle styles --- packages/main/src/RangeSlider.hbs | 8 ++++---- packages/main/src/RangeSlider.js | 12 ++++++++++-- packages/main/src/Slider.js | 8 ++++++++ packages/main/src/themes/SliderBase.css | 14 +++++++++++++- .../main/src/themes/base/SliderBase-parameters.css | 2 ++ 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/packages/main/src/RangeSlider.hbs b/packages/main/src/RangeSlider.hbs index d365f3b4a221..0c01161c6fee 100644 --- a/packages/main/src/RangeSlider.hbs +++ b/packages/main/src/RangeSlider.hbs @@ -1,16 +1,16 @@ {{>include "./SliderBase.hbs"}} {{#*inline "handles"}} -
    +
    {{#if showTooltip}} -
    +
    {{tooltipStartValue}}
    {{/if}}
    -
    +
    {{#if showTooltip}} -
    +
    {{tooltipEndValue}}
    {{/if}} diff --git a/packages/main/src/RangeSlider.js b/packages/main/src/RangeSlider.js index 11144340b2f0..88e74d90c5c3 100644 --- a/packages/main/src/RangeSlider.js +++ b/packages/main/src/RangeSlider.js @@ -136,6 +136,10 @@ class RangeSlider extends SliderBase { * @private */ _onmousedown(event) { + if (this.showTooltip) { + this._tooltipVisibility = "visible"; + } + // 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) { @@ -252,6 +256,10 @@ class RangeSlider extends SliderBase { this.fireEvent("change"); } + if (this.showTooltip) { + this._tooltipVisibility = "hidden"; + } + this._swapValues(); this.handleUpBase(); @@ -275,8 +283,8 @@ class RangeSlider extends SliderBase { * @private */ _pressTargetAndAffectedValue(clientX, value) { - const startHandle = this.shadowRoot.querySelector(".start-handle"); - const endHandle = this.shadowRoot.querySelector(".end-handle"); + const startHandle = this.shadowRoot.querySelector(".ui5-slider-handle--start"); + const endHandle = this.shadowRoot.querySelector(".ui5-slider-handle--end"); // Check if the press point is in the bounds of any of the Range Slider handles const handleStartDomRect = startHandle.getBoundingClientRect(); diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index 6739ed8cf12f..2709ae4699fe 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -113,6 +113,10 @@ class Slider extends SliderBase { * @private */ _onmousedown(event) { + if (this.showTooltip) { + this._tooltipVisibility = "visible"; + } + // 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) { @@ -158,6 +162,10 @@ class Slider extends SliderBase { this.fireEvent("change"); } + if (this.showTooltip) { + this._tooltipVisibility = "hidden"; + } + this.handleUpBase(); this._valueOnInteractionStart = null; } diff --git a/packages/main/src/themes/SliderBase.css b/packages/main/src/themes/SliderBase.css index dd46d1ae22c9..8cf91592ca91 100644 --- a/packages/main/src/themes/SliderBase.css +++ b/packages/main/src/themes/SliderBase.css @@ -68,15 +68,27 @@ width: var(--_ui5_slider_handle_width); } +.ui5-slider-handle--start, .ui5-slider-handle--end { + background: var(--_ui5_range_slider_handle_background); +} + + [dir="rtl"] .ui5-slider-handle { margin-right: var(--_ui5_slider_handle_margin_left); } -.ui5-slider-root:hover .ui5-slider-handle { +.ui5-slider-root:hover .ui5-slider-handle, +.ui5-slider-root:active .ui5-slider-handle, .ui5-slider-handle:active { background: var(--_ui5_slider_handle_hover_background); border-color: var(--_ui5_slider_handle_hover_border); } +.ui5-slider-root:hover .ui5-slider-handle--start, .ui5-slider-root:hover .ui5-slider-handle--end, +.ui5-slider-root:active .ui5-slider-handle--start, .ui5-slider-root:active .ui5-slider-handle--end, +.ui5-slider-handle--start:active, .ui5-slider-handle--end:active { + background: var(--_ui5_range_slider_handle_hover_background); +} + .ui5-slider-tooltip { text-align: center; visibility: hidden; diff --git a/packages/main/src/themes/base/SliderBase-parameters.css b/packages/main/src/themes/base/SliderBase-parameters.css index 6f4c45a78e11..f656c1dce3fe 100644 --- a/packages/main/src/themes/base/SliderBase-parameters.css +++ b/packages/main/src/themes/base/SliderBase-parameters.css @@ -9,10 +9,12 @@ --_ui5_slider_handle_width: 1.25rem; --_ui5_slider_handle_border: solid 0.125rem var(--sapField_BorderColor); --_ui5_slider_handle_background: var(--sapButton_Background); + --_ui5_range_slider_handle_background: rgba(var(--sapButton_Background), 0.25); --_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_range_slider_handle_hover_background: rgba(var(--sapButton_Background), 0.25); --_ui5_slider_tickmark_color: #89919a; --_ui5_slider_tickmark_top: -0.375rem; --_ui5_slider_disabled_opacity: 0.4; From deab7c7838f0c92875d013d924c8c8634f588bff Mon Sep 17 00:00:00 2001 From: ndeshev Date: Fri, 20 Nov 2020 12:52:34 +0200 Subject: [PATCH 51/52] Fix tooltips on mobile --- packages/main/src/RangeSlider.js | 8 -------- packages/main/src/Slider.js | 8 -------- packages/main/src/SliderBase.js | 10 ++++++++++ 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/main/src/RangeSlider.js b/packages/main/src/RangeSlider.js index 88e74d90c5c3..b1170a52e23e 100644 --- a/packages/main/src/RangeSlider.js +++ b/packages/main/src/RangeSlider.js @@ -136,10 +136,6 @@ class RangeSlider extends SliderBase { * @private */ _onmousedown(event) { - if (this.showTooltip) { - this._tooltipVisibility = "visible"; - } - // 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) { @@ -256,10 +252,6 @@ class RangeSlider extends SliderBase { this.fireEvent("change"); } - if (this.showTooltip) { - this._tooltipVisibility = "hidden"; - } - this._swapValues(); this.handleUpBase(); diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index 2709ae4699fe..6739ed8cf12f 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -113,10 +113,6 @@ class Slider extends SliderBase { * @private */ _onmousedown(event) { - if (this.showTooltip) { - this._tooltipVisibility = "visible"; - } - // 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) { @@ -162,10 +158,6 @@ class Slider extends SliderBase { this.fireEvent("change"); } - if (this.showTooltip) { - this._tooltipVisibility = "hidden"; - } - this.handleUpBase(); this._valueOnInteractionStart = null; } diff --git a/packages/main/src/SliderBase.js b/packages/main/src/SliderBase.js index 2c43065237bb..2fff49f31513 100644 --- a/packages/main/src/SliderBase.js +++ b/packages/main/src/SliderBase.js @@ -3,6 +3,8 @@ 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 { isPhone } from "@ui5/webcomponents-base/dist/Device.js"; + import { getTheme } from "@ui5/webcomponents-base/dist/config/Theme.js"; // Styles @@ -300,6 +302,10 @@ class SliderBase extends UI5Element { * @protected */ handleDownBase(event, min, max) { + if (isPhone() && this.showTooltip) { + this._tooltipVisibility = "visible"; + } + // 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; @@ -319,6 +325,10 @@ class SliderBase extends UI5Element { * @protected */ handleUpBase(valueType) { + if (isPhone() && this.showTooltip) { + this._tooltipVisibility = "hidden"; + } + SliderBase.UP_EVENTS.forEach(upEventType => window.removeEventListener(upEventType, this._upHandler)); window.removeEventListener(this._moveEventType, this._moveHandler); From 094c06abdcdbf890c3ae18a8826a2240abcf6aed Mon Sep 17 00:00:00 2001 From: ndeshev Date: Fri, 20 Nov 2020 14:28:00 +0200 Subject: [PATCH 52/52] Fix range-slider tests with the new handles and tooltips classes --- packages/main/test/specs/RangeSlider.spec.js | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/main/test/specs/RangeSlider.spec.js b/packages/main/test/specs/RangeSlider.spec.js index 87a9266dbc5f..04eedd27e9d6 100644 --- a/packages/main/test/specs/RangeSlider.spec.js +++ b/packages/main/test/specs/RangeSlider.spec.js @@ -7,7 +7,7 @@ describe("Testing Range Slider interactions", () => { browser.setWindowSize(1257, 2000); const rangeSlider = browser.$("#range-slider-tickmarks"); - const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle--start"); assert.strictEqual(startHandle.getAttribute("style"), "left: 0%;", "Initially if no value is set, the Range Slider start-handle is at the beginning of the Range Slider"); @@ -28,7 +28,7 @@ describe("Testing Range Slider interactions", () => { it("Changing the endValue is reflected", () => { const rangeSlider = browser.$("#range-slider-tickmarks"); - const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle--end"); assert.strictEqual(endHandle.getAttribute("style"), "left: 50%;", "Range Slider end-handle is should be 50% from the start the Range Slider"); rangeSlider.setProperty("endValue", 10); @@ -67,8 +67,8 @@ describe("Testing Range Slider interactions", () => { it("Dragging the selected range should change both values and handles", () => { const rangeSlider = browser.$("#range-slider-tickmarks"); - const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); - const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle--start"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle--end"); rangeSlider.dragAndDrop({ x: 100, y: 1 }); @@ -78,8 +78,8 @@ describe("Testing Range Slider interactions", () => { it("Dragging the start-handle pass the end-handle should swap the values", () => { const rangeSlider = browser.$("#range-slider-tickmarks"); - const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); - const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle--start"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle--end"); rangeSlider.setProperty("endValue", 9); @@ -91,8 +91,8 @@ describe("Testing Range Slider interactions", () => { it("Dragging the whole range selection should always keep the initially selected range and be within min/max values", () => { const rangeSlider = browser.$("#range-slider-tickmarks"); - const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); - const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle--start"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle--end"); rangeSlider.setProperty("endValue", 30); @@ -127,12 +127,12 @@ describe("Range Slider elements - tooltip, step, tickmarks, labels", () => { it("Range Slider tooltips are displayed showing the current value", () => { const rangeSlider = browser.$("#basic-range-slider-with-tooltip"); - const rangeSliderStartTooltip = rangeSlider.shadow$(".ui5-slider-tooltip.start-tooltip"); + const rangeSliderStartTooltip = rangeSlider.shadow$(".ui5-slider-tooltip--start"); const rangeSliderStartTooltipValue = rangeSliderStartTooltip.shadow$(".ui5-slider-tooltip-value"); - const rangeSliderEndTooltip = rangeSlider.shadow$(".ui5-slider-tooltip.end-tooltip"); + const rangeSliderEndTooltip = rangeSlider.shadow$(".ui5-slider-tooltip--end"); const rangeSliderEndTooltipValue = rangeSliderEndTooltip.shadow$(".ui5-slider-tooltip-value"); - const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); - const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle--start"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle--end"); rangeSlider.moveTo(); @@ -202,8 +202,8 @@ describe("Properties synchronization and normalization", () => { describe("Testing resize handling and RTL support", () => { it("Testing RTL support", () => { const rangeSlider = browser.$("#range-slider-tickmarks-labels"); - const startHandle = rangeSlider.shadow$(".ui5-slider-handle.start-handle"); - const endHandle = rangeSlider.shadow$(".ui5-slider-handle.end-handle"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle--start"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle--end"); rangeSlider.setAttribute("dir", "rtl"); rangeSlider.setProperty("min", 0);