Skip to content

Commit 2b9008c

Browse files
authored
feat(ui5-slider): Add Slider component (#2349)
FIXES: #1245
1 parent 03cae4b commit 2b9008c

25 files changed

+1462
-0
lines changed

packages/main/bundle.esm.js

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import RadioButton from "./dist/RadioButton.js";
6565
import ResponsivePopover from "./dist/ResponsivePopover.js";
6666
import SegmentedButton from "./dist/SegmentedButton.js";
6767
import Select from "./dist/Select.js";
68+
import Slider from "./dist/Slider.js";
6869
import Switch from "./dist/Switch.js";
6970
import MessageStrip from "./dist/MessageStrip.js";
7071
import MultiComboBox from "./dist/MultiComboBox.js";

packages/main/src/Slider.hbs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{{>include "./SliderBase.hbs"}}
2+
3+
{{#*inline "handles"}}
4+
<div class="ui5-slider-handle" style="{{styles.handle}}">
5+
{{#if showTooltip}}
6+
<div class="ui5-slider-tooltip" style="{{styles.tooltip}}">
7+
<span class="ui5-slider-tooltip-value">{{tooltipValue}}</span>
8+
</div>
9+
{{/if}}
10+
</div>
11+
{{/inline}}

packages/main/src/Slider.js

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
import Float from "@ui5/webcomponents-base/dist/types/Float.js";
2+
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
3+
import SliderBase from "./SliderBase.js";
4+
5+
// Template
6+
import SliderTemplate from "./generated/templates/SliderTemplate.lit.js";
7+
8+
/**
9+
* @public
10+
*/
11+
const metadata = {
12+
tag: "ui5-slider",
13+
languageAware: true,
14+
managedSlots: true,
15+
properties: /** @lends sap.ui.webcomponents.main.Slider.prototype */ {
16+
/**
17+
* Current value of the slider
18+
*
19+
* @type {Float}
20+
* @defaultvalue 0
21+
* @public
22+
*/
23+
value: {
24+
type: Float,
25+
defaultValue: 0,
26+
},
27+
},
28+
};
29+
30+
/**
31+
* @class
32+
*
33+
* <h3 class="comment-api-title">Overview</h3>
34+
* The Slider component represents a numerical range and a handle (grip).
35+
* The purpose of the component is to enable visual selection of a value in
36+
* a continuous numerical range by moving an adjustable handle.
37+
*
38+
* <h3>Structure</h3>
39+
* The most important properties of the Slider are:
40+
* <ul>
41+
* <li>min - The minimum value of the slider range</li>
42+
* <li>max - The maximum value of the slider range</li>
43+
* <li>value - The current value of the slider</li>
44+
* <li>step - Determines the increments in which the slider will move</li>
45+
* <li>showTooltip - Determines if a tooltip should be displayed above the handle</li>
46+
* <li>showTickmarks - Displays a visual divider between the step values</li>
47+
* <li>labelInterval - Labels some or all of the tickmarks with their values.</li>
48+
* </ul>
49+
*
50+
* <h3>Usage</h3>
51+
* The most common usecase is to select values on a continuous numerical scale (e.g. temperature, volume, etc. ).
52+
*
53+
* <h3>Responsive Behavior</h3>
54+
* The <code>ui5-slider</code> component adjusts to the size of its parent container by recalculating and
55+
* resizing the width of the control. You can move the slider handle in several different ways:
56+
* <ul>
57+
* <li>Drag and drop to the desired value</li>
58+
* <li>Click/tap on the range bar to move the handle to that location</li>
59+
* </ul>
60+
*
61+
* <h3>ES6 Module Import</h3>
62+
*
63+
* <code>import "@ui5/webcomponents/dist/Slider";</code>
64+
*
65+
* @constructor
66+
* @author SAP SE
67+
* @alias sap.ui.webcomponents.main.Slider
68+
* @extends sap.ui.webcomponents.base.UI5Element
69+
* @tagname ui5-slider
70+
* @since 1.0.0-rc.11
71+
* @appenddocs SliderBase
72+
* @public
73+
*/
74+
class Slider extends SliderBase {
75+
static get metadata() {
76+
return metadata;
77+
}
78+
79+
static get template() {
80+
return SliderTemplate;
81+
}
82+
83+
constructor() {
84+
super();
85+
this._stateStorage.value = null;
86+
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
87+
}
88+
89+
/**
90+
*
91+
* Check if the previously saved state is outdated. That would mean
92+
* either it is the initial rendering or that a property has been changed
93+
* programatically - because the previous state is always updated in
94+
* the interaction handlers.
95+
*
96+
* Normalize current properties, update the previously stored state.
97+
* Update the visual UI representation of the Slider
98+
*
99+
*/
100+
onBeforeRendering() {
101+
if (!this.isCurrentStateOutdated()) {
102+
return;
103+
}
104+
105+
this.notResized = true;
106+
this.syncUIAndState("value");
107+
this._updateHandleAndProgress(this.value);
108+
}
109+
110+
/**
111+
* Called when the user starts interacting with the slider
112+
*
113+
* @private
114+
*/
115+
_onmousedown(event) {
116+
// If step is 0 no interaction is available because there is no constant
117+
// (equal for all user environments) quantitative representation of the value
118+
if (this.disabled || this.step === 0) {
119+
return;
120+
}
121+
122+
const newValue = this.handleDownBase(event, this._effectiveMin, this._effectiveMax);
123+
124+
// Do not yet update the Slider if press is over a handle. It will be updated if the user drags the mouse.
125+
if (!this._isHandlePressed(this.constructor.getPageXValueFromEvent(event))) {
126+
this._updateHandleAndProgress(newValue);
127+
this.updateValue("value", newValue);
128+
}
129+
}
130+
131+
/**
132+
* Called when the user moves the slider
133+
*
134+
* @private
135+
*/
136+
_handleMove(event) {
137+
event.preventDefault();
138+
139+
// If step is 0 no interaction is available because there is no constant
140+
// (equal for all user environments) quantitative representation of the value
141+
if (this.disabled || this._effectiveStep === 0) {
142+
return;
143+
}
144+
145+
const newValue = this.constructor.getValueFromInteraction(event, this._effectiveStep, this._effectiveMin, this._effectiveMax, this.getBoundingClientRect(), this.directionStart);
146+
147+
this._updateHandleAndProgress(newValue);
148+
this.updateValue("value", newValue);
149+
}
150+
151+
/** Called when the user finish interacting with the slider
152+
*
153+
* @private
154+
*/
155+
_handleUp(event) {
156+
this.handleUpBase();
157+
}
158+
159+
/** Determines if the press is over the handle
160+
*
161+
* @private
162+
*/
163+
_isHandlePressed(clientX) {
164+
const sliderHandle = this.shadowRoot.querySelector(".ui5-slider-handle");
165+
const sliderHandleDomRect = sliderHandle.getBoundingClientRect();
166+
167+
return clientX >= sliderHandleDomRect.left && clientX <= sliderHandleDomRect.right;
168+
}
169+
170+
171+
/** Updates the UI representation of the progress bar and handle position
172+
*
173+
* @private
174+
*/
175+
_updateHandleAndProgress(newValue) {
176+
const max = this._effectiveMax;
177+
const min = this._effectiveMin;
178+
179+
// The progress (completed) percentage of the slider.
180+
this._progressPercentage = (newValue - min) / (max - min);
181+
// How many pixels from the left end of the slider will be the placed the affected by the user action handle
182+
this._handlePositionFromStart = this._progressPercentage * 100;
183+
}
184+
185+
get styles() {
186+
return {
187+
progress: {
188+
"transform": `scaleX(${this._progressPercentage})`,
189+
"transform-origin": `${this.directionStart} top`,
190+
},
191+
handle: {
192+
[this.directionStart]: `${this._handlePositionFromStart}%`,
193+
},
194+
tickmarks: {
195+
"background": `${this._tickmarks}`,
196+
},
197+
label: {
198+
"width": `${this._labelWidth}%`,
199+
},
200+
labelContainer: {
201+
"width": `100%`,
202+
[this.directionStart]: `-${this._labelWidth / 2}%`,
203+
},
204+
tooltip: {
205+
"visibility": `${this._tooltipVisibility}`,
206+
},
207+
};
208+
}
209+
210+
get labelItems() {
211+
return this._labelItems;
212+
}
213+
214+
get tooltipValue() {
215+
const stepPrecision = this.constructor._getDecimalPrecisionOfNumber(this._effectiveStep);
216+
return this.value.toFixed(stepPrecision);
217+
}
218+
219+
static async onDefine() {
220+
await fetchI18nBundle("@ui5/webcomponents");
221+
}
222+
}
223+
224+
Slider.define();
225+
226+
export default Slider;

packages/main/src/SliderBase.hbs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<div
2+
class="ui5-slider-root"
3+
@mousedown="{{_onmousedown}}"
4+
@touchstart="{{_ontouchstart}}"
5+
@mouseover="{{_onmouseover}}"
6+
@mouseout="{{_onmouseout}}"
7+
dir="{{effectiveDir}}"
8+
>
9+
<div class="ui5-slider-inner">
10+
<div class="ui5-slider-progress-container">
11+
<div class="ui5-slider-progress" style="{{styles.progress}}"></div>
12+
</div>
13+
14+
{{#if step}}
15+
{{#if showTickmarks}}
16+
<div class="ui5-slider-tickmarks" style="{{styles.tickmarks}}"></div>
17+
{{#if labelInterval}}
18+
<ul class="ui5-slider-labels {{classes.labelContainer}}" style="{{styles.labelContainer}}">
19+
{{#each _labels}}
20+
<li style="{{../styles.label}}">{{this}}</li>
21+
{{/each}}
22+
</ul>
23+
{{/if}}
24+
{{/if}}
25+
{{/if}}
26+
{{> handles}}
27+
</div>
28+
</div>
29+
30+
{{#*inline "handles"}}{{/inline}}

0 commit comments

Comments
 (0)