Skip to content

feat(ui5-slider): Add Slider component #2349

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 46 commits into from
Nov 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
92d3e95
Initial Slider POC essentials
ndeshev Oct 7, 2020
c67f585
Minor formatting fixes
ndeshev Oct 7, 2020
831da57
RangeSlider functionality improvements
ndeshev Oct 8, 2020
cd7e00d
Range selection functionality wip
ndeshev Oct 8, 2020
1954979
Range Slider concepts implemented
ndeshev Oct 8, 2020
415da4e
Most of the comments fixed
ndeshev Oct 9, 2020
f99f666
showTooltip property added for tooltiping current value, minor code c…
ndeshev Oct 9, 2020
15f3eaf
Tooltip functionality is introduced
ndeshev Oct 9, 2020
68d3400
Labels introduced
ndeshev Oct 11, 2020
b671445
Slider and Range Slider separation
ndeshev Oct 12, 2020
5eb2b54
Slider separation from the Range Slider functionality
ndeshev Oct 13, 2020
a6c4b34
SliderBase class introduced as a foundation of the Slider and Range S…
ndeshev Oct 13, 2020
5c26fc7
Dynamic manipulation of the inline styles fixed, some other minor cha…
ndeshev Oct 13, 2020
dd58f68
Bundling fix
ndeshev Oct 13, 2020
67da54a
Inline styling fixes
ndeshev Oct 15, 2020
ce40132
Minor fixes
ndeshev Oct 15, 2020
97afe65
Minor fixes 2
ndeshev Oct 15, 2020
db2f2fa
Cleanup & refactoring of the UI sync and onBeforeRendering cycle
ndeshev Oct 15, 2020
a825d06
Small formatting fixes
ndeshev Oct 15, 2020
74ccf26
Stepiphying the values now is improved as well as some other calculat…
ndeshev Oct 15, 2020
004f97b
calc improvements
ndeshev Oct 16, 2020
d6014de
Refactoring, resize handling implemented
ndeshev Oct 21, 2020
db456ef
Added support (handling) for touch and pointer event types
ndeshev Oct 21, 2020
1705983
Fiori 3 and Belize theming
ndeshev Oct 23, 2020
652de00
The high contrast variants of the Fiori 3 and Belize themes are added
ndeshev Oct 23, 2020
577c688
Refactor private/protected methods
ndeshev Oct 24, 2020
494694b
Add state storage for tracking state changes
ndeshev Oct 26, 2020
0e5e881
Introduce template inheritance. Slider & RangeSlider templates inheri…
ndeshev Oct 26, 2020
d9c9d87
Fix template inheritance
ndeshev Oct 26, 2020
ecc630f
Fixes and improvements from the code reviews
ndeshev Nov 2, 2020
e692495
Add playground samples, add aria-labelledby attribute to Slider's handle
ndeshev Nov 4, 2020
04685d6
fix touch handling bug, add since tag in the sample header, remove ar…
ndeshev Nov 4, 2020
da25fb0
InvisibleStyles removed from css
ndeshev Nov 4, 2020
e0a2350
Add RTL Support
ndeshev Nov 5, 2020
2c567d8
Add more documentation
ndeshev Nov 5, 2020
f2397a8
Add additional validations and normalizations, refactoring
ndeshev Nov 11, 2020
92055bc
Modify sample with the new name of the tickmarks prop
ndeshev Nov 11, 2020
dfd9375
Add tests, fix synchronization of the UI when min and max properties …
ndeshev Nov 13, 2020
0e4d37c
Perform linting
ndeshev Nov 13, 2020
3b68e23
Minor
ndshv Nov 13, 2020
0d878b0
Fix syncUIAndState bug. Show tooltips on disabled sliders
ndshv Nov 14, 2020
29f5f5f
Fix slider movement on initial mouse down of the handle
ndeshev Nov 16, 2020
cb80ab4
Fix tests according to the synched main repo
ndeshev Nov 16, 2020
c9c0e0b
Add appenddocs and since tags, sample docs fix
ndeshev Nov 16, 2020
a485770
Fix code reviews proposals
ndeshev Nov 17, 2020
26be6ac
Remove unnecessary log file and whitespace
ndeshev Nov 17, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/main/bundle.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
11 changes: 11 additions & 0 deletions packages/main/src/Slider.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{{>include "./SliderBase.hbs"}}

{{#*inline "handles"}}
<div class="ui5-slider-handle" style="{{styles.handle}}">
{{#if showTooltip}}
<div class="ui5-slider-tooltip" style="{{styles.tooltip}}">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tooltip is not in the static area. Was that discussed? Probably the tooltip can be cut if there are overflow: hidden elements on the page. I guess it's not the biggest issue and makes the implementation much simpler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. this was not discussed previously. It will be good if it is ok to refactor this in the near future.

<span class="ui5-slider-tooltip-value">{{tooltipValue}}</span>
</div>
{{/if}}
</div>
{{/inline}}
226 changes: 226 additions & 0 deletions packages/main/src/Slider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import Float from "@ui5/webcomponents-base/dist/types/Float.js";
import { fetchI18nBundle, getI18nBundle } from "@ui5/webcomponents-base/dist/i18nBundle.js";
import SliderBase from "./SliderBase.js";

// Template
import SliderTemplate from "./generated/templates/SliderTemplate.lit.js";

/**
* @public
*/
const metadata = {
tag: "ui5-slider",
languageAware: true,
managedSlots: true,
properties: /** @lends sap.ui.webcomponents.main.Slider.prototype */ {
/**
* Current value of the slider
*
* @type {Float}
* @defaultvalue 0
* @public
*/
value: {
type: Float,
defaultValue: 0,
},
},
};

/**
* @class
*
* <h3 class="comment-api-title">Overview</h3>
* The Slider component represents a numerical range and a handle (grip).
* The purpose of the component is to enable visual selection of a value in
* a continuous numerical range by moving an adjustable handle.
*
* <h3>Structure</h3>
* The most important properties of the Slider are:
* <ul>
* <li>min - The minimum value of the slider range</li>
* <li>max - The maximum value of the slider range</li>
* <li>value - The current value of the slider</li>
* <li>step - Determines the increments in which the slider will move</li>
* <li>showTooltip - Determines if a tooltip should be displayed above the handle</li>
* <li>showTickmarks - Displays a visual divider between the step values</li>
* <li>labelInterval - Labels some or all of the tickmarks with their values.</li>
* </ul>
*
* <h3>Usage</h3>
* The most common usecase is to select values on a continuous numerical scale (e.g. temperature, volume, etc. ).
*
* <h3>Responsive Behavior</h3>
* The <code>ui5-slider</code> component adjusts to the size of its parent container by recalculating and
* resizing the width of the control. You can move the slider handle in several different ways:
* <ul>
* <li>Drag and drop to the desired value</li>
* <li>Click/tap on the range bar to move the handle to that location</li>
* </ul>
*
* <h3>ES6 Module Import</h3>
*
* <code>import "@ui5/webcomponents/dist/Slider";</code>
*
* @constructor
* @author SAP SE
* @alias sap.ui.webcomponents.main.Slider
* @extends sap.ui.webcomponents.base.UI5Element
* @tagname ui5-slider
* @since 1.0.0-rc.11
* @appenddocs SliderBase
* @public
*/
class Slider extends SliderBase {
static get metadata() {
return metadata;
}

static get template() {
return SliderTemplate;
}

constructor() {
super();
this._stateStorage.value = null;
this.i18nBundle = getI18nBundle("@ui5/webcomponents");
}

/**
*
* Check if the previously saved state is outdated. That would mean
* either it is the initial rendering or that a property has been changed
* programatically - because the previous state is always updated in
* the interaction handlers.
*
* Normalize current properties, update the previously stored state.
* Update the visual UI representation of the Slider
*
*/
onBeforeRendering() {
if (!this.isCurrentStateOutdated()) {
return;
}

this.notResized = true;
this.syncUIAndState("value");
this._updateHandleAndProgress(this.value);
}

/**
* Called when the user starts interacting with the slider
*
* @private
*/
_onmousedown(event) {
// If step is 0 no interaction is available because there is no constant
// (equal for all user environments) quantitative representation of the value
if (this.disabled || this.step === 0) {
return;
}

const newValue = this.handleDownBase(event, this._effectiveMin, this._effectiveMax);

// Do not yet update the Slider if press is over a handle. It will be updated if the user drags the mouse.
if (!this._isHandlePressed(this.constructor.getPageXValueFromEvent(event))) {
this._updateHandleAndProgress(newValue);
this.updateValue("value", newValue);
}
}

/**
* Called when the user moves the slider
*
* @private
*/
_handleMove(event) {
event.preventDefault();

// If step is 0 no interaction is available because there is no constant
// (equal for all user environments) quantitative representation of the value
if (this.disabled || this._effectiveStep === 0) {
return;
}

const newValue = this.constructor.getValueFromInteraction(event, this._effectiveStep, this._effectiveMin, this._effectiveMax, this.getBoundingClientRect(), this.directionStart);

this._updateHandleAndProgress(newValue);
this.updateValue("value", newValue);
}

/** Called when the user finish interacting with the slider
*
* @private
*/
_handleUp(event) {
this.handleUpBase();
}

/** Determines if the press is over the handle
*
* @private
*/
_isHandlePressed(clientX) {
const sliderHandle = this.shadowRoot.querySelector(".ui5-slider-handle");
const sliderHandleDomRect = sliderHandle.getBoundingClientRect();

return clientX >= sliderHandleDomRect.left && clientX <= sliderHandleDomRect.right;
}


/** Updates the UI representation of the progress bar and handle position
*
* @private
*/
_updateHandleAndProgress(newValue) {
const max = this._effectiveMax;
const min = this._effectiveMin;

// The progress (completed) percentage of the slider.
this._progressPercentage = (newValue - min) / (max - min);
// How many pixels from the left end of the slider will be the placed the affected by the user action handle
this._handlePositionFromStart = this._progressPercentage * 100;
}

get styles() {
return {
progress: {
"transform": `scaleX(${this._progressPercentage})`,
"transform-origin": `${this.directionStart} top`,
},
handle: {
[this.directionStart]: `${this._handlePositionFromStart}%`,
},
tickmarks: {
"background": `${this._tickmarks}`,
},
label: {
"width": `${this._labelWidth}%`,
},
labelContainer: {
"width": `100%`,
[this.directionStart]: `-${this._labelWidth / 2}%`,
},
tooltip: {
"visibility": `${this._tooltipVisibility}`,
},
};
}

get labelItems() {
return this._labelItems;
}

get tooltipValue() {
const stepPrecision = this.constructor._getDecimalPrecisionOfNumber(this._effectiveStep);
return this.value.toFixed(stepPrecision);
}

static async onDefine() {
await fetchI18nBundle("@ui5/webcomponents");
}
}

Slider.define();

export default Slider;
30 changes: 30 additions & 0 deletions packages/main/src/SliderBase.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<div
class="ui5-slider-root"
@mousedown="{{_onmousedown}}"
@touchstart="{{_ontouchstart}}"
@mouseover="{{_onmouseover}}"
@mouseout="{{_onmouseout}}"
dir="{{effectiveDir}}"
>
<div class="ui5-slider-inner">
<div class="ui5-slider-progress-container">
<div class="ui5-slider-progress" style="{{styles.progress}}"></div>
</div>

{{#if step}}
{{#if showTickmarks}}
<div class="ui5-slider-tickmarks" style="{{styles.tickmarks}}"></div>
{{#if labelInterval}}
<ul class="ui5-slider-labels {{classes.labelContainer}}" style="{{styles.labelContainer}}">
{{#each _labels}}
<li style="{{../styles.label}}">{{this}}</li>
{{/each}}
</ul>
{{/if}}
{{/if}}
{{/if}}
{{> handles}}
</div>
</div>

{{#*inline "handles"}}{{/inline}}
Loading